Site icon QATechTools

Chrome Extension E2E Testing with Playwright: A Practical QA Guide

Chrome Extension E2E Testing with Playwright: A Practical QA Guide featured image

Chrome extension E2E testing with Playwright helps QA engineers validate the real workflow users see: the extension loads, its popup opens, a page changes as expected, and failures produce useful evidence. The setup is different from a normal website test because the extension must be loaded when Chromium starts and its runtime ID is not something your test should guess.

This tutorial builds a small, repeatable workflow for a Manifest V3 extension. It follows current official Chrome and Playwright guidance and avoids brittle shortcuts such as hard-coding an extension ID or asserting only against hidden implementation state.

What you will test

Assume your unpacked extension lives in my-extension/. Its popup contains an Enable switch, and enabling it adds a visible marker to a sample page. The tutorial will:

  1. launch Playwright’s bundled Chromium with the extension loaded;
  2. discover the Manifest V3 extension ID from its service worker;
  3. open and test the popup page;
  4. assert a user-visible result on the target page;
  5. save screenshots and traces when a test fails; and
  6. plan one focused service-worker resilience check.

Chrome’s official E2E guidance recommends testing flows a user would perform and generally preferring visible behavior over internal state. That principle keeps the suite useful even when implementation details change.

Prerequisites

Install Playwright Test and its browser:

npm init playwright@latest
npx playwright install chromium

Keep the extension build deterministic. If your source must be compiled, add a build command and point the fixture to the generated extension directory, not the source directory.

Step 1: Create a persistent Chromium fixture

Playwright’s official Chrome extension guide says extensions work in Chromium through a persistent browser context. It also recommends Playwright’s bundled Chromium because Google Chrome and Microsoft Edge removed the command-line flags used for this side-loading workflow.

Create tests/extension.fixture.ts:

import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';

type ExtensionFixtures = {
  context: BrowserContext;
  extensionId: string;
};

export const test = base.extend<ExtensionFixtures>({
  context: async ({}, use) => {
    const extensionPath = path.join(__dirname, '..', 'my-extension');
    const context = await chromium.launchPersistentContext('', {
      channel: 'chromium',
      args: [
        `--disable-extensions-except=${extensionPath}`,
        `--load-extension=${extensionPath}`
      ]
    });

    await use(context);
    await context.close();
  },

  extensionId: async ({ context }, use) => {
    let [worker] = context.serviceWorkers();
    if (!worker) {
      worker = await context.waitForEvent('serviceworker');
    }
    const extensionId = worker.url().split('/')[2];
    await use(extensionId);
  }
});

export const expect = test.expect;

The empty user-data directory tells Playwright to create a temporary persistent profile. The service-worker URL has the form chrome-extension://EXTENSION_ID/..., so the fixture can retrieve the ID at runtime instead of embedding a machine-specific value.

Step 2: Test the popup like a normal page

Chrome documentation confirms that extension pages can be opened through their chrome-extension:// URLs. This is a practical way to test a popup when automating a toolbar click is not available or would add unnecessary fragility.

Create tests/popup.spec.ts:

import { test, expect } from './extension.fixture';

test('enables the extension from the popup', async ({ page, extensionId }) => {
  await page.goto(`chrome-extension://${extensionId}/popup.html`);

  await expect(page.getByRole('heading', { name: 'Extension settings' }))
    .toBeVisible();

  const enableSwitch = page.getByRole('switch', { name: 'Enable extension' });
  await expect(enableSwitch).toHaveAttribute('aria-checked', 'false');
  await enableSwitch.click();
  await expect(enableSwitch).toHaveAttribute('aria-checked', 'true');
});

Use role- and label-based locators when the popup exposes accessible names. These locators mirror user intent and make accessibility problems visible during test review. Replace the example names with the actual accessible labels in your extension.

Step 3: Validate behavior on a target page

A popup assertion proves the control changed, but not that the extension delivered its value. Add a second test against a controlled sample page:

import { test, expect } from './extension.fixture';

test('shows the enabled marker on the test page', async ({ page, extensionId }) => {
  await page.goto(`chrome-extension://${extensionId}/popup.html`);
  await page.getByRole('switch', { name: 'Enable extension' }).click();

  await page.goto('http://127.0.0.1:4173/extension-test-page');
  await expect(page.getByTestId('extension-enabled-marker')).toBeVisible();
  await expect(page.getByTestId('extension-enabled-marker'))
    .toHaveText('Extension enabled');
});

Run the sample site locally in CI so the test does not depend on a changing public page. Verify that the manifest grants only the host access the test needs. A passing popup test plus a passing target-page assertion gives stronger evidence than checking storage alone.

Step 4: Add evidence for failures

Configure Playwright to retain a trace and screenshot on failure:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    screenshot: 'only-on-failure',
    trace: 'retain-on-failure'
  },
  reporter: [['html', { open: 'never' }]]
});

Run the focused suite:

npx playwright test tests/popup.spec.ts
npx playwright show-report

For triage, capture the failing step, popup state, target-page state, console errors, and trace. Do not treat a screenshot alone as proof of the root cause; combine it with the action timeline and relevant logs.

Step 5: Cover Manifest V3 service-worker resilience

Manifest V3 extension service workers can stop after inactivity and restart when an event arrives. Chrome advises developers to persist important state rather than relying only on global variables. Playwright’s extension documentation also explains how its worker handle behaves across idle suspension.

Add a focused scenario for any critical workflow that depends on worker state:

  1. enable the feature and confirm the visible result;
  2. allow or deliberately trigger the worker lifecycle condition in a controlled test;
  3. perform the user action again; and
  4. verify that persisted settings and the visible result still work.

Keep this as a separate resilience test because it can take longer and needs clearer diagnostics. Avoid fixed-duration waiting as a synchronization strategy. Wait for observable events, UI states, or messages that prove the extension is ready.

Practical QA review checklist

Screenshot checklist

Common mistakes

What this tutorial does not replace

One E2E flow does not replace unit tests, manual permission inspection, accessibility testing, security review, browser-version coverage, Web Store policy checks, or exploratory testing. Use this workflow as a reliable automation layer around your most important user journeys.

References

FAQ

Can Playwright test a Manifest V3 Chrome extension?

Yes. Playwright documents loading an unpacked extension in its bundled Chromium with a persistent context and retrieving the extension ID from the service worker.

Why should the extension ID be discovered dynamically?

The runtime ID can differ across environments. Deriving it from the service-worker URL makes the test portable and avoids a fragile hard-coded value.

Should a Chrome extension E2E test inspect internal storage?

Only when it adds useful diagnostic coverage. The primary assertion should normally reflect user-visible behavior, following Chrome’s E2E testing guidance.

Do these tests replace manual Chrome extension testing?

No. They automate repeatable critical flows, while exploratory testing, permission review, accessibility, security, and release judgment remain important human activities.

Conclusion

Chrome extension E2E testing with Playwright becomes manageable when the setup is explicit: load the unpacked build in a persistent Chromium context, discover the extension ID, test the popup as a page, assert the visible target-page outcome, and retain strong failure evidence. Add one targeted Manifest V3 lifecycle scenario for critical state, and your QA suite will catch problems that simple popup checks often miss.


Exit mobile version