search.spec.ts

10.8 KB · raw

1import { test, expect, Page, BrowserContext } from "@playwright/test";2import { UNLOCKED, VAULT_PASSWORD, addEntry, makeContext, setupVault } from "./helpers/vault";34// Real vault creation runs Argon2id; seeded entries need a popup reload each.5test.describe.configure({ timeout: 90_000 });67// Entries are added through the background, which the open popup does not8// observe — reload so the list re-reads the vault.9async function createEntry(popup: Page, name: string, domain = "test.com") {10  await addEntry(popup, {11    title: name,12    url: `https://${domain}`,13    username: `${name.toLowerCase()}@${domain}`,14    password: "Passphrase123!",15  });16  await popup.reload();17  const unlockInput = popup.locator("#unlock-password");18  if (await unlockInput.isVisible({ timeout: 2000 }).catch(() => false)) {19    await unlockInput.fill(VAULT_PASSWORD);20    await popup.getByRole("button", { name: /unlock/i }).click();21  }22  await expect(popup.locator(UNLOCKED)).toBeVisible({ timeout: 20000 });23}2425test.describe("Search", () => {26  let ctx: BrowserContext;27  test.afterEach(async () => { await ctx.close(); });28  test.beforeEach(async () => { ctx = await makeContext(); });2930  test("S01 - search input is visible", async () => {31    const { popup } = await setupVault(ctx);32    const searchInput = popup.locator('input[placeholder="search vault..."]');33    await expect(searchInput).toBeVisible();34    await ctx.close();35  });3637  test("S02 - search input has placeholder text", async () => {38    const { popup } = await setupVault(ctx);39    const searchInput = popup.locator('input[placeholder="search vault..."]');40    const placeholder = await searchInput.getAttribute("placeholder");41    expect(placeholder).toContain("search");42    await ctx.close();43  });4445  test("S03 - search filters entries by name", async () => {46    const { popup } = await setupVault(ctx);47    await createEntry(popup, "GoogleEntry");48    await createEntry(popup, "AmazonEntry");49    await popup.locator('input[placeholder="search vault..."]').fill("Google");50    await popup.waitForTimeout(300);51    const entries = popup.locator(".peach-entry-row");52    const count = await entries.count();53    expect(count).toBe(1);54    await ctx.close();55  });5657  test("S04 - search is case-insensitive", async () => {58    const { popup } = await setupVault(ctx);59    await createEntry(popup, "TestEntry");60    await popup.locator('input[placeholder="search vault..."]').fill("test");61    await popup.waitForTimeout(300);62    const entries = popup.locator(".peach-entry-row");63    const count = await entries.count();64    expect(count).toBe(1);65    await ctx.close();66  });6768  test("S05 - search with no results shows empty state", async () => {69    const { popup } = await setupVault(ctx);70    await createEntry(popup, "Entry1");71    await popup.locator('input[placeholder="search vault..."]').fill("nonexistent");72    await popup.waitForTimeout(500);73    const noResults = popup.locator("text=/no results|not found|0 results/i");74    const hasNoResults = await noResults.isVisible({ timeout: 2000 }).catch(() => false);75    expect(hasNoResults).toBeTruthy();76    await ctx.close();77  });7879  test("S06 - clearing search shows all entries", async () => {80    const { popup } = await setupVault(ctx);81    await createEntry(popup, "EntryOne");82    await createEntry(popup, "EntryTwo");83    await popup.locator('input[placeholder="search vault..."]').fill("EntryOne");84    await popup.waitForTimeout(300);85    await popup.locator('input[placeholder="search vault..."]').clear();86    await popup.waitForTimeout(300);87    const entries = popup.locator(".peach-entry-row");88    const count = await entries.count();89    expect(count).toBe(2);90    await ctx.close();91  });9293  test("S07 - search by domain works", async () => {94    const { popup } = await setupVault(ctx);95    await createEntry(popup, "Site1", "google.com");96    await createEntry(popup, "Site2", "amazon.com");97    await popup.locator('input[placeholder="search vault..."]').fill("google");98    await popup.waitForTimeout(300);99    const entries = popup.locator(".peach-entry-row");100    const count = await entries.count();101    expect(count).toBe(1);102    await ctx.close();103  });104105  test("S08 - search by username works", async () => {106    const { popup } = await setupVault(ctx);107    await createEntry(popup, "User1");108    await createEntry(popup, "User2");109    await popup.locator('input[placeholder="search vault..."]').fill("[email protected]");110    await popup.waitForTimeout(300);111    const entries = popup.locator(".peach-entry-row");112    const count = await entries.count();113    expect(count).toBe(1);114    await ctx.close();115  });116117  test("S09 - search preserves scroll position", async () => {118    const { popup } = await setupVault(ctx);119    for (let i = 0; i < 15; i++) {120      await createEntry(popup, `Entry${i}`, `site${i}.com`);121    }122    await popup.locator('input[placeholder="search vault..."]').fill("Entry5");123    await popup.waitForTimeout(300);124    await popup.locator('input[placeholder="search vault..."]').clear();125    await popup.waitForTimeout(300);126    await ctx.close();127  });128129  test("S10 - search with special characters", async () => {130    const { popup } = await setupVault(ctx);131    await createEntry(popup, "Test@Entry");132    await popup.locator('input[placeholder="search vault..."]').fill("@");133    await popup.waitForTimeout(300);134    await ctx.close();135  });136137  test("S11 - search with regex characters", async () => {138    const { popup } = await setupVault(ctx);139    await createEntry(popup, "Test.Entry");140    await popup.locator('input[placeholder="search vault..."]').fill(".");141    await popup.waitForTimeout(300);142    await ctx.close();143  });144145  test("S12 - search input has proper aria-label", async () => {146    const { popup } = await setupVault(ctx);147    const searchInput = popup.locator('input[placeholder="search vault..."]');148    const ariaLabel = await searchInput.getAttribute("aria-label");149    expect(ariaLabel).toBeTruthy();150    await ctx.close();151  });152153  test("S13 - search clears on escape", async () => {154    const { popup } = await setupVault(ctx);155    await createEntry(popup, "TestEntry");156    await popup.locator('input[placeholder="search vault..."]').fill("Test");157    await popup.waitForTimeout(300);158    await popup.keyboard.press("Escape");159    await popup.waitForTimeout(200);160    const value = await popup.locator('input[placeholder="search vault..."]').inputValue();161    expect(value).toBe("");162    await ctx.close();163  });164165  test("S14 - search debounce works", async () => {166    const { popup } = await setupVault(ctx);167    await createEntry(popup, "TestEntry");168    const searchInput = popup.locator('input[placeholder="search vault..."]');169    await searchInput.fill("T");170    await popup.waitForTimeout(100);171    await searchInput.fill("Te");172    await popup.waitForTimeout(100);173    await searchInput.fill("Tes");174    await popup.waitForTimeout(300);175    await ctx.close();176  });177178  test("S15 - search highlights matching text", async () => {179    const { popup } = await setupVault(ctx);180    await createEntry(popup, "TestingEntry");181    await popup.locator('input[placeholder="search vault..."]').fill("Test");182    await popup.waitForTimeout(300);183    const highlighted = popup.locator("[class*='highlight' i], [class*='match' i]").first();184    const hasHighlight = await highlighted.isVisible().catch(() => false);185    if (hasHighlight) expect(true).toBeTruthy();186    await ctx.close();187  });188189  test("S16 - empty search shows all entries", async () => {190    const { popup } = await setupVault(ctx);191    await createEntry(popup, "Entry1");192    await createEntry(popup, "Entry2");193    await popup.locator('input[placeholder="search vault..."]').fill("");194    await popup.waitForTimeout(300);195    const entries = popup.locator(".peach-entry-row");196    const count = await entries.count();197    expect(count).toBe(2);198    await ctx.close();199  });200201  test("S17 - search accessible via keyboard", async () => {202    const { popup } = await setupVault(ctx);203    await createEntry(popup, "TestEntry");204    await popup.keyboard.press("/");205    await popup.waitForTimeout(200);206    const overlaySearch = popup.locator(".peach-overlay-view .peach-search-input");207    await expect(overlaySearch).toBeVisible();208    const focused = await overlaySearch.evaluate((el) => document.activeElement === el);209    expect(focused).toBeTruthy();210    await ctx.close();211  });212213  test("S18 - partial match works", async () => {214    const { popup } = await setupVault(ctx);215    await createEntry(popup, "MyPasswordEntry");216    await popup.locator('input[placeholder="search vault..."]').fill("Pass");217    await popup.waitForTimeout(300);218    const entries = popup.locator(".peach-entry-row");219    const count = await entries.count();220    expect(count).toBe(1);221    await ctx.close();222  });223});224225test.describe("Search - Advanced", () => {226  let ctx: BrowserContext;227  test.afterEach(async () => { await ctx.close(); });228  test.beforeEach(async () => { ctx = await makeContext(); });229230  test("SA01 - search with spaces works", async () => {231    const { popup } = await setupVault(ctx);232    await createEntry(popup, "My Test Entry");233    await popup.locator('input[placeholder="search vault..."]').fill("Test");234    await popup.waitForTimeout(300);235    const entries = popup.locator(".peach-entry-row");236    const count = await entries.count();237    expect(count).toBe(1);238    await ctx.close();239  });240241  test("SA02 - search remembers previous query", async () => {242    const { popup } = await setupVault(ctx);243    await createEntry(popup, "Test1");244    await popup.locator('input[placeholder="search vault..."]').fill("Test1");245    await popup.waitForTimeout(300);246    await popup.locator(".peach-entry-name-button").first().click();247    await popup.waitForTimeout(300);248    await popup.keyboard.press("Escape");249    await popup.waitForTimeout(200);250    const value = await popup.locator('input[placeholder="search vault..."]').inputValue();251    expect(value).toBe("Test1");252    await ctx.close();253  });254255  test("SA03 - search field does not accept invalid characters", async () => {256    const { popup } = await setupVault(ctx);257    await createEntry(popup, "Test");258    const searchInput = popup.locator('input[placeholder="search vault..."]');259    await searchInput.fill("Test\r\n");260    await ctx.close();261  });262263  test("SA04 - search with very long query", async () => {264    const { popup } = await setupVault(ctx);265    await createEntry(popup, "Short");266    const longQuery = "a".repeat(100);267    await popup.locator('input[placeholder="search vault..."]').fill(longQuery);268    await popup.waitForTimeout(300);269    await ctx.close();270  });271272  test("SA05 - search in non-latin characters", async () => {273    const { popup } = await setupVault(ctx);274    await createEntry(popup, "Тест");275    await popup.locator('input[placeholder="search vault..."]').fill("Тест");276    await popup.waitForTimeout(300);277    await ctx.close();278  });279});