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("Copy & TOTP", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("14 - copy username shows copied state", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("CopyTest"); await popup.locator('input[placeholder="domain.com"]').fill("https://copytest.com"); await popup.locator('input[placeholder="username or email"]').fill("copyuser@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("copypass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator("text=CopyTest").first().click(); await popup.waitForTimeout(500); const copyBtn = popup.locator(".peach-copy-btn").first(); await copyBtn.click(); await popup.waitForTimeout(300); await expect(copyBtn).toHaveClass(/is-copied/); }); test("15 - password reveal toggles visibility", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("RevealTest"); await popup.locator('input[placeholder="domain.com"]').fill("https://revealtest.com"); await popup.locator('input[placeholder="username or email"]').fill("reveal@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("secretpassword"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator("text=RevealTest").first().click(); await popup.waitForTimeout(500); await expect(popup.locator(".peach-password-dots")).toBeVisible(); const eyeBtn = popup.locator("button[aria-label='Reveal password']"); await eyeBtn.click(); await popup.waitForTimeout(300); }); test("16 - TOTP countdown ring animates", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("TOTPTest"); await popup.locator('input[placeholder="domain.com"]').fill("https://totptest.com"); await popup.locator('input[placeholder="username or email"]').fill("totp@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("totppass"); await popup.locator('input[placeholder*="totp" i]').fill("JBSWY3DPEHPK3PXP"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator("text=TOTPTest").first().click(); await popup.waitForTimeout(500); const ring = popup.locator(".peach-totp-ring-progress, svg circle").first(); await expect(ring).toBeVisible(); }); test("17 - generator produces different passwords each time", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /password generator/i }).click(); await popup.waitForTimeout(500); const output1 = await popup.locator(".peach-pwgen-output").textContent(); await popup.locator("button[aria-label='Regenerate']").click(); await popup.waitForTimeout(300); const output2 = await popup.locator(".peach-pwgen-output").textContent(); expect(output1).not.toEqual(output2); }); test("18 - generator respects length slider", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /password generator/i }).click(); await popup.waitForTimeout(500); const slider = popup.locator(".peach-pwgen-slider"); await slider.fill("32"); await popup.waitForTimeout(300); const output = await popup.locator(".peach-pwgen-output").textContent(); expect(output?.length).toBeGreaterThanOrEqual(30); }); test("19 - settings accent color swatches update --ac", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); const swatch = popup.locator(".peach-swatch").nth(2); await swatch.click(); await popup.waitForTimeout(300); await expect(swatch).toHaveClass(/is-active/); }); test("20 - clipboard clear after 30 seconds", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("ClipboardTest"); await popup.locator('input[placeholder="domain.com"]').fill("https://clipboardtest.com"); await popup.locator('input[placeholder="username or email"]').fill("clip@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("cleartest"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator("text=ClipboardTest").first().click(); await popup.waitForTimeout(500); await popup.locator(".peach-copy-btn").nth(1).click(); await popup.waitForTimeout(200); const copyBtns = popup.locator(".peach-copy-btn.is-copied"); expect(await copyBtns.count()).toBeGreaterThan(0); }); });