import { test, expect, 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-entry-${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 waitForSW(ctx: BrowserContext, maxWait = 15000): Promise { 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`); return new URL(sw.url()).host; } async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> { const extId = await waitForSW(ctx); const popup = await ctx.newPage(); await popup.goto(`chrome-extension://${extId}/src/popup/index.html`); await popup.waitForTimeout(3000); 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("Entry Field Validation", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("EF01 - entry requires name", 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('input[placeholder="domain.com"]').fill("https://noname.com"); await popup.locator('input[placeholder="username or email"]').fill("user@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(500); const nameInput = popup.locator(".peach-new-name-input"); const value = await nameInput.inputValue(); expect(value).toBe(""); await ctx.close(); }); test("EF02 - entry allows empty domain", 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("NoDomain"); await popup.locator('input[placeholder="domain.com"]').fill(""); await popup.locator('input[placeholder="username or email"]').fill("user@test.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await expect(popup.locator("text=NoDomain")).toBeVisible({ timeout: 3000 }); await ctx.close(); }); test("EF03 - entry allows empty username", 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("NoUsername"); await popup.locator('input[placeholder="domain.com"]').fill("https://nouser.com"); await popup.locator('input[placeholder="username or email"]').fill(""); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await expect(popup.locator("text=NoUsername")).toBeVisible({ timeout: 3000 }); await ctx.close(); }); test("EF04 - entry requires password", 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("NoPass"); await popup.locator('input[placeholder="domain.com"]').fill("https://nopass.com"); await popup.locator('input[placeholder="username or email"]').fill("user@test.com"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(500); const passField = popup.locator(".peach-new-field-input[placeholder='password']"); const value = await passField.inputValue(); expect(value).toBe(""); await ctx.close(); }); test("EF05 - entry truncates very long names", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); const longName = "A".repeat(500); await popup.locator(".peach-new-name-input").fill(longName); await popup.locator('input[placeholder="domain.com"]').fill("https://long.com"); await popup.locator('input[placeholder="username or email"]').fill("long@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); const nameInList = popup.locator(".peach-entry-name").first(); const text = await nameInList.textContent(); expect(text?.length).toBeLessThanOrEqual(200); await ctx.close(); }); }); test.describe("Notes Field", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("EF06 - notes field accepts multiline text", 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("MultiLine"); await popup.locator('input[placeholder="domain.com"]').fill("https://multi.com"); await popup.locator('input[placeholder="username or email"]').fill("multi@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); const notes = popup.locator("textarea"); await notes.fill("Line 1\nLine 2\nLine 3"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator(".peach-entry-name-button").first().click(); await popup.waitForTimeout(500); const noteContent = popup.locator(".peach-note-content"); await expect(noteContent).toBeVisible({ timeout: 3000 }); const text = await noteContent.textContent(); expect(text).toContain("Line 1"); expect(text).toContain("Line 2"); await ctx.close(); }); test("EF07 - notes field accepts special characters", 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("SpecialNotes"); await popup.locator('input[placeholder="domain.com"]').fill("https://special.com"); await popup.locator('input[placeholder="username or email"]').fill("special@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); const notes = popup.locator("textarea"); await notes.fill('&"\'<>@#$%^&*()_+-=[]{}|;:,./<>?'); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator(".peach-entry-name-button").first().click(); await popup.waitForTimeout(500); const noteContent = popup.locator(".peach-note-content"); const html = await noteContent.innerHTML(); expect(html).not.toContain("