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 74 75 76 77 78 79 const result = await popup.evaluate(async () => {80 const send = (m: object) =>81 new Promise<{ ok?: boolean }>((res) => chrome.runtime.sendMessage(m, res));82 const create = await send({83 type: "CREATE_VAULT",84 payload: { password: "passkeysmoke123" },85 });86 const unlock = await send({87 type: "UNLOCK",88 payload: { password: "passkeysmoke123" },89 });90 await send({ type: "UPDATE_SETTINGS", payload: { lockOnClose: false } });91 const enable = await send({92 type: "SET_PASSKEY_PROVIDER_ENABLED",93 payload: { enabled: true },94 });95 return { create, unlock, enable };96 });9798 expect(result.create.ok).toBe(true);99 expect(result.unlock.ok).toBe(true);100 expect(result.enable.ok).toBe(true);101}102103async function approvePopupRequest(104 ctx: BrowserContext,105 extId: string,106 buttonName: RegExp,107 expectedTitle: RegExp108) {109 110 111 112 const popup = await openPopup(ctx, extId);113 await expect(popup.getByRole("heading", { name: expectedTitle })).toBeVisible({114 timeout: 15_000,115 });116 await popup.getByRole("button", { name: buttonName }).click();117}118119test.describe("Passkey Smoke", () => {120 let ctx: BrowserContext;121 let server: Server;122 let url: string;123124 test.beforeEach(async () => {125 ({ server, url } = await startFixtureServer());126 ctx = await makeContext();127 });128129 test.afterEach(async () => {130 if (ctx) await ctx.close();131 if (server) await stopFixtureServer(server);132 });133134 test("create() and get() resolve through the Peach extension provider", async () => {135 const extId = await waitForExtensionId(ctx);136 const popup = await openPopup(ctx, extId);137 await createVaultAndEnablePasskeys(popup);138139 const page = await ctx.newPage();140 await page.goto(url);141142 await page.getByRole("button", { name: /create passkey/i }).click();143 await approvePopupRequest(ctx, extId, /save passkey/i, /save a passkey\?/i);144145 const createStatus = page.locator("#create-status");146 await expect(createStatus).toHaveAttribute("data-state", "ok", {147 timeout: 15_000,148 });149 await expect(createStatus).toContainText("Created");150151 await page.getByRole("button", { name: /authenticate/i }).click();152 await approvePopupRequest(ctx, extId, /sign in/i, /sign in with passkey\?/i);153154 const getStatus = page.locator("#get-status");155 await expect(getStatus).toHaveAttribute("data-state", "ok", {156 timeout: 15_000,157 });158 await expect(getStatus).toContainText("Authenticated");159160 const refreshedPopup = await openPopup(ctx, extId);161 await refreshedPopup.locator(".peach-search-input").click();162 await expect(refreshedPopup.locator("text=Peach Passkey Smoke").first()).toBeVisible({163 timeout: 10_000,164 });165 await expect(refreshedPopup.locator("text=passkey").first()).toBeVisible();166 });167});