import { test, expect, BrowserContext } from "@playwright/test"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import os from "os"; import fs from "fs/promises"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const EXTENSION_PATH = join(__dirname, "..", "dist"); async function makeContext() { const { chromium } = await import("playwright"); const userDataDir = join(os.tmpdir(), `peach-imp-${Date.now()}`); await fs.mkdir(userDataDir, { recursive: true }); return chromium.launchPersistentContext(userDataDir, { headless: false, args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`], }); } async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise { const start = Date.now(); let sw = ctx.serviceWorkers()[0]; while (!sw && Date.now() - start < maxWait) { await new Promise(r => setTimeout(r, 500)); sw = ctx.serviceWorkers()[0]; } if (!sw) throw new Error(`No SW after ${maxWait}ms`); return new URL(sw.url()).host; } async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> { const extId = await waitForSW(ctx); const popup = await ctx.newPage(); await popup.goto(`chrome-extension://${extId}/src/popup/index.html`); await popup.waitForTimeout(3000); const pw = popup.locator("#master-password"); if (await pw.isVisible()) { await pw.fill("password123"); await popup.locator("#confirm-password").fill("password123"); await popup.getByRole("button", { name: /create vault/i }).click(); await popup.waitForTimeout(2000); } return { popup, extId }; } test.describe("CSV Import", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("IMP01 - CSV import accepts standard format", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "Google,user@gmail.com,pass123,https://google.com", "Amazon,user@amazon.com,pass456,https://amazon.com" ].join("\n"); await fileInput.setInputFiles({ name: "import.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await popup.keyboard.press("Escape"); await popup.waitForTimeout(300); const entries = popup.locator(".peach-entry-row"); const count = await entries.count(); expect(count).toBeGreaterThan(0); await ctx.close(); }); test("IMP02 - CSV import with different column order", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "password,title,url,username", "secret1,Site1,https://site1.com,user1@site.com", "secret2,Site2,https://site2.com,user2@site.com" ].join("\n"); await fileInput.setInputFiles({ name: "import2.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP03 - CSV import with BOM header", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = "\ufefftitle,username,password,url\nTest,user@test.com,pass,https://test.com"; await fileInput.setInputFiles({ name: "import-bom.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP04 - CSV import with quotes in fields", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "\"Test, User\",\"user@test.com\",\"pass,\"\"123\"\"\",https://test.com" ].join("\n"); await fileInput.setInputFiles({ name: "import-quotes.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP05 - CSV import with empty fields", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "EmptyFields,,,https://empty.com", ",user@test.com,," ].join("\n"); await fileInput.setInputFiles({ name: "import-empty.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP06 - CSV import with newlines in fields", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "\"Multi\nLine\",\"user@test.com\",\"pass\",\"https://test.com\"" ].join("\n"); await fileInput.setInputFiles({ name: "import-newline.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP07 - CSV import with special characters", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "日本語,用户@例子.测试,密码,https://test.com", "Emoji🎉,emoji@user.com,🔐🔑,https://emoji.com" ].join("\n"); await fileInput.setInputFiles({ name: "import-unicode.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP08 - CSV import with 100+ entries", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const rows = ["title,username,password,url"]; for (let i = 0; i < 100; i++) { rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`); } const csvContent = rows.join("\n"); await fileInput.setInputFiles({ name: "import-large.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(3000); await ctx.close(); }); test("IMP09 - CSV import with only required fields", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,password", "MinimalImport,secretpass" ].join("\n"); await fileInput.setInputFiles({ name: "import-minimal.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IMP10 - CSV import shows progress", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const rows = ["title,username,password,url"]; for (let i = 0; i < 20; i++) { rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`); } const csvContent = rows.join("\n"); await fileInput.setInputFiles({ name: "import-progress.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(2000); const importStatus = popup.locator("text=importing, text=imported, text=entries"); const hasStatus = await importStatus.isVisible({ timeout: 3000 }).catch(() => false); expect(hasStatus).toBeTruthy(); await ctx.close(); }); }); test.describe("JSON Export", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("EXP01 - export creates valid JSON", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("ExportJSON"); await popup.locator('input[placeholder="domain.com"]').fill("https://export.com"); await popup.locator('input[placeholder="username or email"]').fill("export@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("exportpass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); const downloadBtn = popup.getByRole("button", { name: /download backup/i }); await downloadBtn.click(); await popup.waitForTimeout(2000); const result = popup.locator("text=exported"); const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false); expect(hasResult).toBeTruthy(); await ctx.close(); }); test("EXP02 - export includes all entries", async () => { const { popup } = await setupVault(ctx); for (const name of ["Export1", "Export2", "Export3"]) { await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); const result = popup.locator("text=3 entries").first(); const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false); expect(hasResult).toBeTruthy(); await ctx.close(); }); test("EXP03 - export with TOTP entries includes TOTP", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("ExportTOTP"); await popup.locator('input[placeholder="domain.com"]').fill("https://exporttotp.com"); await popup.locator('input[placeholder="username or email"]').fill("totp@user.com"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); await ctx.close(); }); test("EXP04 - export requires password", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const downloadBtn = popup.getByRole("button", { name: /download backup/i }); await downloadBtn.click(); await popup.waitForTimeout(500); const pwInput = popup.locator(".form-input[type='password']"); const hasError = await pwInput.isVisible({ timeout: 2000 }).catch(() => false); expect(hasError).toBeTruthy(); await ctx.close(); }); test("EXP05 - export with short password shows warning", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("short"); const downloadBtn = popup.getByRole("button", { name: /download backup/i }); const isDisabled = await downloadBtn.isDisabled(); expect(isDisabled).toBeTruthy(); await ctx.close(); }); test("EXP06 - export empty vault works", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); const result = popup.locator("text=0 entries").first(); const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false); expect(hasResult).toBeTruthy(); await ctx.close(); }); }); test.describe("JSON Import", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("JIP01 - JSON import accepts Peach export format", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const jsonContent = JSON.stringify({ version: 1, type: "peach-export", kdf: "PBKDF2-SHA256", entries: [ { title: "JSONImport", username: "user@test.com", password: "pass123", url: "https://test.com" } ] }); await fileInput.setInputFiles({ name: "peach-export.json", mimeType: "application/json", buffer: Buffer.from(jsonContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP02 - JSON import with 1Password format", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const json1pContent = JSON.stringify({ accounts: [{ vaults: [{ items: [{ overview: { title: "1P Import", url: "https://1p.com" }, details: { loginFields: [ { designation: "username", value: "testuser" }, { designation: "password", value: "testpass" } ] } }] }] }] }); await fileInput.setInputFiles({ name: "1p-export.json", mimeType: "application/json", buffer: Buffer.from(json1pContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP03 - JSON import with Bitwarden format", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const jsonBWContent = JSON.stringify({ items: [ { name: "BW Import", login: { username: "bwuser", password: "bwpass", uris: [{ uri: "https://bw.com" }] } } ] }); await fileInput.setInputFiles({ name: "bw-export.json", mimeType: "application/json", buffer: Buffer.from(jsonBWContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP04 - JSON import with nested structure", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const jsonContent = JSON.stringify({ data: { folders: [], items: [ { id: "1", name: "Nested", login: { username: "nested@test.com", password: "pass123" } } ] } }); await fileInput.setInputFiles({ name: "nested.json", mimeType: "application/json", buffer: Buffer.from(jsonContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP05 - JSON import with TOTP secrets", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const jsonContent = JSON.stringify({ items: [ { name: "TOTPImport", login: { username: "totp@test.com", password: "pass123", totp: "JBSWY3DPEHPK3PXP" } } ] }); await fileInput.setInputFiles({ name: "totp-import.json", mimeType: "application/json", buffer: Buffer.from(jsonContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP06 - JSON import with custom fields", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const jsonContent = JSON.stringify({ items: [ { name: "CustomFields", login: { username: "custom@test.com", password: "pass123" }, extra: { notes: "some notes", favorite: true } } ] }); await fileInput.setInputFiles({ name: "custom-fields.json", mimeType: "application/json", buffer: Buffer.from(jsonContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("JIP07 - JSON import validates file format", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); await fileInput.setInputFiles({ name: "invalid.json", mimeType: "application/json", buffer: Buffer.from("not valid json {") }); await popup.waitForTimeout(500); const error = popup.locator("text=error, text=invalid, text=parse"); const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false); expect(hasError).toBeTruthy(); await ctx.close(); }); }); test.describe("Import/Export Edge Cases", () => { let ctx: BrowserContext; test.afterEach(async () => { await ctx.close(); }); test.beforeEach(async () => { ctx = await makeContext(); }); test("IEC01 - import file larger than 10MB shows error", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const largeContent = "x".repeat(11 * 1024 * 1024); await fileInput.setInputFiles({ name: "large.csv", mimeType: "text/csv", buffer: Buffer.from(largeContent) }); await popup.waitForTimeout(500); const error = popup.locator("text=too large, text=file size, text=max"); const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false); expect(hasError).toBeTruthy(); await ctx.close(); }); test("IEC02 - export to existing file prompts overwrite", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); await ctx.close(); }); test("IEC03 - import with duplicate entries merges", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = [ "title,username,password,url", "Duplicate,user@test.com,pass,https://dup.com", "Duplicate,user@test.com,pass,https://dup.com" ].join("\n"); await fileInput.setInputFiles({ name: "duplicate.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); const entries = popup.locator(".peach-entry-row"); const count = await entries.count(); expect(count).toBeLessThanOrEqual(2); await ctx.close(); }); test("IEC04 - export preserves entry order", async () => { const { popup } = await setupVault(ctx); const order = ["First", "Second", "Third"]; for (const name of order) { await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill(name); await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`); await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`); await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(800); } await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); await ctx.close(); }); test("IEC05 - import with missing password field", async () => { const { popup } = await setupVault(ctx); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /import passwords/i }).click(); await popup.waitForTimeout(500); const fileInput = popup.locator('input[type="file"]'); const csvContent = "title,username\nNoPassword,user@test.com"; await fileInput.setInputFiles({ name: "no-password.csv", mimeType: "text/csv", buffer: Buffer.from(csvContent) }); await popup.waitForTimeout(1500); await ctx.close(); }); test("IEC06 - export with unicode entries", async () => { const { popup } = await setupVault(ctx); await popup.locator('input[placeholder="search vault..."]').click(); await popup.getByRole("button", { name: /new entry/i }).click(); await popup.locator(".peach-new-name-input").fill("日本語エントリー"); await popup.locator('input[placeholder="domain.com"]').fill("https://日本語.com"); await popup.locator('input[placeholder="username or email"]').fill("ユーザー@例子.测试"); await popup.locator(".peach-new-field-input[placeholder='password']").fill("密码🔐"); await popup.getByRole("button", { name: /create/i }).click(); await popup.waitForTimeout(1500); await popup.locator(".peach-icon-btn[aria-label='Settings']").click(); await popup.waitForTimeout(300); await popup.getByRole("button", { name: /export backup/i }).click(); await popup.waitForTimeout(500); const exportPw = popup.locator(".form-input[type='password']"); await exportPw.fill("exportpassword"); await popup.getByRole("button", { name: /download backup/i }).click(); await popup.waitForTimeout(2000); const result = popup.locator("text=1 entries").first(); const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false); expect(hasResult).toBeTruthy(); await ctx.close(); }); });