import { test, expect } from "@playwright/test"; import type { BrowserContext } from "@playwright/test"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import os from "os"; import fs from "fs/promises"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const EXTENSION_PATH = join(__dirname, "..", "dist"); async function makeContext() { const { chromium } = await import("playwright"); const userDataDir = join(os.tmpdir(), `peach-test-${Date.now()}`); await fs.mkdir(userDataDir, { recursive: true }); return chromium.launchPersistentContext(userDataDir, { headless: false, args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`], }); } async function setupVault(ctx: BrowserContext): Promise<{ popup: import("@playwright/test").Page, extId: string }> { const maxWait = 15000; const start = Date.now(); let sw = ctx.serviceWorkers()[0]; while (!sw && Date.now() - start < maxWait) { await new Promise(r => setTimeout(r, 500)); sw = ctx.serviceWorkers()[0]; } if (!sw) throw new Error(`No SW after ${maxWait}ms`); const extId = new URL(sw.url()).host; const popup = await ctx.newPage(); await popup.goto(`chrome-extension://${extId}/src/popup/index.html`); await popup.waitForTimeout(2000); const pw = popup.locator("#master-password"); if (await pw.isVisible()) { await pw.fill("password123"); await popup.locator("#confirm-password").fill("password123"); await popup.getByRole("button", { name: /create vault/i }).click(); await popup.waitForTimeout(2000); } return { popup, extId }; } test.describe("Settings & Appearance", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("21 - settings panel opens from chrome bar", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await expect(popup.locator(".peach-overlay-title").or(popup.locator("text=settings"))).toBeVisible(); }); test("22 - settings shows lock on close, breach monitoring, auto-lock", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await expect(popup.locator("text=lock on close")).toBeVisible(); await expect(popup.locator("text=breach monitoring")).toBeVisible(); await expect(popup.locator("text=auto-lock")).toBeVisible(); await expect(popup.locator("text=clipboard clear")).toBeVisible(); }); test("23 - settings shows sync stub with pro badge", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await expect(popup.locator("text=sync")).toBeVisible(); await expect(popup.locator(".peach-pro-badge")).toBeVisible(); }); test("24 - settings shows vault entry count", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await expect(popup.locator("text=entries").first()).toBeVisible(); }); test("25 - accent color presets are all clickable", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); const swatches = popup.locator(".peach-swatch"); const count = await swatches.count(); expect(count).toBeGreaterThanOrEqual(6); for (let i = 0; i < count; i++) { await swatches.nth(i).click(); await popup.waitForTimeout(100); } }); test("26 - lock vault button returns to lock screen", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.locator("text=lock vault").click(); await popup.waitForTimeout(1000); const pwField = popup.locator("#master-password"); await expect(pwField).toBeVisible({ timeout: 3000 }); }); test("27 - import passwords button opens import view", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); await expect(popup.locator(".import-title, text=import")).toBeVisible(); }); test("28 - export backup button opens export view", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); await expect(popup.locator(".export-title, text=export")).toBeVisible(); }); test("29 - onboarding shows peach wordmark and tagline", async () => { const c = await makeContext(); const maxWait = 15000; const start = Date.now(); let sw = c.serviceWorkers()[0]; while (!sw && Date.now() - start < maxWait) { await new Promise(r => setTimeout(r, 500)); sw = c.serviceWorkers()[0]; } if (!sw) throw new Error("No SW"); const extId = new URL(sw.url()).host; const p = await c.newPage(); await p.goto(`chrome-extension://${extId}/src/popup/index.html`); await p.waitForTimeout(2000); await expect(p.locator(".wordmark, .peach-wordmark").first().or(p.locator("text=peach"))).toBeVisible(); await expect(p.locator(".peach-onboarding-headline, text=your secrets").first().or(p.locator("text=your secrets"))).toBeVisible(); await c.close(); }); test("30 - lock screen pre-rendered HTML visible before JS", async () => { const { chromium } = await import("playwright"); const userDataDir = join(os.tmpdir(), `peach-prelock-test-${Date.now()}`); await fs.mkdir(userDataDir, { recursive: true }); const c = await chromium.launchPersistentContext(userDataDir, { headless: true, args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`], }); const maxWait = 15000; const start = Date.now(); let sw = c.serviceWorkers()[0]; while (!sw && Date.now() - start < maxWait) { await new Promise(r => setTimeout(r, 500)); sw = c.serviceWorkers()[0]; } if (!sw) throw new Error("No SW"); const extId = new URL(sw.url()).host; await c.newPage(); const p = await c.newPage(); await p.goto(`chrome-extension://${extId}/src/popup/index.html`); await p.waitForTimeout(200); const preLock = p.locator("#pre-lock"); await expect(preLock).toBeAttached(); await c.close(); }); });