How to Use k6 for Browser Testing
Set up k6 browser testing, run and assert a real user journey, read browser metrics, and choose when to combine browser checks with protocol load.
Use k6 browser testing when you need to verify what a user sees and does in a real Chromium browser, or measure browser-visible performance such as Web Vitals. Install k6 and a Chromium-based browser, define a browser scenario, then navigate, interact with locators, check an expected result, and close the page. For most high-volume load generation, use protocol-level requests; add a smaller browser workload when user experience under load matters.
1. What k6 browser testing measures
The k6 browser module combines browser automation with frontend performance metrics in the k6 workflow. It can answer questions such as whether a page rendered, whether an element became interactive, whether a loading indicator persisted, and what browser metrics the journey produced. It complements protocol-level load testing; it does not make browser instances the default choice for generating all traffic.
| Test type | Best for | Typical approach |
|---|---|---|
| Browser-level | User-visible flows, client-side behavior, browser metrics | Navigate and interact through browser APIs |
| Protocol-level | Generating substantial request load and measuring backend endpoints | Send HTTP requests directly |
| Hybrid | Sampling a user journey while backend traffic is generated | Combine protocol traffic with a smaller browser workload |
A full browser has more overhead than a direct request. A useful division is to generate most load at the protocol level and run enough browser VUs to sample the user experience that matters.
2. Install and prepare
- Install k6 using the official k6 installation instructions.
- Install or make available a Chromium-based browser. Grafana’s first-test example uses Chrome.
- Use a code editor and basic JavaScript or TypeScript familiarity. k6 has its own runtime; it is not Node.js, and compatibility with npm modules can vary.
- Choose a test environment and a journey that is safe to repeat. Avoid running load against a production system unless its owners have approved the test and its limits.
You can scaffold a browser test from the template, then run it locally:
k6 new --template browser browser-script.js
k6 run browser-script.js
See Grafana’s browser testing documentation for version-specific setup details.
3. Write a minimal browser test
This example opens a page, checks that its heading is present, and closes the page even if navigation or the assertion path fails. Replace the URL and selector with elements in your own test environment. The threshold is an example pass condition, not a universal performance target.
import { browser } from 'k6/browser';
import { check } from 'k6';
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: { browser: { type: 'chromium' } },
},
},
thresholds: {
checks: ['rate==1.0'],
},
};
export default async function () {
const page = await browser.newPage();
try {
await page.goto('https://your-test-environment.example');
const heading = await page.locator('h1').textContent();
check(heading, {
'expected page is shown': (value) => value !== null && value.trim() !== '',
});
} finally {
await page.close();
}
}
Browser operations are asynchronous, so use async and await. The browser API became asynchronous starting with k6 v0.52.0. A page should be closed in a cleanup path: this frees resources and supports accurate Web Vital calculation.
4. Test a meaningful user journey
A page-load check is a useful starting point, but a browser test should verify the outcome a user cares about. For example, submit a search and assert that results appear, or add an item and verify a confirmation. Use locators to find and interact with elements; they are preferable for dynamic pages because they can handle changes such as frame navigation and SPA content updates.
import { browser } from 'k6/browser';
import { check } from 'k6';
export const options = {
scenarios: {
search_journey: {
executor: 'shared-iterations',
options: { browser: { type: 'chromium' } },
},
},
};
export default async function () {
const page = await browser.newPage();
try {
await page.goto('https://your-test-environment.example');
const search = page.locator('input[name="q"]');
await search.fill('k6 browser testing');
await page.locator('button[type="submit"]').click();
const results = page.locator('[data-testid="search-results"]');
await results.waitFor({ state: 'visible' });
check(await results.isVisible(), {
'search results are visible': (visible) => visible,
});
} finally {
await page.close();
}
}
Use selectors that are stable in your application, such as test IDs or semantic labels where supported. Replace the example selectors with ones that match your page and the API supported by your installed k6 version. Wait for a meaningful state change, such as a results container becoming visible, rather than adding an arbitrary fixed delay.
5. Configure scenarios and thresholds
A browser scenario requires an executor and options.browser.type: 'chromium'. The example uses shared-iterations, which assigns a fixed number of iterations across the scenario. Choose the executor and load profile to fit the test question; consult the scenario documentation for the available executors and their behavior.
Thresholds turn measurements into pass or fail criteria. For example, checks: ['rate==1.0'] requires all checks to pass. Set thresholds based on your service objectives and a representative test environment. Documentation output and sample metrics are not expected targets for your application.
k6 browser output can include browser request metrics and Web Vitals such as FCP, LCP, CLS, INP, and TTFB. Use the metrics relevant to the question you are investigating. Browser measurements depend on the test machine, browser, network, application state, and load profile; compare like with like and avoid treating a single run as a universal baseline.
6. Run locally or in Grafana Cloud
Local run
k6 run browser-script.js
Local runs are useful for developing and debugging a journey. Keep the machine and browser environment consistent when comparing runs, and be aware that local resource contention can affect results.
Grafana Cloud k6
Grafana Cloud k6 supports browser test execution through its interface or CLI and provides a results view with browser-test information, including the 75th percentile of Web Vitals over time. Cloud configuration can include a load zone, test name, and project settings; follow the current Grafana Cloud k6 documentation for the applicable setup.
Grafana documents that browser VUs consume 10 times more VU hours than protocol VUs in Grafana Cloud k6. This is specific to its cloud service; it is not a statement about local execution or other providers. Also, environment-variable browser customization is unsupported for browser tests running in Grafana Cloud k6.
7. Use browser checks alongside load generation
- Use protocol-level requests to generate most of the traffic when the goal is backend capacity or endpoint behavior.
- Add a smaller browser workload to observe whether representative user flows still work and how frontend metrics behave under that traffic.
- Keep browser checks focused on meaningful journeys; every browser VU carries more execution overhead than a protocol VU.
- Record the test conditions and choose thresholds from your own objectives, then compare runs under consistent conditions.
This hybrid approach can reveal user-facing problems that endpoint checks alone miss, while avoiding the assumption that a large number of full browser instances is always the most efficient load generator.
8. Reliability, waits, and environment details
- Close every page. Put
page.close()infinallyso failures do not skip cleanup. - Wait for state, not time. Prefer a locator or event-based wait that expresses readiness over arbitrary sleeps. The recommended-practices guide covers waits, user-input delay, and time-series cardinality.
- Handle consent banners. A cookie banner can cover a button or block interaction. Make the test environment’s consent state explicit or handle the banner as part of the journey.
- Expect dynamic content. Prefer locators and meaningful state checks for SPAs, dynamic elements, and frame navigation rather than assuming an element is immediately ready.
- Interpret device presets carefully. Device presets can approximate mobile browser behavior, but they are emulation rather than measurements from a physical device.
- Use Docker security guidance. Grafana’s
master-with-browserimage documentation warns that Chrome is launched withno-sandbox. Use it only with trustworthy websites, and consult the documented hardened alternative before using it in a less trusted environment.
See Grafana’s browser recommended practices and browser options for current operational guidance.
9. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser scenario or browser launch fails | Chromium is unavailable, the installation is incomplete, or the runtime environment does not match the setup | Check the k6 and Chromium setup instructions, confirm the browser is available in the execution environment, and review version-specific browser requirements. |
| Browser operation errors or returns unexpectedly | An asynchronous operation was not awaited, or an older script uses a pre-v0.52.0 synchronous pattern | Use async/await consistently and follow the asynchronous browser API documentation. |
| Locator cannot find an element | The selector is wrong, the page has not reached the expected state, content is dynamic, or a banner obscures the flow | Check the selector against the current page, wait for a meaningful state, and handle consent or other overlays explicitly. |
| Flow passes sometimes and fails sometimes | Timing assumptions, dynamic content, or unstable test data make the journey nondeterministic | Wait for the actual result state, use stable selectors, and reset or isolate data between iterations where the application requires it. |
| Checks fail but the page appears to load | The assertion is testing the wrong element or checks are being confused with performance thresholds | Verify the locator and expected value, then set the check-rate threshold intentionally. rate==1.0 is only suitable when every iteration must pass. |
| Browser measurements vary across runs | Machine contention, network variance, environment differences, or changing application state | Keep conditions consistent, inspect the relevant metrics over multiple runs, and avoid interpreting documentation sample values as a target. |
| Cloud browser usage is higher than expected | Browser VUs consume more Grafana Cloud k6 VU hours than protocol VUs | Use protocol traffic for most load and reserve browser VUs for representative user-experience sampling; check current cloud usage documentation. |
| Environment-variable browser customization has no effect in Cloud | That customization is unsupported for browser tests running in Grafana Cloud k6 | Use supported cloud configuration options documented for the browser test instead. |
10. Or skip the browser setup
If your task is to capture a page image rather than exercise a user journey or generate load, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, with options documented in the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture; known consent platforms, newsletter popups, and chat widgets are removed, and each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
11. FAQ
Can k6 browser tests replace protocol tests?
They answer different questions. Browser tests cover user-visible flows and browser metrics; protocol tests are generally the more suitable way to generate most request load.
Do I need Node.js to run a k6 browser script?
No. k6 runs scripts in its own runtime, and compatibility with npm modules can vary.
Does a device preset measure a real phone?
No. Device presets emulate aspects of browser behavior; they do not replace measurement on physical hardware.
What should my Web Vitals threshold be?
Set it from your own service objectives and representative environment. Documentation examples are not universal targets.


