Skip to content

Browser tests

A Browser test drives a real browser through your app instead of sending raw HTTP requests. You get page timings, Web Vitals, screenshots, and full session capture for every browser instance.

Build a browser test.

From Tests → New test, select the Browser test card, then choose how to author it:

  1. Upload script: bring a scenario for any browser-capable executor. The two first-class choices are a Selenium .py script (or Selenium IDE .side project) and a Playwright script. MaxoPerf also supports WebdriverIO specs.

  2. Scriptless builder: build the browser steps in the console (navigate, click, type, wait, assert, and more) with no code, including a page-contains assertion that checks the visible text of the page (contains or regex, optionally negated). See Scriptless test builders.

  3. AI steps: write the journey as numbered sentences and let a real browser turn them into steps. See AI steps.

The scriptless path for a browser test. You drag steps from the palette into transactions, and MaxoPerf renders the browser scenario from them.

Capture is automatic. You don’t edit your script, add logging, or configure a proxy. For every browser instance in the run, MaxoPerf records:

  • Video: a clean recording of the browser (no “controlled by automated test software” infobar).
  • Steps: the ordered actions the test took (navigate, find element, click, type, …), timestamped and synced to the video.
  • HAR: the full network waterfall (requests, responses, status, size, timing).
  • Console: the browser console output (logs, warnings, errors).
CapabilityChrome / ChromiumFirefox
Video✅✅
Steps✅✅
Network HAR✅Roadmap (WebDriver BiDi)
Console logs✅Roadmap (WebDriver BiDi)

Record browser tests: video, steps, HAR & console shows how to read the captured session on a run.

You can run an existing Playwright script or project in MaxoPerf with little or no editing. This section explains which file runs, what happens to your playwright.config, how browsers and load are assigned, and how to read a failed run.

Upload your script as the test’s entrypoint, either on its own or through a Taurus YAML:

  • Bare script. The entrypoint you upload is the file that runs, whatever its name. checkout-load.js doesn’t need a .spec or .test suffix, because MaxoPerf pins Playwright’s testMatch to that exact file.
  • Taurus YAML. Set executor: playwright. MaxoPerf runs the script of the first scenario. The script must be a .js, .ts, .mjs, .cjs, .mts or .cts file that you uploaded with the test.
execution:
- executor: playwright
concurrency: 10
hold-for: 10m
scenario: checkout
scenarios:
checkout:
script: checkout-load.js # upload this file with the test

Both shapes run the same way:

  • Specs with test() blocks run as they are. This includes specs that import test from your own fixtures file (import { test } from './fixtures'), as long as you upload that file too.
  • Library scripts with no test() blocks call chromium.launch(), browser.newContext() and so on directly. MaxoPerf wraps them in a generated test, so each iteration runs the whole script once.

Validation reads your script as text and never executes it. It fails the test before any run if:

  • the script has no test() blocks and no library calls (playwright_no_runnable_tests)
  • the YAML’s script is missing or isn’t JavaScript or TypeScript (playwright_script_not_found, playwright_script_not_supported)
  • the entrypoint is a playwright.config.* file (playwright_config_as_entrypoint). Upload the config as an asset instead.

If you upload a playwright.config.ts, .js, .mjs, .cjs, .mts or .cts with the test, MaxoPerf wraps it rather than replacing it. The file is renamed to maxoperf-user-playwright.config.<ext>, and a generated playwright.config.ts imports it and merges its settings. A CommonJS .js config (module.exports = …) is renamed to .cjs when the bundle is an ES module. That happens when you upload a library script, or when your own package.json sets "type": "module".

SettingWhat happens
Top-level settings (timeout, expect, retries, use, and the rest)Kept.
Your reportersKept, and run beside MaxoPerf’s. Exceptions are html and json, which are dropped.
testDir, testMatch, testIgnoreOverridden, at the top level and on every project, so your entrypoint always runs.
projectsReplaced by one project per browser selected on the test. Each project’s use starts from the Playwright device defaults. Then your top-level use is applied, then the use of your project that maps to that browser, and finally MaxoPerf’s browser channel and launch arguments.
Other projects (for example setup, webkit) and dependenciesNot run.

A Playwright test can run on Chrome, Edge and Firefox, or on any combination of them. When you upload a config with a literal projects array, MaxoPerf reads the projects statically and sets the test’s browsers from them. The browser picker then shows the selection as synced from playwright.config. If you change the browsers later in the console or through the API, your choice wins. It stays in place until you upload a config whose detected browsers differ from the last synced set.

Each project is mapped by the first of these attributes it sets:

Attribute (checked in this order)ChromeEdgeFirefox
use.channelchromemsedge—
Device (devices['…'])Desktop ChromeDesktop EdgeDesktop Firefox
use.browserNamechromium—firefox
Project namechromium, chromeedge, msedgefirefox

Any other project is skipped, and validation adds a playwright_project_unsupported warning that names it. That covers WebKit, mobile devices, other channels such as chrome-beta, and unknown names. The warning doesn’t block the test. WebKit and mobile Safari aren’t supported.

