Browser performance do / don't
Browser performance testing gives you data that protocol load cannot: real page-load times, Core Web Vitals, and what users see. It is also expensive. Browser VUs cost 10–100× more than protocol VUs, and teams tend to make the same mistakes that waste money or produce misleading results. This page collects the guidance that matters most.
Do / don’t
Section titled “Do / don’t”Do: use real browsers for UX truth, not load generation.
A browser VU exists to measure what a user experiences. 5 browser VUs running for 15 minutes give you dozens of real page-load measurements, which is enough to detect degradation. Scale comes from protocol VUs.
Don’t: try to generate backend load with browser VUs.
Running 200 Chrome instances to stress a server is impractical. Each Chrome process uses 500 MB–1 GB of RAM and 1–2 CPU cores. At 200 VUs, you need ~200 GB of RAM and 400 CPU cores for the browsers alone, before the server receives a single request. Use JMeter, k6, or Taurus HTTP to generate load at scale. Use 3–10 browser VUs to sample the UX.
Web vitals
Section titled “Web vitals”Do: measure Core Web Vitals under realistic backend load.
An LCP measured at idle tells you the theoretical best case. An LCP measured while your API handles 500 concurrent requests tells you what users get at peak. Run browser checks during the steady-state phase of the protocol load, not before it starts or after it ends.
Don’t: treat an idle web-vitals check as a production proxy.
If your backend response time is 50 ms at idle and 800 ms at peak load, TTFB moves from good to “needs improvement” and LCP from good to poor. A web-vitals gate that runs only against an idle staging environment can miss this completely.
Tooling clarity
Section titled “Tooling clarity”Do: use Playwright for web-vitals scripting and MaxoPerf for scale load.
Playwright is the best tool for writing browser journeys and measuring Core Web Vitals. Use it to write clear, maintainable scripts that capture TTFB, LCP, CLS, and INP. Use MaxoPerf to run the concurrent protocol load that stresses the backend while your Playwright script measures the UX.
Don’t: use hundreds of Playwright browsers where protocol load would do.
MaxoPerf runs Playwright scripts as browser VUs with the playwright executor, alongside selenium and wdio. See Playwright tests. Each browser VU is a real browser and costs far more than a protocol VU. Keep browser VUs for UX measurement, and generate backend load with protocol VUs.
Concurrency
Section titled “Concurrency”Do: keep browser VU counts in the 2–10 range for measurement runs.
- 2–3 VUs: pre-release gate, login regression, core page check.
- 5 VUs: checkout journey, marketing-launch smoke.
- 10 VUs: the practical maximum for most runs. Above this, the overhead of managing browsers starts to add noise.
Don’t: scale browser VUs beyond 20 expecting proportionally better data.
Above 20 VUs, browser management overhead, runner resource contention and Chrome processes interfering with each other start to dominate the measurements. You get more noise, not more signal. To check UX at high concurrent user counts, use the hybrid pattern: many protocol VUs stress the backend, and a few browser VUs measure the result.
Assertions and gates
Section titled “Assertions and gates”Do: assert on web vitals thresholds in your browser script.
MaxoPerf counts an assertion failure as an error. That turns a number you read by hand into a hard gate: if loadComplete > 3000, the run fails, the CI step fails, and the deploy is blocked. Write assertions for every metric you care about.
assert timing['ttfb'] < 800, f"TTFB {timing['ttfb']:.0f} ms > 800 ms SLO"assert timing['loadComplete'] < 3000, f"Load {timing['loadComplete']:.0f} ms > 3 s SLO"Don’t: rely on manual result review for web vitals gates.
Manual review does not scale, and people make mistakes. A failed run that nobody checked until the day after a launch is worse than no test at all. Automate the gate: trigger the run through the API, poll for finished, and check errorRate === 0.
Credentials and test data
Section titled “Credentials and test data”Do: inject credentials via MaxoPerf secrets and environment variables.
Every login flow needs credentials. Store them as MaxoPerf secrets (account- or workspace-scoped) and reference them through environment variables in the Taurus YAML. The secret value is never written to the script file.
env: TEST_EMAIL: ${TEST_EMAIL} TEST_PASSWORD: ${TEST_PASSWORD}Don’t: hard-code credentials in Python scripts or Taurus YAML.
Committed credentials are a security incident. Even in a private repo, test assets uploaded to MaxoPerf may be visible to workspace members, and log output may print the value. Always use secrets.
Comparing metrics
Section titled “Comparing metrics”Do: compare browser timings against browser baselines and protocol timings against protocol baselines.
Browser and protocol tests measure different things. A browser loadComplete of 2.5 s and a k6 p95 of 120 ms are both valid, and you cannot compare them. Keep a separate baseline for each type and compare like with like.
Don’t: compare browser test response times against protocol test latency numbers.
“Our k6 p95 is 120 ms but the browser test shows 2500 ms, so that’s a regression!” is a false alarm. 120 ms is the server-side HTTP response time. 2500 ms is the full browser load, including rendering. The gap is expected and does not point to a problem.
Headless mode
Section titled “Headless mode”Do: always run headless browsers on MaxoPerf runners.
MaxoPerf runner environments have no display server, so headed Chrome fails. Set headless: true in your Taurus YAML, or make sure your Python script does not override it. For Taurus selenium, headless is the default; confirm it is not turned off.
Don’t: develop scripts in headed mode and forget to switch to headless for MaxoPerf.
A script that works in headed mode locally may fail headless if it depends on browser window size, UI visibility, or a display the runner does not have. Test headless on your machine before uploading: pytest tests/test_checkout.py --headless or DISPLAY=:99 python scripts/checkout.py with Xvfb.
Quick-reference table
Section titled “Quick-reference table”| Do | Don’t |
|---|---|
| Use browser VUs for UX measurement (2–10 VUs) | Scale browser VUs for load generation |
| Measure web vitals during protocol load steady state | Run web-vitals checks only at idle |
| Use Playwright for web-vitals scripting; MaxoPerf for scale | Try to run Playwright as a MaxoPerf executor |
| Assert on TTFB / LCP thresholds in the browser script | Review web vitals results manually post-run |
| Inject credentials via MaxoPerf secrets + env vars | Hard-code credentials in scripts or YAML |
| Compare browser timings to browser baselines | Compare browser load times against k6 latency numbers |
Run headless: true on all MaxoPerf browser tests | Rely on headed-mode scripts without headless testing |
| Use the hybrid pattern for combined scale + UX checks | Skip browser VUs during peak-load simulation |
Where to go next
Section titled “Where to go next”- Hybrid load architecture: the pattern for combining protocol and browser VUs.
- Daily browser performance scenarios: concrete scenario guides for the most common use cases.
- Selenium performance testing: browser VU configuration and Python script patterns.
- Playwright performance testing: web-vitals scripting and how Playwright pairs with MaxoPerf.
- Frontend web vitals under load: measuring LCP/CLS/INP while the backend is under stress.