Skip to main content
The k6 browser module brings browser automation and end-to-end web testing to k6 while supporting core k6 features. It adds browser-level APIs to interact with browsers and collect frontend performance metrics.

Overview

The browser module is built into k6 (not a separate extension) and provides:
  • Browser automation with Playwright-compatible APIs
  • Frontend performance metrics (Web Vitals)
  • Real browser interaction testing
  • Integration with protocol-level load testing
To use the browser module, you need:

Watch: Introduction to k6 browser

Use cases

The browser module helps you:

Frontend performance

Measure user experience with Web Vitals and page load metrics

Combined testing

Test frontend under protocol-level load to find real-world bottlenecks

Element validation

Verify all UI elements are interactive and loading correctly

User flows

Test complete user journeys across multiple pages
Browser-level testing helps answer questions like:
  • When my application receives thousands of simultaneous requests, what happens to the frontend?
  • How can I get metrics specific to browsers, like total page load time?
  • Are all my elements interactive on the frontend?
  • Are there loading spinners that take too long to disappear?

Quick start

1

Install k6

Download and install the latest k6 version.
2

Install a Chromium browser

Install Google Chrome, Microsoft Edge, or another Chromium-based browser.
3

Create a browser test

Generate a browser test template:
k6 new --template browser browser-script.js
4

Run the test

Execute your browser test:
k6 run browser-script.js

Example browser test

Here’s a simple browser test that navigates to a page and checks elements:
import { browser } from 'k6/browser';
import { check } from 'k6';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  try {
    await page.goto('https://test.k6.io/');

    // Check page title
    const header = await page.locator('h1').textContent();
    check(header, {
      'header exists': (h) => h === 'Welcome to test.k6.io',
    });

    // Click on a link
    await page.locator('a[href="/my_messages.php"]').click();

    // Wait for navigation and check new page
    await page.waitForSelector('h2');
    const pageTitle = await page.locator('h2').textContent();
    check(pageTitle, {
      'navigated successfully': (t) => t === 'Messages',
    });
  } finally {
    await page.close();
  }
}

Browser metrics

The browser module automatically collects metrics:

Browser-specific metrics

MetricDescription
browser_data_receivedData received by the browser
browser_data_sentData sent by the browser
browser_http_req_durationTime for browser HTTP requests
browser_http_req_failedRate of failed browser HTTP requests

Web Vitals

MetricDescription
browser_web_vital_clsCumulative Layout Shift
browser_web_vital_fcpFirst Contentful Paint
browser_web_vital_fidFirst Input Delay
browser_web_vital_inpInteraction to Next Paint
browser_web_vital_lcpLargest Contentful Paint
browser_web_vital_ttfbTime to First Byte
Example output:
BROWSER
browser_data_received.........: 357 kB 54 kB/s
browser_data_sent.............: 4.9 kB 738 B/s
browser_http_req_duration.....: avg=355.28ms min=124.04ms med=314.4ms max=1.45s
browser_http_req_failed.......: 0.00%  0 out of 18

WEB_VITALS
browser_web_vital_cls.........: avg=0        min=0        med=0       max=0
browser_web_vital_fcp.........: avg=2.33s    min=2.33s    med=2.33s   max=2.33s
browser_web_vital_fid.........: avg=300µs    min=300µs    med=300µs   max=300µs
browser_web_vital_inp.........: avg=56ms     min=56ms     med=56ms    max=56ms
browser_web_vital_lcp.........: avg=2.33s    min=2.33s    med=2.33s   max=2.33s
browser_web_vital_ttfb........: avg=1.45s    min=1.45s    med=1.45s   max=1.45s

Playwright-compatible APIs

The browser module uses Playwright-inspired APIs:
import { browser } from 'k6/browser';

export default async function () {
  const page = await browser.newPage();

  // Navigation
  await page.goto('https://example.com');

  // Locators
  const button = page.locator('button#submit');
  await button.click();

  // Form interactions
  await page.locator('input[name="email"]').fill('[email protected]');
  await page.locator('input[name="password"]').fill('password123');

  // Screenshots
  await page.screenshot({ path: 'screenshot.png' });

  // Wait for elements
  await page.waitForSelector('.results');

  // Extract data
  const text = await page.locator('h1').textContent();
  console.log('Header:', text);

  await page.close();
}

Combining browser and protocol tests

Test frontend performance under realistic protocol-level load:
import http from 'k6/http';
import { browser } from 'k6/browser';
import { check } from 'k6';

export const options = {
  scenarios: {
    // Protocol-level load
    api_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
      exec: 'apiTest',
    },
    // Browser test during load
    browser_test: {
      executor: 'constant-vus',
      vus: 2,
      duration: '5m',
      exec: 'browserTest',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

// Protocol-level load test
export function apiTest() {
  const res = http.get('https://test.k6.io/api/data');
  check(res, { 'status is 200': (r) => r.status === 200 });
}

// Browser test
export async function browserTest() {
  const page = await browser.newPage();
  
  try {
    await page.goto('https://test.k6.io/');
    await page.waitForSelector('h1');
    
    const header = await page.locator('h1').textContent();
    check(header, { 'header loaded': (h) => h !== '' });
  } finally {
    await page.close();
  }
}

Browser options

Configure browser behavior:
export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
          headless: true,              // Run in headless mode (default)
          timeout: '60s',              // Default timeout for actions
          slowMo: '500ms',             // Slow down operations (for debugging)
          args: ['--no-sandbox'],      // Additional browser arguments
        },
      },
    },
  },
};

Debugging browser tests

Run in headed mode

See the browser window during test execution:
export const options = {
  scenarios: {
    ui: {
      options: {
        browser: {
          type: 'chromium',
          headless: false,  // Show browser window
        },
      },
    },
  },
};

Take screenshots

Capture page state:
await page.screenshot({ path: 'debug.png' });

Slow down execution

export const options = {
  scenarios: {
    ui: {
      options: {
        browser: {
          type: 'chromium',
          slowMo: '1s',  // Wait 1 second between actions
        },
      },
    },
  },
};

Migrating from Playwright

If you have existing Playwright tests, migration is straightforward: Playwright:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await browser.close();
})();
k6 browser:
import { browser } from 'k6/browser';

export default async function () {
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.close();
}
Key differences:
  • No need to launch/close browser (k6 manages this)
  • Use k6’s export default function instead of IIFE
  • Use import instead of require
  • k6 automatically handles concurrent browser instances

Best practices

Use headless mode

Run tests in headless mode for better performance in CI/CD

Set appropriate timeouts

Configure timeouts based on your application’s performance

Clean up resources

Always close pages in a finally block to prevent resource leaks

Limit browser VUs

Browser tests are resource-intensive; use fewer VUs than protocol tests

Next steps

Explore Extensions

Discover other k6 extensions

xk6-disruptor

Add chaos testing to your suite

Protocol Extensions

Test MQTT, Redis, and SQL

Full Browser API

Complete browser module reference

Build docs developers (and LLMs) love