src/__tests__/e2e/fixtures/extension.ts

Total Symbols
2
Lines of Code
111
Avg Complexity
1.5
Symbol Types
2

Symbols by Kind

interface 1
function 1

All Symbols

Name Kind Visibility Status Lines Signature
ExtensionFixtures interface exported- 11-15 interface ExtensionFixtures
sendToTab function exported- 95-110 sendToTab( background: Worker, pageUrl: string, message: Record<string, unknown>, ): : Promise<unknown>

Full Source

/// <reference types="node" />
import { test as base, chromium, expect } from "@playwright/test";
import type { BrowserContext, Worker } from "@playwright/test";
import path from "path";

const EXTENSION_PATH = path.join(process.cwd(), "dist");
const CHROME_PATH =
  process.env.CHROME_PATH ||
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";

export interface ExtensionFixtures {
  extContext: BrowserContext;
  background: Worker;
  extensionId: string;
}

/**
 * Extended Playwright `test` with a persistent Chrome context that has the
 * Fill All extension loaded.
 *
 * Provides:
 * - `extContext`: the BrowserContext with the extension loaded
 * - `background`: the extension's background service worker (for sending messages)
 * - `extensionId`: the extension ID extracted from the service worker URL
 *
 * Usage:
 * ```ts
 * import { test, expect } from "@/__tests__/e2e/fixtures/extension";
 *
 * test("fill form", async ({ extContext, background }) => {
 *   const page = await extContext.newPage();
 *   await page.goto("http://localhost:8765/test-form.html");
 *   // ...
 * });
 * ```
 *
 * Helper to send a message to the content script of a page:
 * ```ts
 * await sendToTab(background, page.url(), { type: "FILL_ALL_FIELDS" });
 * ```
 */
export const test = base.extend<ExtensionFixtures>({
  extContext: [
    async ({}, use) => {
      const context = await chromium.launchPersistentContext("", {
        headless: false,
        executablePath: CHROME_PATH,
        args: [
          `--disable-extensions-except=${EXTENSION_PATH}`,
          `--load-extension=${EXTENSION_PATH}`,
          "--no-first-run",
          "--no-default-browser-check",
          "--disable-infobars",
          "--disable-popup-blocking",
          "--disable-background-networking",
        ],
      });
      await use(context);
      await context.close();
    },
    { scope: "test" },
  ],

  background: async ({ extContext }, use) => {
    let [sw] = extContext.serviceWorkers();
    if (!sw) {
      sw = await extContext.waitForEvent("serviceworker");
    }
    // Give the service worker time to initialize
    await sw.waitForEvent("close").catch(() => {});
    [sw] = extContext.serviceWorkers();
    if (!sw) {
      sw = await extContext.waitForEvent("serviceworker");
    }
    await use(sw);
  },

  extensionId: async ({ background }, use) => {
    const extensionId = background.url().split("/")[2];
    await use(extensionId);
  },
});

export { expect };

/**
 * Sends a message to the content script of the given page via the background
 * service worker (`chrome.tabs.sendMessage`).
 *
 * @param background - The extension background service worker
 * @param pageUrl - URL of the page (used to find the tab ID)
 * @param message - Message object to send (`{ type, payload? }`)
 * @returns The response from the content script
 */
export async function sendToTab(
  background: Worker,
  pageUrl: string,
  message: Record<string, unknown>,
): Promise<unknown> {
  const origin = new URL(pageUrl).origin;
  return background.evaluate(
    async ({ tabOrigin, msg }) => {
      const tabs = await chrome.tabs.query({ url: `${tabOrigin}/*` });
      const tab = tabs[0];
      if (!tab?.id) throw new Error(`No tab found for origin: ${tabOrigin}`);
      return chrome.tabs.sendMessage(tab.id, msg);
    },
    { tabOrigin: origin, msg: message },
  );
}