search.spec.ts

10.1 KB · raw

1import { test, expect, Page, BrowserContext } from "@playwright/test";2import { UNLOCKED, VAULT_PASSWORD, addEntry, listSearch, makeContext, openList, setupVault } from "./helpers/vault";34// The top bar's search field is readOnly — it only opens the list overlay,5// where the editable field lives. Specs that filled the top field were6// filling a readOnly input.7const topSearch = (popup: Page) => popup.locator(".peach-search-wrap .peach-search-input");89// Real vault creation runs Argon2id; seeded entries need a popup reload each.10test.describe.configure({ timeout: 90_000 });1112// Entries are added through the background, which the open popup does not13// observe — reload so the list re-reads the vault.14async function createEntry(popup: Page, name: string, domain = "test.com") {15  await addEntry(popup, {16    title: name,17    url: `https://${domain}`,18    username: `${name.toLowerCase()}@${domain}`,19    password: "Passphrase123!",20  });21  await popup.reload();22  const unlockInput = popup.locator("#unlock-password");23  if (await unlockInput.isVisible({ timeout: 2000 }).catch(() => false)) {24    await unlockInput.fill(VAULT_PASSWORD);25    await popup.getByRole("button", { name: /unlock/i }).click();26  }27  await expect(popup.locator(UNLOCKED)).toBeVisible({ timeout: 20000 });28  await openList(popup);29}3031test.describe("Search", () => {32  let ctx: BrowserContext;33  test.afterEach(async () => { await ctx.close(); });34  test.beforeEach(async () => { ctx = await makeContext(); });3536  test("S01 - search input is visible", async () => {37    const { popup } = await setupVault(ctx);38    await expect(topSearch(popup)).toBeVisible();39    await ctx.close();40  });4142  test("S02 - search input has placeholder text", async () => {43    const { popup } = await setupVault(ctx);44    const placeholder = await topSearch(popup).getAttribute("placeholder");45    expect(placeholder).toContain("search");46    await ctx.close();47  });4849  test("S03 - search filters entries by name", async () => {50    const { popup } = await setupVault(ctx);51    await createEntry(popup, "GoogleEntry");52    await createEntry(popup, "AmazonEntry");53    await listSearch(popup).fill("Google");54    await popup.waitForTimeout(300);55    const entries = popup.locator(".peach-entry-row");56    const count = await entries.count();57    expect(count).toBe(1);58    await ctx.close();59  });6061  test("S04 - search is case-insensitive", async () => {62    const { popup } = await setupVault(ctx);63    await createEntry(popup, "TestEntry");64    await listSearch(popup).fill("test");65    await popup.waitForTimeout(300);66    const entries = popup.locator(".peach-entry-row");67    const count = await entries.count();68    expect(count).toBe(1);69    await ctx.close();70  });7172  test("S05 - search with no results shows empty state", async () => {73    const { popup } = await setupVault(ctx);74    await createEntry(popup, "Entry1");75    await listSearch(popup).fill("nonexistent");76    await popup.waitForTimeout(500);77    const noResults = popup.locator("text=/no results|not found|0 results/i");78    const hasNoResults = await noResults.isVisible({ timeout: 2000 }).catch(() => false);79    expect(hasNoResults).toBeTruthy();80    await ctx.close();81  });8283  test("S06 - clearing search shows all entries", async () => {84    const { popup } = await setupVault(ctx);85    await createEntry(popup, "EntryOne");86    await createEntry(popup, "EntryTwo");87    await listSearch(popup).fill("EntryOne");88    await popup.waitForTimeout(300);89    await listSearch(popup).clear();90    await popup.waitForTimeout(300);91    const entries = popup.locator(".peach-entry-row");92    const count = await entries.count();93    expect(count).toBe(2);94    await ctx.close();95  });9697  test("S07 - search by domain works", async () => {98    const { popup } = await setupVault(ctx);99    await createEntry(popup, "Site1", "google.com");100    await createEntry(popup, "Site2", "amazon.com");101    await listSearch(popup).fill("google");102    await popup.waitForTimeout(300);103    const entries = popup.locator(".peach-entry-row");104    const count = await entries.count();105    expect(count).toBe(1);106    await ctx.close();107  });108109  test("S08 - search by username works", async () => {110    const { popup } = await setupVault(ctx);111    await createEntry(popup, "User1");112    await createEntry(popup, "User2");113    await listSearch(popup).fill("[email protected]");114    await popup.waitForTimeout(300);115    const entries = popup.locator(".peach-entry-row");116    const count = await entries.count();117    expect(count).toBe(1);118    await ctx.close();119  });120121  test("S09 - search preserves scroll position", async () => {122    const { popup } = await setupVault(ctx);123    for (let i = 0; i < 15; i++) {124      await createEntry(popup, `Entry${i}`, `site${i}.com`);125    }126    await listSearch(popup).fill("Entry5");127    await popup.waitForTimeout(300);128    await listSearch(popup).clear();129    await popup.waitForTimeout(300);130    await ctx.close();131  });132133  test("S10 - search with special characters", async () => {134    const { popup } = await setupVault(ctx);135    await createEntry(popup, "Test@Entry");136    await listSearch(popup).fill("@");137    await popup.waitForTimeout(300);138    await ctx.close();139  });140141  test("S11 - search with regex characters", async () => {142    const { popup } = await setupVault(ctx);143    await createEntry(popup, "Test.Entry");144    await listSearch(popup).fill(".");145    await popup.waitForTimeout(300);146    await ctx.close();147  });148149  test("S12 - search input has proper aria-label", async () => {150    const { popup } = await setupVault(ctx);151    const ariaLabel = await topSearch(popup).getAttribute("aria-label");152    expect(ariaLabel).toBeTruthy();153    await ctx.close();154  });155156  test("S13 - search clears on escape", async () => {157    const { popup } = await setupVault(ctx);158    await createEntry(popup, "TestEntry");159    await listSearch(popup).fill("Test");160    await popup.waitForTimeout(300);161    await popup.keyboard.press("Escape");162    await popup.waitForTimeout(200);163    const value = await listSearch(popup).inputValue();164    expect(value).toBe("");165    await ctx.close();166  });167168  test("S14 - search debounce works", async () => {169    const { popup } = await setupVault(ctx);170    await createEntry(popup, "TestEntry");171    const searchInput = listSearch(popup);172    await searchInput.fill("T");173    await popup.waitForTimeout(100);174    await searchInput.fill("Te");175    await popup.waitForTimeout(100);176    await searchInput.fill("Tes");177    await popup.waitForTimeout(300);178    await ctx.close();179  });180181  test("S15 - search highlights matching text", async () => {182    const { popup } = await setupVault(ctx);183    await createEntry(popup, "TestingEntry");184    await listSearch(popup).fill("Test");185    await popup.waitForTimeout(300);186    const highlighted = popup.locator("[class*='highlight' i], [class*='match' i]").first();187    const hasHighlight = await highlighted.isVisible().catch(() => false);188    if (hasHighlight) expect(true).toBeTruthy();189    await ctx.close();190  });191192  test("S16 - empty search shows all entries", async () => {193    const { popup } = await setupVault(ctx);194    await createEntry(popup, "Entry1");195    await createEntry(popup, "Entry2");196    await listSearch(popup).fill("");197    await popup.waitForTimeout(300);198    const entries = popup.locator(".peach-entry-row");199    const count = await entries.count();200    expect(count).toBe(2);201    await ctx.close();202  });203204  test("S17 - search accessible via keyboard", async () => {205    const { popup } = await setupVault(ctx);206    await createEntry(popup, "TestEntry");207    await popup.keyboard.press("/");208    await popup.waitForTimeout(200);209    const overlaySearch = popup.locator(".peach-overlay-view .peach-search-input");210    await expect(overlaySearch).toBeVisible();211    const focused = await overlaySearch.evaluate((el) => document.activeElement === el);212    expect(focused).toBeTruthy();213    await ctx.close();214  });215216  test("S18 - partial match works", async () => {217    const { popup } = await setupVault(ctx);218    await createEntry(popup, "MyPasswordEntry");219    await listSearch(popup).fill("Pass");220    await popup.waitForTimeout(300);221    const entries = popup.locator(".peach-entry-row");222    const count = await entries.count();223    expect(count).toBe(1);224    await ctx.close();225  });226});227228test.describe("Search - Advanced", () => {229  let ctx: BrowserContext;230  test.afterEach(async () => { await ctx.close(); });231  test.beforeEach(async () => { ctx = await makeContext(); });232233  test("SA01 - search with spaces works", async () => {234    const { popup } = await setupVault(ctx);235    await createEntry(popup, "My Test Entry");236    await listSearch(popup).fill("Test");237    await popup.waitForTimeout(300);238    const entries = popup.locator(".peach-entry-row");239    const count = await entries.count();240    expect(count).toBe(1);241    await ctx.close();242  });243244  test("SA02 - search remembers previous query", async () => {245    const { popup } = await setupVault(ctx);246    await createEntry(popup, "Test1");247    await listSearch(popup).fill("Test1");248    await popup.waitForTimeout(300);249    await popup.locator(".peach-entry-name-button").first().click();250    await popup.waitForTimeout(300);251    await popup.keyboard.press("Escape");252    await popup.waitForTimeout(200);253    const value = await listSearch(popup).inputValue();254    expect(value).toBe("Test1");255    await ctx.close();256  });257258  test("SA03 - search field does not accept invalid characters", async () => {259    const { popup } = await setupVault(ctx);260    await createEntry(popup, "Test");261    const searchInput = listSearch(popup);262    await searchInput.fill("Test\r\n");263    await ctx.close();264  });265266  test("SA04 - search with very long query", async () => {267    const { popup } = await setupVault(ctx);268    await createEntry(popup, "Short");269    const longQuery = "a".repeat(100);270    await listSearch(popup).fill(longQuery);271    await popup.waitForTimeout(300);272    await ctx.close();273  });274275  test("SA05 - search in non-latin characters", async () => {276    const { popup } = await setupVault(ctx);277    await createEntry(popup, "Тест");278    await listSearch(popup).fill("Тест");279    await popup.waitForTimeout(300);280    await ctx.close();281  });282});