12345678910111213141516171819202122232425import { test, expect } from "@playwright/test";26import type { BrowserContext, Page } from "@playwright/test";27import { fileURLToPath } from "url";28import { dirname, join } from "path";29import os from "os";30import fs from "fs/promises";3132const __filename = fileURLToPath(import.meta.url);33const __dirname = dirname(__filename);34const EXTENSION_PATH = join(__dirname, "..", "dist");3536const CODEX_PDF_PATH = "/Users/june/Downloads/peach-codex-2026-05-20 (2).pdf";3738async function makeContext(): Promise<BrowserContext> {39 const { chromium } = await import("playwright");40 const userDataDir = join(os.tmpdir(), `peach-codex-test-${Date.now()}`);41 await fs.mkdir(userDataDir, { recursive: true });42 return chromium.launchPersistentContext(userDataDir, {43 headless: false,44 args: [45 `--load-extension=${EXTENSION_PATH}`,46 `--disable-extensions-except=${EXTENSION_PATH}`,47 ],48 });49}5051async function getExtId(ctx: BrowserContext): Promise<string> {52 const maxWait = 15_000;53 const start = Date.now();54 let sw = ctx.serviceWorkers()[0];55 while (!sw && Date.now() - start < maxWait) {56 await new Promise((r) => setTimeout(r, 500));57 sw = ctx.serviceWorkers()[0];58 }59 if (!sw) throw new Error(`No service worker after ${maxWait}ms`);60 return new URL(sw.url()).host;61}6263async function openCodexRestorePage(ctx: BrowserContext, extId: string): Promise<Page> {64 const page = await ctx.newPage();6566 67 page.on("console", (msg) => {68 const text = msg.text();69 if (msg.type() === "error" || text.startsWith("[codex]")) {70 console.log(` [browser:${msg.type()}] ${text}`);71 }72 });7374 75 76 77 await page.goto(`chrome-extension://${extId}/src/popup/index.html?codex=restore`);7879 80 81 await page.waitForSelector('input[type="file"]', { state: "attached", timeout: 10_000 });8283 return page;84}8586test.describe("Codex PDF restore", () => {87 let ctx: BrowserContext;8889 test.beforeEach(async () => {90 ctx = await makeContext();91 });9293 test.afterEach(async () => {94 await ctx.close();95 });9697 test("imports all chunks from the real Codex PDF", async () => {98 99 100 test.setTimeout(180_000);101102 const extId = await getExtId(ctx);103 const page = await openCodexRestorePage(ctx, extId);104105 106 107 await page.locator('input[type="file"]').setInputFiles(CODEX_PDF_PATH);108109 110 111 112 113 114 await page.waitForFunction(115 () => {116 const body = document.body.textContent ?? "";117 return (118 body.includes("codes") ||119 body.includes("master password") ||120 body.includes("no Codex codes found") ||121 body.includes("failed to read")122 );123 },124 { timeout: 150_000, polling: 1_000 },125 );126127 const bodyText = await page.evaluate(() => document.body.textContent ?? "");128 console.log("Body snippet:", bodyText.replace(/\s+/g, " ").slice(0, 400));129130 expect(bodyText).not.toContain("no Codex codes found");131 expect(bodyText).not.toContain("failed to read");132133 const passphraseVisible = await page.locator("#codex-passphrase").isVisible().catch(() => false);134 if (passphraseVisible) {135 console.log("✓ Buffer is ready — passphrase prompt visible.");136 }137 expect(bodyText).toMatch(/master password|codes/);138 });139140 test("decodes at least one Aztec envelope from page 1", async () => {141 test.setTimeout(120_000);142143 const extId = await getExtId(ctx);144 const page = await openCodexRestorePage(ctx, extId);145146 await page.locator('input[type="file"]').setInputFiles(CODEX_PDF_PATH);147148 await page.waitForFunction(149 () => {150 const body = document.body.textContent ?? "";151 return (152 body.includes("codes") ||153 body.includes("master password") ||154 body.includes("no Codex codes found") ||155 body.includes("failed to read")156 );157 },158 { timeout: 90_000, polling: 500 },159 );160161 const bodyText = await page.evaluate(() => document.body.textContent ?? "");162 console.log("Body snippet:", bodyText.slice(0, 300));163164 expect(bodyText).not.toContain("no Codex codes found");165 expect(bodyText).not.toContain("failed to read");166 });167});