How each browser runs:

  • Chrome and Edge run on the real Google Chrome and Microsoft Edge. A script that calls chromium.launch(), chromium.launchPersistentContext() or chromium.launchServer() without a channel runs on Chrome, or on Edge in an Edge test. Playwright’s own bundled Chromium isn’t installed on runners.
  • Firefox runs on Playwright’s Firefox build.
  • Playwright itself is pre-installed on the runner at a version MaxoPerf pins, so runs don’t spend time installing browsers. Your package.json can’t change that version. Its dependencies and devDependencies are replaced by that pinned Playwright, and other npm packages aren’t installed. Import only Playwright and files you upload with the test.

When a test has more than one browser, its virtual users are split evenly across the browsers. They are not multiplied by the number of browsers. Any remainder goes to the first browsers in the test’s order:

Virtual usersBrowsersVirtual users per browser
10Chrome, Firefox5 Chrome, 5 Firefox
7Chrome, Edge, Firefox3 Chrome, 2 Edge, 2 Firefox

Each browser gets its own runners, sized from its share. The run’s total virtual users and VU-hours are the same as a single-browser run with the same total.

  • Each browser needs at least one virtual user. A test with more browsers than virtual users is rejected with BROWSERS_EXCEED_VUS.
  • You can’t add runners to a multi-browser run while it’s running. The request is rejected with ADD_RUNNERS_MULTI_BROWSER_UNSUPPORTED.

On managed cloud locations, a browser test’s runner size comes from how many browsers each runner hosts. Each virtual user is one browser, so this is the virtual users per runner.

Browsers per runnerRunner size
1–3s — 2 vCPU, 8 GiB
4–6m — 4 vCPU, 16 GiB
7 or morel — 8 vCPU, 32 GiB

This applies to Playwright, Selenium, Selenium IDE and WebdriverIO tests, and to Browser fleets, which size by browsers per runner. A larger size that was already configured is never reduced. On a private location, the runner uses your own node’s resources, so this sizing doesn’t apply.

A failed browser run names a cause and shows the engine’s own error text under it, such as Error: No tests found or page.goto: Timeout 60000ms exceeded. That text is up to 1,024 characters of the first meaningful error lines, with your secret values redacted. It appears on runs started after this feature shipped. Older failed runs keep their original message.

A browser run in which no iteration passed ends as Failed with one of the causes below, even when the test engine itself exited without an error (a Selenium script does when every iteration fails). If at least one iteration passed, the run keeps the engine’s result. On a run with several runners, the cause names the runner whose iterations all failed.

CauseWhat it meansWhat to do
Playwright found no testsPlaywright collected 0 tests.Check that the script has test() blocks, and that no grep in your config filters them out.
Script failed to loadA syntax or runtime error happened while the script was being imported.The error text names the failing line.
Missing script dependencyThe script imports a module that isn’t on the runner.Upload the file with the test, or remove the import. Third-party npm packages aren’t installed.
Browser failed to startThe browser couldn’t be launched.Remove any custom executablePath, and check the launch arguments in your config.
Page navigation timed outAt least 80 % of the failures were navigation timeouts, and no attempt passed.Often caused by networkidle; see below.
Target unreachableAt least 80 % of the failures were DNS lookup failures or refused connections, and no attempt passed.Check the URL, and that the target is reachable from the internet. For internal targets, use a private location.
Script never passedEvery attempt failed, for mixed reasons. This includes a Playwright run cut by its duration whose finished tests all failed or timed out.Read the most common error in the summary and the error text.
Test produced no resultsThe engine ran but reported no samples, or stopped reporting them.Check the run logs and runner CPU.
Invalid test configurationTaurus rejected the test configuration.The error text names the setting.
Runner overloadedA browser runner on a managed location stopped responding. In the 60 seconds before it went silent, its memory reached 85 % or its 1-minute load average reached twice its vCPU count.Lower the browsers per runner, or choose a larger runner size.

A Playwright run that reaches its duration limit while a test is still running has no finished test to count, so it can still end as Passed. Give the run a duration longer than your test timeout.

A runner that stops responding with no sign of overload is still reported as Runner unresponsive. For other failures, see Run stuck or failed.

page.goto(url, { waitUntil: 'networkidle' }) and page.waitForLoadState('networkidle') wait until the page has had no network connections for at least 500 ms. Pages with analytics, bot protection, payment widgets or live chat keep sending requests, so the wait often never finishes and every iteration ends in a timeout. Validation warns about each occurrence (playwright_networkidle, with file and line) but doesn’t block the test.

Wait for load, then for the element you actually need:

await page.goto('https://shop.example.com/checkout', { waitUntil: 'load' });
await page.getByRole('button', { name: 'Place order' }).waitFor();

You may already have a Selenium or Playwright suite and want to point it straight at browsers MaxoPerf hosts, outside the test/run model, with your own test runner and CI. Use Browser fleets for that. A fleet gives you one central endpoint that speaks WebDriver and Playwright connect(), with the same video/steps/HAR/console capture.