1import { test, expect } from "@playwright/test";2import type { BrowserContext } from "@playwright/test";3import { fileURLToPath } from "url";4import { dirname, join } from "path";5import os from "os";6import fs from "fs/promises";78const __filename = fileURLToPath(import.meta.url);9const __dirname = dirname(__filename);10const EXTENSION_PATH = join(__dirname, "..", "dist");1112async function makeContext() {13 const { chromium } = await import("playwright");14 const userDataDir = join(os.tmpdir(), `peach-test-${Date.now()}`);15 await fs.mkdir(userDataDir, { recursive: true });16 return chromium.launchPersistentContext(userDataDir, {17 headless: false,18 args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],19 });20}2122async function setupVault(ctx: BrowserContext): Promise<{ popup: import("@playwright/test").Page, extId: string }> {23 const maxWait = 15000;24 const start = Date.now();25 let sw = ctx.serviceWorkers()[0];26 while (!sw && Date.now() - start < maxWait) {27 await new Promise(r => setTimeout(r, 500));28 sw = ctx.serviceWorkers()[0];29 }30 if (!sw) throw new Error(`No SW after ${maxWait}ms`);31 const extId = new URL(sw.url()).host;32 const popup = await ctx.newPage();33 await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);34 await popup.waitForTimeout(2000);35 const pw = popup.locator("#master-password");36 if (await pw.isVisible()) {37 await pw.fill("password123");38 await popup.locator("#confirm-password").fill("password123");39 await popup.getByRole("button", { name: /create vault/i }).click();40 await popup.waitForTimeout(2000);41 }42 return { popup, extId };43}4445test.describe("Settings & Appearance", () => {46 let ctx: BrowserContext;47 test.afterEach(async () => { await ctx.close(); });48 test.beforeEach(async () => { ctx = await makeContext(); });4950 test("21 - settings panel opens from chrome bar", async () => {51 const { popup } = await setupVault(ctx);52 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();53 await popup.waitForTimeout(500);54 await expect(popup.locator(".peach-overlay-title").or(popup.locator("text=settings"))).toBeVisible();55 });5657 test("22 - settings shows lock on close, breach monitoring, auto-lock", async () => {58 const { popup } = await setupVault(ctx);59 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();60 await popup.waitForTimeout(500);61 await expect(popup.locator("text=lock on close")).toBeVisible();62 await expect(popup.locator("text=breach monitoring")).toBeVisible();63 await expect(popup.locator("text=auto-lock")).toBeVisible();64 await expect(popup.locator("text=clipboard clear")).toBeVisible();65 });6667 test("23 - settings shows sync stub with pro badge", async () => {68 const { popup } = await setupVault(ctx);69 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();70 await popup.waitForTimeout(500);71 await expect(popup.locator("text=sync")).toBeVisible();72 await expect(popup.locator(".peach-pro-badge")).toBeVisible();73 });7475 test("24 - settings shows vault entry count", async () => {76 const { popup } = await setupVault(ctx);77 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();78 await popup.waitForTimeout(500);79 await expect(popup.locator("text=entries").first()).toBeVisible();80 });8182 test("25 - accent color presets are all clickable", async () => {83 const { popup } = await setupVault(ctx);84 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();85 await popup.waitForTimeout(500);86 const swatches = popup.locator(".peach-swatch");87 const count = await swatches.count();88 expect(count).toBeGreaterThanOrEqual(6);89 for (let i = 0; i < count; i++) {90 await swatches.nth(i).click();91 await popup.waitForTimeout(100);92 }93 });9495 test("26 - lock vault button returns to lock screen", async () => {96 const { popup } = await setupVault(ctx);97 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();98 await popup.waitForTimeout(500);99 await popup.locator("text=lock vault").click();100 await popup.waitForTimeout(1000);101 const pwField = popup.locator("#master-password");102 await expect(pwField).toBeVisible({ timeout: 3000 });103 });104105 test("27 - import passwords button opens import view", async () => {106 const { popup } = await setupVault(ctx);107 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();108 await popup.waitForTimeout(500);109 await popup.getByRole("button", { name: /import passwords/i }).click();110 await popup.waitForTimeout(500);111 await expect(popup.locator(".import-title, text=import")).toBeVisible();112 });113114 test("28 - export backup button opens export view", async () => {115 const { popup } = await setupVault(ctx);116 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();117 await popup.waitForTimeout(500);118 await popup.getByRole("button", { name: /export backup/i }).click();119 await popup.waitForTimeout(500);120 await expect(popup.locator(".export-title, text=export")).toBeVisible();121 });122123 test("29 - onboarding shows peach wordmark and tagline", async () => {124 const c = await makeContext();125 const maxWait = 15000;126 const start = Date.now();127 let sw = c.serviceWorkers()[0];128 while (!sw && Date.now() - start < maxWait) {129 await new Promise(r => setTimeout(r, 500));130 sw = c.serviceWorkers()[0];131 }132 if (!sw) throw new Error("No SW");133 const extId = new URL(sw.url()).host;134 const p = await c.newPage();135 await p.goto(`chrome-extension://${extId}/src/popup/index.html`);136 await p.waitForTimeout(2000);137 await expect(p.locator(".wordmark, .peach-wordmark").first().or(p.locator("text=peach"))).toBeVisible();138 await expect(p.locator(".peach-onboarding-headline, text=your secrets").first().or(p.locator("text=your secrets"))).toBeVisible();139 await c.close();140 });141142 test("30 - lock screen pre-rendered HTML visible before JS", async () => {143 const { chromium } = await import("playwright");144 const userDataDir = join(os.tmpdir(), `peach-prelock-test-${Date.now()}`);145 await fs.mkdir(userDataDir, { recursive: true });146 const c = await chromium.launchPersistentContext(userDataDir, {147 headless: true,148 args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],149 });150 const maxWait = 15000;151 const start = Date.now();152 let sw = c.serviceWorkers()[0];153 while (!sw && Date.now() - start < maxWait) {154 await new Promise(r => setTimeout(r, 500));155 sw = c.serviceWorkers()[0];156 }157 if (!sw) throw new Error("No SW");158 const extId = new URL(sw.url()).host;159 await c.newPage();160 const p = await c.newPage();161 await p.goto(`chrome-extension://${extId}/src/popup/index.html`);162 await p.waitForTimeout(200);163 const preLock = p.locator("#pre-lock");164 await expect(preLock).toBeAttached();165 await c.close();166 });167});