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 23 24 await popup.waitForSelector(`#unlock-password, ${UNLOCKED}`, { timeout: 30000 });25 const unlockInput = popup.locator("#unlock-password");26 if (await unlockInput.isVisible().catch(() => false)) {27 await unlockInput.fill(VAULT_PASSWORD);28 await popup.getByRole("button", { name: /unlock/i }).click();29 }30 await expect(popup.locator(UNLOCKED)).toBeVisible({ timeout: 30000 });31 await openList(popup);32}3334test.describe("Search", () => {35 let ctx: BrowserContext;36 test.afterEach(async () => { await ctx.close(); });37 test.beforeEach(async () => { ctx = await makeContext(); });3839 test("S01 - search input is visible", async () => {40 const { popup } = await setupVault(ctx);41 await expect(topSearch(popup)).toBeVisible();42 await ctx.close();43 });4445 test("S02 - search input has placeholder text", async () => {46 const { popup } = await setupVault(ctx);47 const placeholder = await topSearch(popup).getAttribute("placeholder");48 expect(placeholder).toContain("search");49 await ctx.close();50 });5152 test("S03 - search filters entries by name", async () => {53 const { popup } = await setupVault(ctx);54 await createEntry(popup, "GoogleEntry");55 await createEntry(popup, "AmazonEntry");56 await listSearch(popup).fill("Google");57 await popup.waitForTimeout(300);58 const entries = popup.locator(".peach-entry-row");59 const count = await entries.count();60 expect(count).toBe(1);61 await ctx.close();62 });6364 test("S04 - search is case-insensitive", async () => {65 const { popup } = await setupVault(ctx);66 await createEntry(popup, "TestEntry");67 await listSearch(popup).fill("test");68 await popup.waitForTimeout(300);69 const entries = popup.locator(".peach-entry-row");70 const count = await entries.count();71 expect(count).toBe(1);72 await ctx.close();73 });7475 test("S05 - search with no results shows empty state", async () => {76 const { popup } = await setupVault(ctx);77 await createEntry(popup, "Entry1");78 await listSearch(popup).fill("nonexistent");79 80 81 await expect(popup.locator(".peach-list-empty")).toBeVisible({ timeout: 5000 });82 await expect(popup.locator(".peach-entry-row")).toHaveCount(0);83 await ctx.close();84 });8586 test("S06 - clearing search shows all entries", async () => {87 const { popup } = await setupVault(ctx);88 await createEntry(popup, "EntryOne");89 await createEntry(popup, "EntryTwo");90 await listSearch(popup).fill("EntryOne");91 await popup.waitForTimeout(300);92 await listSearch(popup).clear();93 await popup.waitForTimeout(300);94 const entries = popup.locator(".peach-entry-row");95 const count = await entries.count();96 expect(count).toBe(2);97 await ctx.close();98 });99100 test("S07 - search by domain works", async () => {101 const { popup } = await setupVault(ctx);102 await createEntry(popup, "Site1", "google.com");103 await createEntry(popup, "Site2", "amazon.com");104 await listSearch(popup).fill("google");105 await popup.waitForTimeout(300);106 const entries = popup.locator(".peach-entry-row");107 const count = await entries.count();108 expect(count).toBe(1);109 await ctx.close();110 });111112 test("S08 - search by username works", async () => {113 const { popup } = await setupVault(ctx);114 await createEntry(popup, "User1");115 await createEntry(popup, "User2");116 await listSearch(popup).fill("[email protected]");117 await popup.waitForTimeout(300);118 const entries = popup.locator(".peach-entry-row");119 const count = await entries.count();120 expect(count).toBe(1);121 await ctx.close();122 });123124 test("S09 - search preserves scroll position", async () => {125 const { popup } = await setupVault(ctx);126 for (let i = 0; i < 15; i++) {127 await createEntry(popup, `Entry${i}`, `site${i}.com`);128 }129 await listSearch(popup).fill("Entry5");130 await popup.waitForTimeout(300);131 await listSearch(popup).clear();132 await popup.waitForTimeout(300);133 await ctx.close();134 });135136 test("S10 - search with special characters", async () => {137 const { popup } = await setupVault(ctx);138 await createEntry(popup, "Test@Entry");139 await listSearch(popup).fill("@");140 await popup.waitForTimeout(300);141 await ctx.close();142 });143144 test("S11 - search with regex characters", async () => {145 const { popup } = await setupVault(ctx);146 await createEntry(popup, "Test.Entry");147 await listSearch(popup).fill(".");148 await popup.waitForTimeout(300);149 await ctx.close();150 });151152 test("S12 - search input has proper aria-label", async () => {153 const { popup } = await setupVault(ctx);154 const ariaLabel = await topSearch(popup).getAttribute("aria-label");155 expect(ariaLabel).toBeTruthy();156 await ctx.close();157 });158159 test("S13 - escape closes the list overlay", async () => {160 const { popup } = await setupVault(ctx);161 await createEntry(popup, "TestEntry");162 await listSearch(popup).fill("Test");163 await popup.keyboard.press("Escape");164 165 await expect(listSearch(popup)).toHaveCount(0, { timeout: 5000 });166 await ctx.close();167 });168169 test("S14 - search debounce works", async () => {170 const { popup } = await setupVault(ctx);171 await createEntry(popup, "TestEntry");172 const searchInput = listSearch(popup);173 await searchInput.fill("T");174 await popup.waitForTimeout(100);175 await searchInput.fill("Te");176 await popup.waitForTimeout(100);177 await searchInput.fill("Tes");178 await popup.waitForTimeout(300);179 await ctx.close();180 });181182 test("S15 - search highlights matching text", async () => {183 const { popup } = await setupVault(ctx);184 await createEntry(popup, "TestingEntry");185 await listSearch(popup).fill("Test");186 await popup.waitForTimeout(300);187 const highlighted = popup.locator("[class*='highlight' i], [class*='match' i]").first();188 const hasHighlight = await highlighted.isVisible().catch(() => false);189 if (hasHighlight) expect(true).toBeTruthy();190 await ctx.close();191 });192193 test("S16 - empty search shows all entries", async () => {194 const { popup } = await setupVault(ctx);195 await createEntry(popup, "Entry1");196 await createEntry(popup, "Entry2");197 await listSearch(popup).fill("");198 await popup.waitForTimeout(300);199 const entries = popup.locator(".peach-entry-row");200 const count = await entries.count();201 expect(count).toBe(2);202 await ctx.close();203 });204205 test("S17 - search accessible via keyboard", async () => {206 const { popup } = await setupVault(ctx);207 await createEntry(popup, "TestEntry");208 await popup.keyboard.press("/");209 await popup.waitForTimeout(200);210 const overlaySearch = popup.locator(".peach-overlay-view .peach-search-input");211 await expect(overlaySearch).toBeVisible();212 const focused = await overlaySearch.evaluate((el) => document.activeElement === el);213 expect(focused).toBeTruthy();214 await ctx.close();215 });216217 test("S18 - partial match works", async () => {218 const { popup } = await setupVault(ctx);219 await createEntry(popup, "MyPasswordEntry");220 await listSearch(popup).fill("Pass");221 await popup.waitForTimeout(300);222 const entries = popup.locator(".peach-entry-row");223 const count = await entries.count();224 expect(count).toBe(1);225 await ctx.close();226 });227});228229test.describe("Search - Advanced", () => {230 let ctx: BrowserContext;231 test.afterEach(async () => { await ctx.close(); });232 test.beforeEach(async () => { ctx = await makeContext(); });233234 test("SA01 - search with spaces works", async () => {235 const { popup } = await setupVault(ctx);236 await createEntry(popup, "My Test Entry");237 await listSearch(popup).fill("Test");238 await popup.waitForTimeout(300);239 const entries = popup.locator(".peach-entry-row");240 const count = await entries.count();241 expect(count).toBe(1);242 await ctx.close();243 });244245 246 247 test("SA02 - reopening the list starts from an empty query", async () => {248 const { popup } = await setupVault(ctx);249 await createEntry(popup, "Test1");250 await listSearch(popup).fill("Test1");251 await popup.locator(".peach-entry-row").first().click();252 await expect(listSearch(popup)).toHaveCount(0, { timeout: 5000 });253 await openList(popup);254 await expect(listSearch(popup)).toHaveValue("");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});