Playwright for Chrome Extension Testing

Automate extension popups, options pages, and background scripts using Playwright. Validate behavior on real Chrome with BrowserStack.

Get Started free
Chrome extension tests using Playwright
Home Guide How to Automate Tests for a Chrome Extension using Playwright?

How to Automate Tests for a Chrome Extension using Playwright?

Chrome extensions are tightly coupled with the browser runtime. Unlike web apps, they operate across multiple surfaces such as popups, options pages, background scripts, and content scripts, each with different lifecycles and permissions.

Automating tests for these components is more complex than standard UI automation. Playwright can handle these scenarios effectively when configured correctly, making it a strong choice for Chrome extension testing.

Overview

Common Challenges in Playwright Extension Automation

  • Popup closing before assertions run
  • Service worker restarts in Manifest V3
  • Permission-related failures in CI
  • Dynamic extension IDs
  • Chrome version mismatches

Best Practices for Chrome Extension Testing with Playwright

  • Keep popup tests minimal and focused
  • Validate core logic through options pages and background effects
  • Avoid hardcoded IDs or URLs
  • Isolate permission-based behavior
  • Run tests in environments close to real user setups

What Makes Chrome Extension Testing Different from Web Testing?

Chrome extensions do not behave like traditional web applications loaded over HTTP. They are injected into the browser and rely on Chrome-specific execution models.

Key differences include:

  • Non-HTTP origins using chrome-extension://, which cannot be navigated like standard URLs.
  • Multiple isolated contexts such as popup, options page, and background service worker.
  • Short-lived popup UI that closes when focus is lost.
  • Permission-gated APIs that alter runtime behavior.
  • Startup-only loading, meaning extensions must be injected at browser launch.

Example: attempting to open a popup without loading the extension at startup will silently fail.

Prerequisites for Automating Chrome Extensions with Playwright

Extension automation requires stricter setup than normal Playwright tests.

Required prerequisites include:

  • An unpacked extension build directory, not a .crx file.
  • Chromium-only execution, as Firefox and WebKit do not support Chrome extensions.
  • Headed mode, since extensions are unsupported in headless Chromium.
  • Persistent browser context, so the extension remains loaded.
  • Manifest V2 or V3 awareness, as background behavior differs.

Example: disabling headless mode explicitly:

headless: false

Chrome Extension Architecture Overview (Popup, Options, Background)

Chrome extensions consist of components that must be tested differently.

  • Popup: A temporary UI rendered from popup.html that closes automatically.
  • Options Page: A persistent configuration UI opened in a browser tab.
  • Background Script / Service Worker: Handles logic such as state, messaging, and browser events without UI.
  • Example: a popup toggle sends a message to the background script, which updates storage used by content scripts.

Setting Up Playwright for Chrome Extension Testing

Playwright must launch Chromium with the extension preloaded using a persistent context.

Core setup requirements:

  • Use chromium.launchPersistentContext
  • Disable headless mode
  • Load the extension via Chromium flags
  • Avoid Playwright’s auto-managed browser lifecycle

Example setup:

import { chromium } from ‘@playwright/test’;import path from ‘path’;
const extensionPath = path.resolve(__dirname, ‘../dist’);
const context = await chromium.launchPersistentContext(”, {
headless: false,
args: [
`–disable-extensions-except=${extensionPath}`,
`–load-extension=${extensionPath}`,
],
});

Loading a Chrome Extension in Playwright

Extensions must be loaded at browser startup.

The loading process involves:

  • Building the extension locally
  • Passing the extension path via Chromium arguments
  • Disabling unrelated extensions

If this step is skipped, Playwright cannot access extension pages or APIs.

Accessing and Testing Extension Popup UI

Popup testing is timing-sensitive and requires resolving the extension ID dynamically.

Challenges include:

  • Extension IDs change per run
  • Popups close when focus changes
  • Assertions must execute immediately

Extract the extension ID:

