Skip to content
This repository was archived by the owner on Jun 17, 2024. It is now read-only.

Latest commit

 

History

History
182 lines (138 loc) · 4.78 KB

File metadata and controls

182 lines (138 loc) · 4.78 KB

Playwright

Playwright's Chrome Extension docs: https://playwright.dev/docs/chrome-extensions

This example will walk you through adding E2E tests written with Playwright.

Setup

First, install a few playwright dependencies and add a e2e convenience script:

package.json
@@ -9,13 +9,16 @@
     "dev:firefox": "wxt -b firefox",
     "build": "wxt build",
     "build:firefox": "wxt build -b firefox",
     "zip": "wxt zip",
     "zip:firefox": "wxt zip -b firefox",
     "compile": "tsc --noEmit",
-    "postinstall": "wxt prepare"
+    "postinstall": "wxt prepare",
+    "e2e": "playwright test"
   },
   "devDependencies": {
+    "@playwright/test": "^1.40.1",
+    "playwright": "^1.40.1",
     "typescript": "^5.3.3",
     "wxt": "^0.16.0"
   }
 }

Next, we'll add config for Playwright:

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: 'e2e',

  // Fail the build on CI if you accidentally left test.only in the source code.
  forbidOnly: !!process.env.CI,

  // Retry on CI only.
  retries: process.env.CI ? 2 : 0,

  // Opt out of parallel tests on CI.
  workers: process.env.CI ? 1 : undefined,

  // Reporter to use
  reporter: 'html',

  use: {
    // Collect trace when retrying the failed test.
    trace: 'on-first-retry',
  },

  // Configure projects for major browsers.
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

Then we can create custom fixtures that will:

  1. Open a browser window with the extension installed
  2. Wait for the background script/service worker to extract the extension's ID from the URL
e2e/fixtures.ts
import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';

const pathToExtension = path.resolve('.output/chrome-mv3');

export const test = base.extend<{
  context: BrowserContext;
  extensionId: string;
}>({
  context: async ({}, use) => {
    const context = await chromium.launchPersistentContext('', {
      headless: false,
      args: [
        `--disable-extensions-except=${pathToExtension}`,
        `--load-extension=${pathToExtension}`,
      ],
    });
    await use(context);
    await context.close();
  },
  extensionId: async ({ context }, use) => {
    let background: { url(): string };
    if (pathToExtension.endsWith('-mv3')) {
      [background] = context.serviceWorkers();
      if (!background) background = await context.waitForEvent('serviceworker');
    } else {
      [background] = context.backgroundPages();
      if (!background)
        background = await context.waitForEvent('backgroundpage');
    }

    const extensionId = background.url().split('/')[2];
    await use(extensionId);
  },
});
export const expect = test.expect;

Writing Tests

We're going to write a test to make sure the Popup's counter increments when pressed.

Frameworks often recommend creating an abstraction around their APIs and writing helper function to interact with the page more naturally during tests. We'll follow this approach and create a openPopup util, which returns natural helper functions.

e2e/pages/popup.ts
import { Page } from '@playwright/test';

export async function openPopup(page: Page, extensionId: string) {
  await page.goto(`chrome-extension://${extensionId}/popup.html`);

  await page.waitForSelector('#counter');

  const popup = {
    getCounter: () => page.waitForSelector('#counter'),
    clickCounter: async () => {
      const counter = await popup.getCounter();
      await counter.click();
    },
    getCounterText: async () => {
      const counter = await popup.getCounter();
      return await counter.evaluate((el) => el.textContent);
    },
  };
  return popup;
}

Chromium doesn't support opening popups in the native UI location under the action during testing. Instead, you have to open the popup's URL in a new tab.

And now we can write a simple test:

e2e/popup-counter.spec.ts
import { test, expect } from './fixtures';
import { openPopup } from './pages/popup';

test('Popup counter increments when clicked', async ({ page, extensionId }) => {
  const popup = await openPopup(page, extensionId);
  expect(await popup.getCounterText()).toEqual('count is 0');

  await popup.clickCounter();
  expect(await popup.getCounterText()).toEqual('count is 1');

  await popup.clickCounter();
  expect(await popup.getCounterText()).toEqual('count is 2');
});

Make sure to import your custom test and expect fixtures so the extension is loaded correctly.

Running Tests

To run the tests, build the extension for production and run the e2e script we added at the very beginning of this walkthrough.

pnpm build && pnpm e2e

The test should pass!