Browser performance do / don't
Browser performance testing gives you data that protocol load cannot — real page-load times, Core Web Vitals, and UX truth. It also has a narrow cost envelope: browser VUs are 10–100× more expensive than protocol VUs, and the mistakes that waste money or produce misleading results are consistent across teams. This page collects the most important guidance.
Do / don’t
Section titled “Do / don’t”Do: use real browsers for UX truth, not load generation.
The purpose of a browser VU is to measure what a user experiences. 5 browser VUs running continuously for 15 minutes give you dozens of real page-load measurements — that 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 just for the browsers — 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 is handling 500 concurrent requests tells you what users actually experience at peak. Run browser checks during the protocol load steady-state phase — 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 shifts 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 entirely.
Tooling clarity
Section titled “Tooling clarity”Do: use Playwright for web-vitals scripting and MaxoPerf for scale load.
Playwright is the best authoring tool for browser journeys and Core Web Vitals measurement. Use it to write clear, maintainable scripts and capture TTFB, LCP, CLS, and INP. Use MaxoPerf to run the concurrent protocol load that stresses the backend while your Playwright script measures UX.
Don’t: try to run Playwright at scale as a load generator.
Playwright is not a load-testing executor in Taurus. For browser VUs inside MaxoPerf, use the selenium executor (Python WebDriver scripts) or the wdio executor (WebdriverIO JavaScript scripts). Playwright is the scripting and measurement layer; the Taurus executors are the in-platform VU runners.
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: maximum practical for most runs; beyond this, the overhead of browser management starts to introduce noise.
Don’t: scale browser VUs beyond 20 expecting proportionally better data.
Beyond 20 VUs, browser management overhead, runner resource contention, and Chrome process interference start to dominate the measurements. You get more noise, not more signal. If you need to verify UX at high concurrent user counts, use the hybrid pattern: many protocol VUs stress the backend; a small number of browser VUs measure the result.
Assertions and gates
Section titled “Assertions and gates”Do: assert on web vitals thresholds in your browser script.
An assertion failure counts as an error in MaxoPerf results. This turns a “number you read manually” 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 introduces human error. A failed run that nobody checked until the day after a launch is worse than no test at all. Automate the gate — trigger via the API, poll for finished, 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 via 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 if the repo is private, test assets uploaded to MaxoPerf may be visible to workspace members, and log output may surface the value. Always use the secrets mechanism.
Comparing metrics
Section titled “Comparing metrics”Do: compare browser timings against browser baselines and protocol timings against protocol baselines.
Browser and protocol tests measure fundamentally different things. A browser loadComplete of 2.5 s and a k6 p95 of 120 ms are both valid — they cannot be compared. Establish separate baselines 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 — 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 indicate a problem.
Headless mode
Section titled “Headless mode”Do: always run headless browsers on MaxoPerf runners.
MaxoPerf runner environments do not have a display server. Headed Chrome will fail. Set headless: true in your Taurus YAML or ensure your Python script does not override it. For Taurus selenium, headless is the default; confirm it is not disabled.
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 headlessly if it relies on browser window size, UI visibility, or a display that is not present on the runner. Test headlessly locally 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 stressed.