passkeys.spec.ts

5.1 KB · raw

1import { test, expect, type BrowserContext, type Page } from "@playwright/test";2import { createServer, type Server } from "http";3import { readFile } from "fs/promises";4import { fileURLToPath } from "url";5import { dirname, join } from "path";6import os from "os";7import fs from "fs/promises";89const __filename = fileURLToPath(import.meta.url);10const __dirname = dirname(__filename);11const EXTENSION_PATH = join(__dirname, "..", "dist-chrome");12const FIXTURE_PATH = join(__dirname, "fixtures", "passkeys-smoke.html");1314async function startFixtureServer(): Promise<{ server: Server; url: string }> {15  const html = await readFile(FIXTURE_PATH, "utf-8");16  const server = createServer((_, res) => {17    res.writeHead(200, { "content-type": "text/html; charset=utf-8" });18    res.end(html);19  });2021  await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));22  const address = server.address();23  if (!address || typeof address === "string") {24    throw new Error("Unable to determine fixture server port");25  }2627  return { server, url: `http://localhost:${address.port}` };28}2930async function stopFixtureServer(server: Server): Promise<void> {31  await new Promise<void>((resolve, reject) => {32    server.close((error) => (error ? reject(error) : resolve()));33  });34}3536async function makeContext() {37  const { chromium } = await import("playwright");38  const userDataDir = join(39    os.tmpdir(),40    `peach-passkeys-${Date.now()}-${Math.random().toString(36).slice(2)}`41  );42  await fs.mkdir(userDataDir, { recursive: true });43  return chromium.launchPersistentContext(userDataDir, {44    headless: false,45    args: [46      `--load-extension=${EXTENSION_PATH}`,47      `--disable-extensions-except=${EXTENSION_PATH}`,48      "--no-sandbox",49      "--disable-setuid-sandbox",50    ],51  });52}5354async function waitForExtensionId(ctx: BrowserContext, maxWait = 15_000): Promise<string> {55  const start = Date.now();56  let sw = ctx.serviceWorkers()[0];57  while (!sw && Date.now() - start < maxWait) {58    await new Promise((resolve) => setTimeout(resolve, 250));59    sw = ctx.serviceWorkers()[0];60  }61  if (!sw) throw new Error(`No service worker after ${maxWait}ms`);62  return new URL(sw.url()).host;63}6465async function openPopup(ctx: BrowserContext, extId: string): Promise<Page> {66  const popup = await ctx.newPage();67  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);68  await popup.waitForTimeout(1500);69  return popup;70}7172async function createVaultAndEnablePasskeys(popup: Page) {73  const passwordInput = popup.locator("#master-password");74  if (await passwordInput.isVisible().catch(() => false)) {75    await passwordInput.fill("passkeysmoke123");76    await popup.locator("#confirm-password").fill("passkeysmoke123");77    await popup.getByRole("button", { name: /create vault/i }).click();78    await popup.waitForTimeout(1500);79  }8081  const response = await popup.evaluate(async () => {82    return new Promise((resolve) => {83      chrome.runtime.sendMessage(84        { type: "SET_PASSKEY_PROVIDER_ENABLED", payload: { enabled: true } },85        (result) => resolve(result)86      );87    });88  });8990  expect((response as { ok?: boolean }).ok).toBe(true);91}9293async function approvePopupRequest(94  popup: Page,95  buttonName: RegExp,96  expectedTitle: RegExp97) {98  await expect(popup.getByRole("heading", { name: expectedTitle })).toBeVisible({99    timeout: 15_000,100  });101  await popup.getByRole("button", { name: buttonName }).click();102}103104test.describe("Passkey Smoke", () => {105  let ctx: BrowserContext;106  let server: Server;107  let url: string;108109  test.beforeEach(async () => {110    ({ server, url } = await startFixtureServer());111    ctx = await makeContext();112  });113114  test.afterEach(async () => {115    if (ctx) await ctx.close();116    if (server) await stopFixtureServer(server);117  });118119  test("create() and get() resolve through the Peach extension provider", async () => {120    const extId = await waitForExtensionId(ctx);121    const popup = await openPopup(ctx, extId);122    await createVaultAndEnablePasskeys(popup);123124    const page = await ctx.newPage();125    await page.goto(url);126127    await page.getByRole("button", { name: /create passkey/i }).click();128    await approvePopupRequest(popup, /save passkey/i, /save a passkey\?/i);129130    const createStatus = page.locator("#create-status");131    await expect(createStatus).toHaveAttribute("data-state", "ok", {132      timeout: 15_000,133    });134    await expect(createStatus).toContainText("Created");135136    await page.getByRole("button", { name: /authenticate/i }).click();137    await approvePopupRequest(popup, /sign in/i, /sign in with passkey\?/i);138139    const getStatus = page.locator("#get-status");140    await expect(getStatus).toHaveAttribute("data-state", "ok", {141      timeout: 15_000,142    });143    await expect(getStatus).toContainText("Authenticated");144145    const refreshedPopup = await openPopup(ctx, extId);146    await refreshedPopup.locator(".peach-search-input").click();147    await expect(refreshedPopup.locator("text=Peach Passkey Smoke").first()).toBeVisible({148      timeout: 10_000,149    });150    await expect(refreshedPopup.locator("text=passkey").first()).toBeVisible();151  });152});