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});