IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Advanced·20 / 24

Chrome extensions

How to load and test Chromium extensions with Playwright using a persistent context and extension-specific fixtures.

Chromium only

Chrome extension testing is a Chromium-specific scenario. Extensions need a persistent browser context and custom launch arguments that point to the unpacked extension directory.

Use the bundled chromium channel when loading extensions. Google Chrome and Microsoft Edge removed the command-line flags Playwright needs for side-loading extensions.

Launch an extension

Launch a persistent context with --disable-extensions-except and --load-extension. For Manifest V3 extensions, wait for the extension service worker and derive the extension id from its URL.

ts
import { chromium } from '@playwright/test';
import path from 'node:path';

const pathToExtension = path.join(__dirname, 'my-extension');
const userDataDir = '/tmp/test-user-data-dir';

const context = await chromium.launchPersistentContext(userDataDir, {
  channel: 'chromium',
  args: [
    '--disable-extensions-except=' + pathToExtension,
    '--load-extension=' + pathToExtension,
  ],
});

let [serviceWorker] = context.serviceWorkers();
if (!serviceWorker) {
  serviceWorker = await context.waitForEvent('serviceworker');
}

const extensionId = serviceWorker.url().split('/')[2];
await context.close();

Create a test fixture

For Playwright Test, wrap the persistent context and extension id in fixtures. Tests can then open the extension popup or verify the extension effect on normal pages.

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

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

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

  extensionId: async ({ context }, use) => {
    let [serviceWorker] = context.serviceWorkers();
    if (!serviceWorker) {
      serviceWorker = await context.waitForEvent('serviceworker');
    }

    await use(serviceWorker.url().split('/')[2]);
  },
});

export const expect = test.expect;
ts
import { expect, test } from './fixtures';

test('popup page', async ({ page, extensionId }) => {
  await page.goto('chrome-extension://' + extensionId + '/popup.html');
  await expect(page.locator('body')).toHaveText('my-extension popup');
});