Skip to main content
Validate extension behavior across browsers with repeatable end-to-end tests. Extension.js projects can use Playwright to test extension flows, UI rendering, and integration behavior in continuous integration (CI) and local environments.

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:
Create a playwright.config.ts and define browser projects and reporting.
For deterministic automation, do not parse terminal text. Use the metadata files that Extension.js generates:
  • dist/extension-js/<browser>/ready.json
  • dist/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 | stopped
  • command: dev | start | preview | build
  • browser
  • distPath
  • manifestPath
  • port
  • pid
  • browserPid (the launched browser process, use it for teardown)
  • runId
  • startedAt
  • compiledAt
  • errors
  • runtime: "attached" once the service worker is connected
  • executorAttachedAt
Use this contract as the source of truth for readiness and failures. The full field reference, including extensionId, profilePath, cdpPort, and the error states, is in ready.json.

Running tests

Canonical Playwright flow (AI-friendly)

  1. Start Extension.js in no-browser mode.
  2. Wait until ready.json reports status: "ready".
  3. Launch Playwright with the extension output from distPath.
  4. Run tests and shut down.
One caveat on step 2: 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

  • dev is for watch-mode iteration (extension dev --no-browser + extension dev --wait).
  • start is for production-style checks (extension start --no-browser + extension start --wait).

Important distinction: run mode vs readiness gate

  • --no-browser is 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:
  1. Process A: extension dev --no-browser
  2. Process B: extension dev --wait --browser=<browser> --output json
  3. Start Playwright only after the wait step exits successfully
Production-oriented variant:
  1. Process A: extension start --no-browser
  2. Process B: extension start --wait --browser=<browser> --output json
  3. 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.ndjson over 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

The playwright template scaffolds a working setup you can copy from:

Next steps