1import { test, expect, Page, BrowserContext } from "@playwright/test";2import { UNLOCKED, VAULT_PASSWORD, addEntry, listSearch, makeContext, openList, setupVault } from "./helpers/vault";34567const topSearch = (popup: Page) => popup.locator(".peach-search-wrap .peach-search-input");8910test.describe.configure({ timeout: 90_000 });11121314async 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});