---
title: "k6 Browser Module: When to Add Real Browser Metrics to API Load Tests"
description: "k6 browser module for Core Web Vitals + API latency: Chromium-based scenarios, when to pair with HTTP tests, and official k6 browser docs."
publishDate: "2026-06-25"
draft: false
keyword: "k6 browser module"
intent: "TOFU"
cta: "Explore how Performate simplifies k6 load testing—from imports to results—so your team ships performance confidence faster."
tags: ["k6","load-testing","api-performance","browser","module"]
---

Your API load test shows `p95` under 400ms—yet users complain the checkout page feels slow. The gap is often **client-side work**: JavaScript bundles, layout, and third-party tags that HTTP-only k6 never executes. The [k6 browser module](https://k6.io/docs/using-k6/browser/) drives real Chromium so you can measure Core Web Vitals alongside backend latency—without replacing your HTTP scenarios.

Browser-augmented load testing is not "run everything in Selenium." It is a **targeted layer** for flows where rendering dominates perceived performance, paired with HTTP tests that scale cheaply. In this guide you will learn when browser metrics matter, how to combine browser and API scenarios in one k6 run, and which signals justify the extra CPU cost—not just a Lighthouse run on localhost.

## Why HTTP-only tests miss user-visible latency

Backend SLOs can be green while the experience degrades:

- **Large JS bundles** block interactivity even when `/api/cart` is fast.
- **Client-side routing** triggers waterfall fetches HTTP scripts model as isolated calls—not as a real navigation graph.
- **Third-party scripts** (analytics, payments iframe) add main-thread work invisible to plain `http.get`.
- **Hydration and SSR** shift work between server and browser; API-only tests see only one side.
- **Core Web Vitals gates** (LCP, INP) require browser APIs—[`http_req_duration`](https://k6.io/docs/using-k6/metrics/) cannot substitute.

Think of performance like a restaurant: API tests measure kitchen ticket time; browser tests measure plate-to-table—including garnish and presentation.

### When to pair browser with HTTP scenarios

Use browser k6 for **critical UX paths** (login, checkout shell, dashboard first paint) and HTTP k6 for **high-volume API surfaces** ([performance budget frontend + API](/en/blog/performance-budget-frontend-api)). Align thresholds with [p95 vs p99](/en/blog/p95-vs-p99-latency) on both layers.

## Practical k6 implementation: hybrid browser + HTTP scenarios

Run a **low-VU browser scenario** for Core Web Vitals and a **high-rate HTTP scenario** for API throughput in the same `options.scenarios` block.

**Example script (illustrative—not a production-ready test).** Requires k6 with browser support enabled. Adapt URLs and selectors to your app.

**What this example demonstrates:**

- **Parallel browser and HTTP scenarios:** `browser` executor at 2 VUs for checkout UI; `constant-arrival-rate` at 30 req/s for cart API.
- **Web Vitals-style checks:** `page.goto` with wait and locator checks approximate LCP-critical path timing.
- **Shared base URL via env:** one staging target for both layers—easier correlation in reports.
- **Separate thresholds per layer:** stricter HTTP SLOs; relaxed browser `p95` reflecting Chromium overhead.

```javascript
import http from 'k6/http';
import { browser } from 'k6/browser';
import { check, sleep } from 'k6';

const BASE = __ENV.APP_BASE || 'https://staging.example.com';

export const options = {
  scenarios: {
    checkout_browser: {
      executor: 'constant-vus',
      vus: 2,
      duration: '5m',
      options: { browser: { type: 'chromium' } },
      exec: 'browserCheckout',
      tags: { layer: 'browser', flow: 'checkout' },
    },
    cart_api: {
      executor: 'constant-arrival-rate',
      rate: 30,
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 10,
      maxVUs: 40,
      exec: 'cartApi',
      tags: { layer: 'http', route: 'cart' },
    },
  },
  thresholds: {
    'http_req_duration{layer:http}': ['p(95)<450'],
    'browser_web_vital_lcp{layer:browser}': ['p(95)<2500'],
  },
};

export async function browserCheckout() {
  const page = await browser.newPage();
  try {
    await page.goto(`${BASE}/checkout`, { waitUntil: 'networkidle' });
    await page.locator('[data-testid="pay-button"]').waitFor({ state: 'visible' });
    check(page, {
      'checkout shell visible': () => true,
    });
    sleep(2);
  } finally {
    await page.close();
  }
}

export function cartApi() {
  const res = http.get(`${BASE}/api/v1/cart`, {
    headers: { Authorization: `Bearer ${__ENV.TOKEN}` },
    tags: { layer: 'http', route: 'cart' },
  });
  check(res, { 'cart 2xx': (r) => r.status >= 200 && r.status < 300 });
}
```

**Patterns that work**

- **Keep browser VUs low** (1–5)—Chromium is CPU-heavy; scale APIs with HTTP ([k6 scenario types](/en/blog/k6-scenario-types-explained)).
- **Stable selectors:** prefer `data-testid` over brittle CSS—same discipline as E2E tests.
- **Run browser smokes in CI** on schedule, not every PR—cost and flake management.
- **Correlate tags `layer:browser|http`** in Grafana or Performate reports ([k6 observability tags](/en/blog/k6-observability-metrics-tags)).

**Anti-patterns to avoid**

- Replacing all HTTP tests with browser—cost explodes, signal drowns.
- Maxing browser VUs to "simulate load"—use HTTP arrival rate for that question.
- Ignoring auth in browser flows—mirror cookie or token setup from real users.

**Pro tip (example command):**

```bash
K6_BROWSER_ENABLED=true k6 run hybrid-checkout.js --summary-trend-stats="p(95),p(99)"
```

**What this command demonstrates:** enables Chromium-backed scenarios locally or in CI agents that support browser binaries.

## Decision framework: HTTP only vs hybrid vs browser-only

| Situation | Recommended action |
|:---|:---|
| Internal JSON API, no UI gate | HTTP k6 only |
| SPA checkout with vitals SLO | Hybrid: low-VU browser + HTTP API load |
| Marketing landing LCP regression | Browser-only smoke; 1–3 VUs |
| Mobile WebView shell + APIs | Hybrid; tag `client:webview` on HTTP if applicable |
| CI on every commit | HTTP smoke; weekly browser job |

**Use HTTP only if** releases gate on API latency without client vitals commitments.

**Use hybrid if** product tracks LCP/INP and API SLOs together—most ecommerce and SaaS dashboards.

**Use browser-only if** the risk is entirely front-end (static site, landing) with minimal API surface.

## Observability, documentation, and next steps

Hybrid tests only help if teams know which layer failed. Before scaling:

- [ ] Document browser VU cap, agent CPU requirements, and Chromium install path in runbook.
- [ ] Set alerts on `browser_web_vital_*` thresholds separate from HTTP—different owners may apply.
- [ ] Correlate `layer:*` tags with RUM (Real User Monitoring) for the same routes.
- [ ] Automate HTTP smoke every merge; schedule browser smokes ([CI/CD load testing](/en/blog/load-testing-in-ci-cd)).
- [ ] Archive scenario JSON and selector list when UI refactors—update browser script in same PR.

## How Performate simplifies hybrid browser + API testing

Jumping between Lighthouse, HTTP k6, and spreadsheets splits ownership. Below is a **concrete workflow example** for checkout hybrid testing.

**Example: one workspace for cart API load and checkout vitals**

1. **Import Postman cart API requests** and keep browser checkout as a documented URL list with selectors in scenario notes. *Problem solved:* API truth stays in collection; browser path is explicit.
2. **Create HTTP scenario `cart_api`** at 30 req/s with bearer token from env. *Problem solved:* scalable load without Chromium cost.
3. **Add browser scenario `checkout_shell`** at 2 VUs with target URL and threshold notes for LCP. *Problem solved:* vitals path visible to QA without separate tool.
4. **Tag scenarios `layer:http` and `layer:browser`** for filtered reports. *Problem solved:* same tag model as the k6 example above.
5. **Run hybrid suite before release** and review integrated report with product/design. *Problem solved:* one export for backend and frontend perf sign-off.
6. **Export k6 script** for CI HTTP smoke; keep browser job on nightly agents with `K6_BROWSER_ENABLED=true`. *Problem solved:* fast PR gates plus vitals coverage.

That workflow maps to the `cta`: imports through results in one desktop flow.

## Closing takeaway

Browser k6 is a **precision instrument**, not a replacement for HTTP load tests. Pair low-VU Chromium scenarios on critical UX paths with high-rate API scenarios, tag layers separately, and gate releases on both when Core Web Vitals are part of the contract.

Run this week's hybrid smoke on checkout—note whether API `p95` or LCP moved first when users reported slowness.

[Try Performate free](https://performate.app) | [Book a demo](/demo) | [k6 browser module](https://k6.io/docs/using-k6/browser/)
