1import { test, expect, Page, BrowserContext } from "@playwright/test";2import { UNLOCKED, VAULT_PASSWORD, addEntry, makeContext, setupVault } from "./helpers/vault";345test.describe.configure({ timeout: 90_000 });6789async 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});