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-kb-${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("Global Keyboard Shortcuts", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBG01 - Enter submits master password", async () => { const { popup: _popup } = await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.locator("#master-password").fill("password123"); await newPage.keyboard.press("Enter"); await newPage.waitForTimeout(2000); const entries = newPage.locator("[class*=entry]"); await expect(entries.first()).toBeVisible({ timeout: 5000 }); await ctx.close(); }); test("KBG02 - Ctrl+Shift+P opens extension popup", async () => { const { popup } = await setupVault(ctx); const isVisible = await popup.isVisible(); expect(isVisible).toBe(true); await ctx.close(); }); test("KBG03 - Escape from lock screen does nothing", async () => { const { popup: _popup } = await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.locator("#master-password").fill("password123"); await newPage.keyboard.press("Escape"); await newPage.waitForTimeout(500); const pw = newPage.locator("#master-password"); const isVisible = await pw.isVisible({ timeout: 2000 }).catch(() => false); expect(isVisible).toBe(true); await ctx.close(); }); test("KBG04 - Ctrl+N opens new entry dialog", async () => { const { popup } = await setupVault(ctx); await popup.keyboard.press("Control+n"); await popup.waitForTimeout(500); const newBody = popup.locator(".peach-new-body"); const isVisible = await newBody.isVisible({ timeout: 3000 }).catch(() => false); expect(isVisible).toBeTruthy(); await ctx.close(); }); test("KBG05 - Ctrl+F focuses search", async () => { const { popup } = await setupVault(ctx); await popup.keyboard.press("Control+f"); await popup.waitForTimeout(200); const focused = await popup.evaluate(() => document.activeElement?.className); expect(focused).toMatch(/search/i); await ctx.close(); }); test("KBG06 - Ctrl+, opens settings", async () => { const { popup } = await setupVault(ctx); await popup.keyboard.press("Control+,"); await popup.waitForTimeout(500); const settingsTitle = popup.locator(".peach-overlay-title, text=settings").first(); const isVisible = await settingsTitle.isVisible({ timeout: 3000 }).catch(() => false); expect(isVisible).toBeTruthy(); await ctx.close(); }); test("KBG07 - Ctrl+L locks vault", async () => { const { popup } = await setupVault(ctx); await popup.keyboard.press("Control+l"); await popup.waitForTimeout(1500); const pw = popup.locator("#master-password"); const isVisible = await pw.isVisible({ timeout: 3000 }).catch(() => false); expect(isVisible).toBeTruthy(); await ctx.close(); }); }); test.describe("Entry Form Keyboard Navigation", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBE01 - Tab moves through add entry form", 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.waitForTimeout(300); const nameInput = popup.locator(".peach-new-name-input"); await nameInput.focus(); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focused = await popup.evaluate(() => document.activeElement?.tagName); expect(focused).toBe("INPUT"); await ctx.close(); }); test("KBE02 - Tab from name goes to 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.waitForTimeout(300); await popup.locator(".peach-new-name-input").focus(); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focusedPlaceholder = await popup.evaluate(() => (document.activeElement as HTMLInputElement)?.placeholder); expect(focusedPlaceholder).toContain("domain"); await ctx.close(); }); test("KBE03 - Tab from domain goes to 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.waitForTimeout(300); await popup.locator(".peach-new-name-input").focus(); await popup.keyboard.press("Tab"); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focusedPlaceholder = await popup.evaluate(() => (document.activeElement as HTMLInputElement)?.placeholder); expect(focusedPlaceholder).toContain("username"); await ctx.close(); }); test("KBE04 - Tab from username goes to 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.waitForTimeout(300); await popup.locator(".peach-new-name-input").focus(); await popup.keyboard.press("Tab"); await popup.keyboard.press("Tab"); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focusedClass = await popup.evaluate(() => document.activeElement?.className); expect(focusedClass).toContain("peach-new-field-input"); await ctx.close(); }); test("KBE05 - Shift+Tab goes backwards", 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.waitForTimeout(300); const pwField = popup.locator(".peach-new-field-input[placeholder='password']"); await pwField.focus(); await popup.keyboard.press("Shift+Tab"); await popup.waitForTimeout(100); const focused = await popup.evaluate(() => document.activeElement?.tagName); expect(focused).toBe("INPUT"); await ctx.close(); }); test("KBE06 - Enter submits add entry form", 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.waitForTimeout(300); await popup.locator(".peach-new-name-input").fill("EnterSubmit"); await popup.locator('input[placeholder="domain.com"]').fill("https://enter.com"); await popup.locator('input[placeholder="username or email"]').fill("enter@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.keyboard.press("Enter"); await popup.waitForTimeout(1500); await expect(popup.locator("text=EnterSubmit")).toBeVisible({ timeout: 3000 }); await ctx.close(); }); test("KBE07 - Escape closes add entry dialog", 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.waitForTimeout(300); await popup.keyboard.press("Escape"); await popup.waitForTimeout(300); const newBody = popup.locator(".peach-new-body"); const isHidden = await newBody.isHidden().catch(() => false); expect(isHidden).toBeTruthy(); await ctx.close(); }); }); test.describe("Vault List Keyboard Navigation", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBV01 - ArrowDown moves selection down", async () => { const { popup } = await setupVault(ctx); for (const name of ["First", "Second", "Third"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator('input[placeholder="search vault..."]').click(); await popup.waitForTimeout(200); await popup.keyboard.press("ArrowDown"); await popup.waitForTimeout(200); const activeRow = popup.locator(".peach-entry-row.is-active"); const hasActive = await activeRow.isVisible({ timeout: 2000 }).catch(() => false); expect(hasActive).toBeTruthy(); await ctx.close(); }); test("KBV02 - ArrowUp moves selection up", async () => { const { popup } = await setupVault(ctx); for (const name of ["RowUp1", "RowUp2"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator('input[placeholder="search vault..."]').click(); await popup.waitForTimeout(200); await popup.keyboard.press("ArrowDown"); await popup.keyboard.press("ArrowDown"); await popup.waitForTimeout(200); await popup.keyboard.press("ArrowUp"); await popup.waitForTimeout(200); const activeRow = popup.locator(".peach-entry-row.is-active"); const hasActive = await activeRow.isVisible({ timeout: 2000 }).catch(() => false); expect(hasActive).toBeTruthy(); await ctx.close(); }); test("KBV03 - Enter opens selected entry", 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("OpenMe"); await popup.locator('input[placeholder="domain.com"]').fill("https://open.com"); await popup.locator('input[placeholder="username or email"]').fill("open@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); await popup.locator('input[placeholder="search vault..."]').click(); await popup.waitForTimeout(200); await popup.keyboard.press("Enter"); await popup.waitForTimeout(500); const usernameField = popup.locator(".peach-field-value").filter({ hasText: "open@user.com" }).first(); await expect(usernameField).toBeVisible({ timeout: 3000 }); await ctx.close(); }); test("KBV04 - Home key goes to first entry", async () => { const { popup } = await setupVault(ctx); for (const name of ["AlphaHome", "BetaHome"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator('input[placeholder="search vault..."]').click(); await popup.keyboard.press("End"); await popup.waitForTimeout(200); await popup.keyboard.press("Home"); await popup.waitForTimeout(200); await ctx.close(); }); test("KBV05 - End key goes to last entry", async () => { const { popup } = await setupVault(ctx); for (const name of ["FirstEnd", "SecondEnd", "ThirdEnd"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator('input[placeholder="search vault..."]').click(); await popup.keyboard.press("End"); await popup.waitForTimeout(200); await ctx.close(); }); test("KBV06 - Delete key triggers delete confirmation", 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("DeleteKey"); await popup.locator('input[placeholder="domain.com"]').fill("https://delkey.com"); await popup.locator('input[placeholder="username or email"]').fill("del@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); await ctx.close(); }); test("KBV07 - Ctrl+A selects all in search", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.locator('input[placeholder="search vault..."]').fill("test"); await popup.waitForTimeout(100); await popup.keyboard.press("Control+a"); await popup.waitForTimeout(100); const selected = await popup.evaluate(() => { const input = document.activeElement as HTMLInputElement; return input?.selectionStart === 0 && input?.selectionEnd === input?.value?.length; }); expect(selected).toBeTruthy(); await ctx.close(); }); }); test.describe("Settings Panel Keyboard", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBS01 - Tab navigates settings options", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focused = await popup.evaluate(() => document.activeElement?.tagName); expect(focused).toBeTruthy(); await ctx.close(); }); test("KBS02 - Arrow keys navigate setting options", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.locator("text=auto-lock").click(); await popup.waitForTimeout(300); await popup.keyboard.press("ArrowDown"); await popup.waitForTimeout(200); await popup.keyboard.press("Escape"); await ctx.close(); }); test("KBS03 - Escape closes settings panel", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); await popup.keyboard.press("Escape"); await popup.waitForTimeout(300); const overlay = popup.locator(".peach-overlay-view"); const isHidden = await overlay.isHidden().catch(() => true); expect(isHidden).toBeTruthy(); await ctx.close(); }); test("KBS04 - Enter activates focused option", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(500); const lockVault = popup.locator("text=lock vault").first(); if (await lockVault.isVisible({ timeout: 2000 }).catch(() => false)) { await lockVault.focus(); await popup.keyboard.press("Enter"); await popup.waitForTimeout(1000); const pwField = popup.locator("#master-password"); const isVisible = await pwField.isVisible({ timeout: 3000 }).catch(() => false); expect(isVisible).toBeTruthy(); } await ctx.close(); }); test("KBS05 - Space activates toggle switches", 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 on close").click(); await popup.waitForTimeout(300); const toggle = popup.locator("[role='switch' i]").first(); if (await toggle.isVisible({ timeout: 2000 }).catch(() => false)) { await toggle.focus(); await popup.keyboard.press(" "); await popup.waitForTimeout(200); } await ctx.close(); }); }); test.describe("Password Generator Keyboard", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBG01 - Tab navigates generator options", 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); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focused = await popup.evaluate(() => document.activeElement?.tagName); expect(focused).toBeTruthy(); await ctx.close(); }); test("KBG02 - Arrow keys adjust 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.focus(); await popup.keyboard.press("ArrowRight"); await popup.waitForTimeout(100); await popup.keyboard.press("ArrowLeft"); await popup.waitForTimeout(100); await ctx.close(); }); test("KBG03 - Space toggles option switches", 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 toggleRow = popup.locator(".peach-pwgen-toggle-row").first(); if (await toggleRow.isVisible({ timeout: 2000 }).catch(() => false)) { await toggleRow.focus(); await popup.keyboard.press(" "); await popup.waitForTimeout(200); } await ctx.close(); }); test("KBG04 - Escape closes generator", 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); await popup.keyboard.press("Escape"); await popup.waitForTimeout(300); const output = popup.locator(".peach-pwgen-output"); await output.isHidden().catch(() => true); await ctx.close(); }); test("KBG05 - Enter copies generated password", 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 copyBtn = popup.getByRole("button", { name: /copy/i }).first(); if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) { await copyBtn.focus(); await popup.keyboard.press("Enter"); await popup.waitForTimeout(300); } await ctx.close(); }); }); test.describe("Lock Screen Keyboard", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBL01 - Tab navigates lock screen", async () => { const { popup: _popup } = await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.keyboard.press("Tab"); await newPage.waitForTimeout(100); const focused = await newPage.evaluate(() => document.activeElement?.tagName); expect(focused).toBeTruthy(); await ctx.close(); }); test("KBL02 - Ctrl+Enter submits unlock", async () => { await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.locator("#master-password").fill("password123"); await newPage.keyboard.press("Control+Enter"); await newPage.waitForTimeout(2000); const entries = newPage.locator("[class*=entry]"); await expect(entries.first()).toBeVisible({ timeout: 5000 }); await ctx.close(); }); test("KBL03 - Password field accepts paste", async () => { await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.locator("#master-password").fill("password123"); await newPage.keyboard.press("Control+a"); await newPage.keyboard.press("Control+c"); await newPage.locator("#master-password").fill(""); await newPage.locator("#master-password").focus(); await newPage.keyboard.press("Control+v"); await newPage.waitForTimeout(200); const value = await newPage.locator("#master-password").inputValue(); expect(value).toBe("password123"); await ctx.close(); }); test("KBL04 - Show password toggle via click", async () => { const { popup: _popup } = await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); const showBtn = newPage.locator("[aria-label*='show' i], [aria-label*='reveal' i]").first(); if (await showBtn.isVisible({ timeout: 2000 }).catch(() => false)) { await showBtn.click(); await newPage.waitForTimeout(200); const inputType = await newPage.locator("#master-password").getAttribute("type"); expect(inputType).toBe("text"); } await ctx.close(); }); test("KBL05 - Shift+Tab navigates backwards on lock", async () => { const { popup: _popup } = await setupVault(ctx); const newPage = await ctx.newPage(); const extId = await waitForSW(ctx); await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`); await newPage.waitForTimeout(2000); await newPage.keyboard.press("Shift+Tab"); await newPage.waitForTimeout(100); const focused = await newPage.evaluate(() => document.activeElement?.tagName); expect(focused).toBeTruthy(); await ctx.close(); }); }); test.describe("Edit Entry Keyboard", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBD01 - Tab navigates edit form fields", 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("EditKB"); await popup.locator('input[placeholder="domain.com"]').fill("https://editkb.com"); await popup.locator('input[placeholder="username or email"]').fill("editkb@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); await popup.locator(".peach-entry-name-button").first().click(); await popup.waitForTimeout(500); await popup.locator("button[aria-label='Edit']").first().click(); await popup.waitForTimeout(500); await popup.keyboard.press("Tab"); await popup.waitForTimeout(100); const focused = await popup.evaluate(() => document.activeElement?.tagName); expect(focused).toBe("INPUT"); await ctx.close(); }); test("KBD02 - Enter saves edit", 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("EditSaveKB"); await popup.locator('input[placeholder="domain.com"]').fill("https://editsavekb.com"); await popup.locator('input[placeholder="username or email"]').fill("es@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); await popup.locator(".peach-entry-name-button").first().click(); await popup.waitForTimeout(500); await popup.locator("button[aria-label='Edit']").first().click(); await popup.waitForTimeout(500); await popup.locator(".peach-new-name-input").fill("EditSaveKBChanged"); await popup.keyboard.press("Enter"); await popup.waitForTimeout(1500); await expect(popup.locator("text=EditSaveKBChanged")).toBeVisible({ timeout: 3000 }); await ctx.close(); }); test("KBD03 - Escape cancels edit", 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("CancelEditKB"); await popup.locator('input[placeholder="domain.com"]').fill("https://cancelkb.com"); await popup.locator('input[placeholder="username or email"]').fill("cancelkb@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); await popup.locator(".peach-entry-name-button").first().click(); await popup.waitForTimeout(500); await popup.locator("button[aria-label='Edit']").first().click(); await popup.waitForTimeout(500); await popup.locator(".peach-new-name-input").fill("ShouldNotChange"); await popup.keyboard.press("Escape"); await popup.waitForTimeout(300); await expect(popup.locator("text=CancelEditKB")).toBeVisible({ timeout: 3000 }); await ctx.close(); }); }); test.describe("Search Field Keyboard", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("KBS01 - Typing filters results immediately", async () => { const { popup } = await setupVault(ctx); for (const name of ["FilterTest1", "FilterTest2", "OtherEntry"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first(); await searchInput.click(); await searchInput.fill("Filter"); await popup.waitForTimeout(300); await expect(popup.locator("text=FilterTest1")).toBeVisible(); await expect(popup.locator("text=OtherEntry")).not.toBeVisible(); await ctx.close(); }); test("KBS02 - Backspace reduces search query", 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("BackspaceTest"); await popup.locator('input[placeholder="domain.com"]').fill("https://back.com"); await popup.locator('input[placeholder="username or email"]').fill("back@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 searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first(); await searchInput.click(); await searchInput.fill("BackspaceTest"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.keyboard.press("Backspace"); await popup.waitForTimeout(200); await ctx.close(); }); test("KBS03 - Clear search shows all entries", async () => { const { popup } = await setupVault(ctx); for (const name of ["ShowAll1", "ShowAll2"]) { 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(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first(); await searchInput.click(); await searchInput.fill("ShowAll1"); await popup.waitForTimeout(300); await searchInput.fill(""); await popup.waitForTimeout(300); await expect(popup.locator("text=ShowAll1")).toBeVisible(); await expect(popup.locator("text=ShowAll2")).toBeVisible(); await ctx.close(); }); test("KBS04 - Search with no results shows empty 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("SingleEntry"); await popup.locator('input[placeholder="domain.com"]').fill("https://single.com"); await popup.locator('input[placeholder="username or email"]').fill("single@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 searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first(); await searchInput.click(); await searchInput.fill("NothingMatches"); await popup.waitForTimeout(500); const empty = popup.locator("text=no entries, text=no results, text=0 results"); const hasEmpty = await empty.isVisible({ timeout: 2000 }).catch(() => false); expect(hasEmpty).toBeTruthy(); await ctx.close(); }); test("KBS05 - Search preserves cursor position", 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("CursorPreserve"); await popup.locator('input[placeholder="domain.com"]').fill("https://cursor.com"); await popup.locator('input[placeholder="username or email"]').fill("cursor@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 searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first(); await searchInput.click(); await searchInput.fill("Cursor"); await popup.keyboard.press("ArrowLeft"); await popup.keyboard.press("ArrowLeft"); await popup.waitForTimeout(100); await popup.keyboard.press("X"); await popup.waitForTimeout(100); const value = await searchInput.inputValue(); expect(value).toBe("CurXor"); await ctx.close(); }); });