Playwright testing capabilities
Why use it
- Catch runtime regressions that unit tests miss.
- Validate extension behavior on real browser engines.
- Verify multi-browser changes before release.
Typical setup
Install Playwright test dependencies in your project:playwright.config.ts and define browser projects and reporting.
Recommended baseline
Automation contract (recommended)
For deterministic automation, do not parse terminal text. Use the metadata files that Extension.js generates:dist/extension-js/<browser>/ready.jsondist/extension-js/<browser>/events.ndjson(newline-delimited JSON for watch/rebuild events)
ready.json (schema v2) includes stable fields intended for scripts/agents:
status:starting|ready|error|stoppedcommand:dev|start|preview|buildbrowserdistPathmanifestPathportpidbrowserPid(the launched browser process, use it for teardown)runIdstartedAtcompiledAterrorsruntime:"attached"once the service worker is connectedexecutorAttachedAt
extensionId, profilePath, cdpPort, and the error states, is in ready.json.
Running tests
Canonical Playwright flow (AI-friendly)
- Start Extension.js in no-browser mode.
- Wait until
ready.jsonreportsstatus: "ready". - Launch Playwright with the extension output from
distPath. - Run tests and shut down.
status: "ready" means compiled. If your test drives the extension through the act commands (eval, storage, reload, open), also wait until the contract carries runtime: "attached". Playwright-launched browsers load distPath themselves, so plain UI tests need only ready.
Development mode vs test mode
devis for watch-mode iteration (extension dev --no-browser+extension dev --wait).startis for production-style checks (extension start --no-browser+extension start --wait).
Important distinction: run mode vs readiness gate
--no-browseris the run mode: it starts the extension pipeline without launching a browser.- A readiness gate (
extension dev --wait --browser=<browser>) is the synchronization step that tells Playwright when the extension is ready.
--no-browser produces build output. The wait step confirms readiness before tests proceed. If your environment cannot run a second CLI process, poll ready.json directly.
Use this two-process pattern:
- Process A:
extension dev --no-browser - Process B:
extension dev --wait --browser=<browser> --output json - Start Playwright only after the wait step exits successfully
- Process A:
extension start --no-browser - Process B:
extension start --wait --browser=<browser> --output json - Start Playwright only after the wait step exits successfully
--output json prints one envelope on stdout, human copy moves to stderr. The older --wait-format alias still works but warns on stderr.
headless: false is required, not a preference. Playwright’s default headless
mode runs Chromium’s headless_shell binary, which loads no extensions at all.
A suite that flips it to true for CI still passes, because it silently tests a
browser with your extension missing. To run without a visible window, keep
headless: false and add --headless=new to args, which uses the full
Chromium headless mode that does support extensions.
Practical guidance for extensions
- Keep test fixtures deterministic; extension startup can be sensitive to profile state.
- Prefer explicit waits on extension UI conditions over fixed timeouts.
- Run Chromium and Firefox projects in CI for cross-engine confidence.
- Capture traces/screenshots/videos on failure for faster debugging.
- Prefer
ready.json/events.ndjsonover stdout parsing for machine reliability.
Common pitfalls
- Relying on fixed timeouts instead of state-based waits
- Running only one browser target in CI
- Skipping artifact upload for failed runs
- Coupling tests to local-only profile or environment assumptions
Repository reference
Theplaywright template scaffolds a working setup you can copy from:
playwright.config.tsand ane2e/folder, in extension-js/examples/playwright
Next steps
- Set up CI templates.
- Keep command workflows aligned with dev and build.