const [serviceWorker] = context.serviceWorkers();const extensionId = serviceWorker.url().split(‘/’)[2];
Open the popup manually:

const popupUrl = `chrome-extension://${extensionId}/popup.html`;const popup = await context.newPage();
await popup.goto(popupUrl);
Assert popup UI quickly:

await expect(popup.getByText(‘Enable feature’)).toBeVisible();

Testing Extension Options Pages

Options pages behave like standard web pages and are ideal for deeper testing.

Common scenarios include:

  • Verifying default values
  • Updating settings
  • Validating persistence

Example:

const optionsUrl = `chrome-extension://${extensionId}/options.html`;await page.goto(optionsUrl);
await page.check(‘#trackingEnabled’);
await page.reload();
await expect(page.locator(‘#trackingEnabled’)).toBeChecked();

Validating Background Scripts and Service Workers

Background logic must be validated indirectly through observable effects.

Effective approaches include:

  • Triggering background actions via popup or options UI
  • Inspecting storage state
  • Observing network calls or tab changes

Example: validating storage update triggered by background logic:

const storage = await context.storageState();expect(JSON.stringify(storage)).toContain(‘trackingEnabled’);
For Manifest V3, background service workers may suspend, so tests must re-trigger actions rather than assume state persistence.

Handling Extension Permissions and Browser APIs

Permissions directly control extension behavior.

Automation must handle:

  • APIs failing when permissions are missing
  • Conditional logic based on permission availability
  • Differences between dev and prod manifests

Example: asserting graceful failure when permission is unavailable:

await expect(page.getByText(‘Permission required’)).toBeVisible();

Debugging Chrome Extension Tests in Playwright

Extension failures often require deeper inspection.

Useful debugging techniques include:

  • Running in headed mode
  • Slowing execution
  • Using Playwright Inspector

Example:

npx playwright test –debug
This allows step-by-step execution and DOM inspection across extension pages.

Extension tests passing locally but failing in CI?

Run Playwright Chrome extension tests on real browsers with BrowserStack to avoid CI-only issues.
Playwright Banner

Common Challenges in Playwright Extension Automation

Recurring issues include:

  • Popup closing before assertions run, causing flaky failures.
  • Service worker restarts in Manifest V3 resetting state.
  • Permission mismatches in CI environments.
  • Dynamic extension IDs breaking hardcoded URLs.
  • Chrome version differences affecting API behavior.

These issues require defensive test design rather than retries.

Best Practices for Chrome Extension Testing with Playwright

Chrome extension tests become flaky when they treat extension UIs like normal web pages and background logic like a stable process. The practices below keep tests stable by aligning them with how Chrome actually runs extensions.

  • Build the extension once and test the unpacked output: Tests should always load the same built folder (for example dist/) instead of a source directory. This prevents missing assets, wrong manifest fields, or dev-only scripts from slipping into automation. A consistent build step also ensures the same popup/options URLs exist across machines and CI.
  • Use launchPersistentContext and treat it as part of the test contract: Extensions must be loaded at browser startup, so using a persistent context is not optional. Tests should be structured around a single persistent session per test file (or per suite when appropriate) to reduce startup overhead and avoid intermittent “extension not available” errors.
  • Resolve the extension ID dynamically in every run: Hardcoding the extension ID is one of the fastest ways to create non-portable tests, because IDs can differ by profile or environment. Always derive it at runtime from the background page/service worker URL, then build chrome-extension:///… URLs from that value.
  • Keep popup tests short and assert only what matters: Popups close when focus shifts, so long workflows inside the popup are fragile. Limit popup tests to essential checks such as: the popup opens, core controls render, and the primary action triggers the expected effect. Move deeper validation to options pages or observable background outcomes.
  • Prefer testing complex UI through the options page when possible: Options pages behave like normal tabs, making them better suited for multi-step flows, form validation, and persistence checks. If a feature can be configured in options and reflected in popup behavior, validate logic in options first and keep popup assertions minimal.
  • Validate background/service worker logic through observable side effects: Direct inspection of background state is often unreliable, especially in Manifest V3 where service workers can suspend. Instead, assert outcomes like storage changes, network requests, injected UI changes on a webpage, or messages received by content scripts.
  • Make state explicit: reset storage and avoid test coupling: Extensions often store data in chrome.storage or local storage. Clear or reset state between tests so one test’s settings do not affect another. Tests should create their own state, verify it, and clean up, especially when running in parallel.
  • Account for Manifest V3 service worker suspension: Treat MV3 background logic as ephemeral. Trigger events that wake the service worker (opening popup, sending a message, visiting a matching URL) before asserting behavior. Avoid assumptions like “background is already running.”
  • Test permission-dependent behavior explicitly: Permissions change what the extension can do. Include tests that verify correct behavior when permissions are available and graceful handling when they are missing or denied. This prevents production-only failures when real users reject permission prompts.
  • Avoid brittle selectors inside extension UIs: Extension UIs often evolve quickly. Use stable selectors such as data-testid attributes or role-based locators instead of CSS chains tied to layout. This reduces breakage when UI structure changes but functionality stays the same.
  • Run extension tests on real Chrome environments before scaling: Local runs can hide version-specific bugs and CI-only focus/timing issues. Validating Playwright extension tests on real Chrome environments using BrowserStack Automate helps catch failures caused by browser differences and makes results consistent across machines and pipelines.

Run Playwright Chrome extension tests on real Chrome environments using BrowserStack Automate to catch version-specific, CI-only, and environment-driven failures that local extension testing often misses.

Talk to an Expert

Why Automate Tests for a Chrome Extension using Playwright with BrowserStack?

Automating Chrome extension tests with Playwright is usually built and validated on one local Chrome setup. BrowserStack Automatematters because extension behavior is heavily influenced by the exact Chrome build, OS, permissions, and runtime constraints-things that differ in CI and across machines.

  • Run on real Chrome versions, not a single local build: Extensions often break on specific Chrome versions due to API changes, manifest behavior (especially MV3 service workers), or UI rendering differences. BrowserStack Automate lets Playwright extension tests run against real Chrome versions, making failures reproducible instead of “works locally.”
  • Catch CI-only failures caused by environment differences: Extension tests are sensitive to timing, focus changes (popup auto-close), and resource constraints. A flow that passes on a developer machine can fail in CI due to slower startup, different window focus behavior, or different sandboxing. Running on BrowserStack reduces these surprises by using consistent real-browser environments.
  • More reliable debugging with execution artifacts: When an extension test fails, it is often unclear whether the issue is popup lifecycle, background/service worker state, permissions, or DOM timing. BrowserStack provides session artifacts like logs, screenshots, and video to pinpoint exactly where the extension UI or behavior diverged.
  • Scale without maintaining Chrome infrastructure: Extension testing at scale usually means managing Chrome versions, OS images, and parallel capacity-plus keeping them updated. BrowserStack Automate handles browser provisioning and scaling so Playwright tests can run in parallel without maintaining a custom grid.
  • Consistency across teams and machines: Extensions are especially prone to “different machine, different result” because small Chrome/OS variations change behavior. BrowserStack standardizes the environment so a test recorded or authored by one person behaves the same for the rest of the team.

Try BrowserStack Automate

Conclusion

Automating Chrome extension tests with Playwright requires understanding extension lifecycles, permissions, and browser-level constraints. With the correct setup, Playwright can reliably automate popups, options pages, and background logic.

Validating these tests on real Chrome environments using BrowserStack Automate ensures stability, scalability, and confidence that automation reflects real-world usage rather than idealized local conditions.

 

 

 

Tags
Playwright

Get answers on our Discord Community

Join our Discord community to connect with others! Get your questions answered and stay informed.

Join Discord Community
Discord