1import { test, expect, BrowserContext } from "@playwright/test";2import { fileURLToPath } from "url";3import { dirname, join } from "path";4import os from "os";5import fs from "fs/promises";67const __filename = fileURLToPath(import.meta.url);8const __dirname = dirname(__filename);9const EXTENSION_PATH = join(__dirname, "..", "dist");1011async function makeContext() {12 const { chromium } = await import("playwright");13 const userDataDir = join(os.tmpdir(), `peach-imp-${Date.now()}`);14 await fs.mkdir(userDataDir, { recursive: true });15 return chromium.launchPersistentContext(userDataDir, {16 headless: false,17 args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18 });19}2021async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22 const start = Date.now();23 let sw = ctx.serviceWorkers()[0];24 while (!sw && Date.now() - start < maxWait) {25 await new Promise(r => setTimeout(r, 500));26 sw = ctx.serviceWorkers()[0];27 }28 if (!sw) throw new Error(`No SW after ${maxWait}ms`);29 return new URL(sw.url()).host;30}3132async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33 const extId = await waitForSW(ctx);34 const popup = await ctx.newPage();35 await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36 await popup.waitForTimeout(3000);37 const pw = popup.locator("#master-password");38 if (await pw.isVisible()) {39 await pw.fill("password123");40 await popup.locator("#confirm-password").fill("password123");41 await popup.getByRole("button", { name: /create vault/i }).click();42 await popup.waitForTimeout(2000);43 }44 return { popup, extId };45}4647test.describe("CSV Import", () => {48 let ctx: BrowserContext;49 test.afterEach(async () => { await ctx.close(); });50 test.beforeEach(async () => { ctx = await makeContext(); });5152 test("IMP01 - CSV import accepts standard format", async () => {53 const { popup } = await setupVault(ctx);54 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();55 await popup.waitForTimeout(300);56 await popup.getByRole("button", { name: /import passwords/i }).click();57 await popup.waitForTimeout(500);58 const fileInput = popup.locator('input[type="file"]');59 const csvContent = [60 "title,username,password,url",61 "Google,[email protected],pass123,https://google.com",62 "Amazon,[email protected],pass456,https://amazon.com"63 ].join("\n");64 await fileInput.setInputFiles({65 name: "import.csv",66 mimeType: "text/csv",67 buffer: Buffer.from(csvContent)68 });69 await popup.waitForTimeout(1500);70 await popup.keyboard.press("Escape");71 await popup.waitForTimeout(300);72 const entries = popup.locator(".peach-entry-row");73 const count = await entries.count();74 expect(count).toBeGreaterThan(0);75 await ctx.close();76 });7778 test("IMP02 - CSV import with different column order", async () => {79 const { popup } = await setupVault(ctx);80 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();81 await popup.waitForTimeout(300);82 await popup.getByRole("button", { name: /import passwords/i }).click();83 await popup.waitForTimeout(500);84 const fileInput = popup.locator('input[type="file"]');85 const csvContent = [86 "password,title,url,username",87 "secret1,Site1,https://site1.com,[email protected]",88 "secret2,Site2,https://site2.com,[email protected]"89 ].join("\n");90 await fileInput.setInputFiles({91 name: "import2.csv",92 mimeType: "text/csv",93 buffer: Buffer.from(csvContent)94 });95 await popup.waitForTimeout(1500);96 await ctx.close();97 });9899 test("IMP03 - CSV import with BOM header", async () => {100 const { popup } = await setupVault(ctx);101 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();102 await popup.waitForTimeout(300);103 await popup.getByRole("button", { name: /import passwords/i }).click();104 await popup.waitForTimeout(500);105 const fileInput = popup.locator('input[type="file"]');106 const csvContent = "\ufefftitle,username,password,url\nTest,[email protected],pass,https://test.com";107 await fileInput.setInputFiles({108 name: "import-bom.csv",109 mimeType: "text/csv",110 buffer: Buffer.from(csvContent)111 });112 await popup.waitForTimeout(1500);113 await ctx.close();114 });115116 test("IMP04 - CSV import with quotes in fields", async () => {117 const { popup } = await setupVault(ctx);118 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();119 await popup.waitForTimeout(300);120 await popup.getByRole("button", { name: /import passwords/i }).click();121 await popup.waitForTimeout(500);122 const fileInput = popup.locator('input[type="file"]');123 const csvContent = [124 "title,username,password,url",125 "\"Test, User\",\"[email protected]\",\"pass,\"\"123\"\"\",https://test.com"126 ].join("\n");127 await fileInput.setInputFiles({128 name: "import-quotes.csv",129 mimeType: "text/csv",130 buffer: Buffer.from(csvContent)131 });132 await popup.waitForTimeout(1500);133 await ctx.close();134 });135136 test("IMP05 - CSV import with empty fields", async () => {137 const { popup } = await setupVault(ctx);138 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();139 await popup.waitForTimeout(300);140 await popup.getByRole("button", { name: /import passwords/i }).click();141 await popup.waitForTimeout(500);142 const fileInput = popup.locator('input[type="file"]');143 const csvContent = [144 "title,username,password,url",145 "EmptyFields,,,https://empty.com",146 ",[email protected],,"147 ].join("\n");148 await fileInput.setInputFiles({149 name: "import-empty.csv",150 mimeType: "text/csv",151 buffer: Buffer.from(csvContent)152 });153 await popup.waitForTimeout(1500);154 await ctx.close();155 });156157 test("IMP06 - CSV import with newlines in fields", async () => {158 const { popup } = await setupVault(ctx);159 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();160 await popup.waitForTimeout(300);161 await popup.getByRole("button", { name: /import passwords/i }).click();162 await popup.waitForTimeout(500);163 const fileInput = popup.locator('input[type="file"]');164 const csvContent = [165 "title,username,password,url",166 "\"Multi\nLine\",\"[email protected]\",\"pass\",\"https://test.com\""167 ].join("\n");168 await fileInput.setInputFiles({169 name: "import-newline.csv",170 mimeType: "text/csv",171 buffer: Buffer.from(csvContent)172 });173 await popup.waitForTimeout(1500);174 await ctx.close();175 });176177 test("IMP07 - CSV import with special characters", async () => {178 const { popup } = await setupVault(ctx);179 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();180 await popup.waitForTimeout(300);181 await popup.getByRole("button", { name: /import passwords/i }).click();182 await popup.waitForTimeout(500);183 const fileInput = popup.locator('input[type="file"]');184 const csvContent = [185 "title,username,password,url",186 "日本語,用户@例子.测试,密码,https://test.com",187 "Emoji🎉,[email protected],🔐🔑,https://emoji.com"188 ].join("\n");189 await fileInput.setInputFiles({190 name: "import-unicode.csv",191 mimeType: "text/csv",192 buffer: Buffer.from(csvContent)193 });194 await popup.waitForTimeout(1500);195 await ctx.close();196 });197198 test("IMP08 - CSV import with 100+ entries", async () => {199 const { popup } = await setupVault(ctx);200 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();201 await popup.waitForTimeout(300);202 await popup.getByRole("button", { name: /import passwords/i }).click();203 await popup.waitForTimeout(500);204 const fileInput = popup.locator('input[type="file"]');205 const rows = ["title,username,password,url"];206 for (let i = 0; i < 100; i++) {207 rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`);208 }209 const csvContent = rows.join("\n");210 await fileInput.setInputFiles({211 name: "import-large.csv",212 mimeType: "text/csv",213 buffer: Buffer.from(csvContent)214 });215 await popup.waitForTimeout(3000);216 await ctx.close();217 });218219 test("IMP09 - CSV import with only required fields", async () => {220 const { popup } = await setupVault(ctx);221 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();222 await popup.waitForTimeout(300);223 await popup.getByRole("button", { name: /import passwords/i }).click();224 await popup.waitForTimeout(500);225 const fileInput = popup.locator('input[type="file"]');226 const csvContent = [227 "title,password",228 "MinimalImport,secretpass"229 ].join("\n");230 await fileInput.setInputFiles({231 name: "import-minimal.csv",232 mimeType: "text/csv",233 buffer: Buffer.from(csvContent)234 });235 await popup.waitForTimeout(1500);236 await ctx.close();237 });238239 test("IMP10 - CSV import shows progress", async () => {240 const { popup } = await setupVault(ctx);241 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();242 await popup.waitForTimeout(300);243 await popup.getByRole("button", { name: /import passwords/i }).click();244 await popup.waitForTimeout(500);245 const fileInput = popup.locator('input[type="file"]');246 const rows = ["title,username,password,url"];247 for (let i = 0; i < 20; i++) {248 rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`);249 }250 const csvContent = rows.join("\n");251 await fileInput.setInputFiles({252 name: "import-progress.csv",253 mimeType: "text/csv",254 buffer: Buffer.from(csvContent)255 });256 await popup.waitForTimeout(2000);257 const importStatus = popup.locator("text=importing, text=imported, text=entries");258 const hasStatus = await importStatus.isVisible({ timeout: 3000 }).catch(() => false);259 expect(hasStatus).toBeTruthy();260 await ctx.close();261 });262});263264test.describe("JSON Export", () => {265 let ctx: BrowserContext;266 test.afterEach(async () => { await ctx.close(); });267 test.beforeEach(async () => { ctx = await makeContext(); });268269 test("EXP01 - export creates valid JSON", async () => {270 const { popup } = await setupVault(ctx);271 await popup.locator('input[placeholder="search vault..."]').click();272 await popup.getByRole("button", { name: /new entry/i }).click();273 await popup.locator(".peach-new-name-input").fill("ExportJSON");274 await popup.locator('input[placeholder="domain.com"]').fill("https://export.com");275 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");276 await popup.locator(".peach-new-field-input[placeholder='password']").fill("exportpass");277 await popup.getByRole("button", { name: /create/i }).click();278 await popup.waitForTimeout(1500);279 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();280 await popup.waitForTimeout(300);281 await popup.getByRole("button", { name: /export backup/i }).click();282 await popup.waitForTimeout(500);283 const exportPw = popup.locator(".form-input[type='password']");284 await exportPw.fill("exportpassword");285 const downloadBtn = popup.getByRole("button", { name: /download backup/i });286 await downloadBtn.click();287 await popup.waitForTimeout(2000);288 const result = popup.locator("text=exported");289 const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);290 expect(hasResult).toBeTruthy();291 await ctx.close();292 });293294 test("EXP02 - export includes all entries", async () => {295 const { popup } = await setupVault(ctx);296 for (const name of ["Export1", "Export2", "Export3"]) {297 await popup.locator('input[placeholder="search vault..."]').click();298 await popup.getByRole("button", { name: /new entry/i }).click();299 await popup.locator(".peach-new-name-input").fill(name);300 await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);301 await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);302 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");303 await popup.getByRole("button", { name: /create/i }).click();304 await popup.waitForTimeout(800);305 }306 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();307 await popup.waitForTimeout(300);308 await popup.getByRole("button", { name: /export backup/i }).click();309 await popup.waitForTimeout(500);310 const exportPw = popup.locator(".form-input[type='password']");311 await exportPw.fill("exportpassword");312 await popup.getByRole("button", { name: /download backup/i }).click();313 await popup.waitForTimeout(2000);314 const result = popup.locator("text=3 entries").first();315 const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);316 expect(hasResult).toBeTruthy();317 await ctx.close();318 });319320 test("EXP03 - export with TOTP entries includes TOTP", async () => {321 const { popup } = await setupVault(ctx);322 await popup.locator('input[placeholder="search vault..."]').click();323 await popup.getByRole("button", { name: /new entry/i }).click();324 await popup.locator(".peach-new-name-input").fill("ExportTOTP");325 await popup.locator('input[placeholder="domain.com"]').fill("https://exporttotp.com");326 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");327 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");328 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");329 await popup.getByRole("button", { name: /create/i }).click();330 await popup.waitForTimeout(1500);331 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();332 await popup.waitForTimeout(300);333 await popup.getByRole("button", { name: /export backup/i }).click();334 await popup.waitForTimeout(500);335 const exportPw = popup.locator(".form-input[type='password']");336 await exportPw.fill("exportpassword");337 await popup.getByRole("button", { name: /download backup/i }).click();338 await popup.waitForTimeout(2000);339 await ctx.close();340 });341342 test("EXP04 - export requires password", async () => {343 const { popup } = await setupVault(ctx);344 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();345 await popup.waitForTimeout(300);346 await popup.getByRole("button", { name: /export backup/i }).click();347 await popup.waitForTimeout(500);348 const downloadBtn = popup.getByRole("button", { name: /download backup/i });349 await downloadBtn.click();350 await popup.waitForTimeout(500);351 const pwInput = popup.locator(".form-input[type='password']");352 const hasError = await pwInput.isVisible({ timeout: 2000 }).catch(() => false);353 expect(hasError).toBeTruthy();354 await ctx.close();355 });356357 test("EXP05 - export with short password shows warning", async () => {358 const { popup } = await setupVault(ctx);359 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();360 await popup.waitForTimeout(300);361 await popup.getByRole("button", { name: /export backup/i }).click();362 await popup.waitForTimeout(500);363 const exportPw = popup.locator(".form-input[type='password']");364 await exportPw.fill("short");365 const downloadBtn = popup.getByRole("button", { name: /download backup/i });366 const isDisabled = await downloadBtn.isDisabled();367 expect(isDisabled).toBeTruthy();368 await ctx.close();369 });370371 test("EXP06 - export empty vault works", async () => {372 const { popup } = await setupVault(ctx);373 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();374 await popup.waitForTimeout(300);375 await popup.getByRole("button", { name: /export backup/i }).click();376 await popup.waitForTimeout(500);377 const exportPw = popup.locator(".form-input[type='password']");378 await exportPw.fill("exportpassword");379 await popup.getByRole("button", { name: /download backup/i }).click();380 await popup.waitForTimeout(2000);381 const result = popup.locator("text=0 entries").first();382 const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);383 expect(hasResult).toBeTruthy();384 await ctx.close();385 });386});387388test.describe("JSON Import", () => {389 let ctx: BrowserContext;390 test.afterEach(async () => { await ctx.close(); });391 test.beforeEach(async () => { ctx = await makeContext(); });392393 test("JIP01 - JSON import accepts Peach export format", async () => {394 const { popup } = await setupVault(ctx);395 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();396 await popup.waitForTimeout(300);397 await popup.getByRole("button", { name: /import passwords/i }).click();398 await popup.waitForTimeout(500);399 const fileInput = popup.locator('input[type="file"]');400 const jsonContent = JSON.stringify({401 version: 1,402 type: "peach-export",403 kdf: "PBKDF2-SHA256",404 entries: [405 { title: "JSONImport", username: "[email protected]", password: "pass123", url: "https://test.com" }406 ]407 });408 await fileInput.setInputFiles({409 name: "peach-export.json",410 mimeType: "application/json",411 buffer: Buffer.from(jsonContent)412 });413 await popup.waitForTimeout(1500);414 await ctx.close();415 });416417 test("JIP02 - JSON import with 1Password format", async () => {418 const { popup } = await setupVault(ctx);419 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();420 await popup.waitForTimeout(300);421 await popup.getByRole("button", { name: /import passwords/i }).click();422 await popup.waitForTimeout(500);423 const fileInput = popup.locator('input[type="file"]');424 const json1pContent = JSON.stringify({425 accounts: [{426 vaults: [{427 items: [{428 overview: { title: "1P Import", url: "https://1p.com" },429 details: {430 loginFields: [431 { designation: "username", value: "testuser" },432 { designation: "password", value: "testpass" }433 ]434 }435 }]436 }]437 }]438 });439 await fileInput.setInputFiles({440 name: "1p-export.json",441 mimeType: "application/json",442 buffer: Buffer.from(json1pContent)443 });444 await popup.waitForTimeout(1500);445 await ctx.close();446 });447448 test("JIP03 - JSON import with Bitwarden format", async () => {449 const { popup } = await setupVault(ctx);450 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();451 await popup.waitForTimeout(300);452 await popup.getByRole("button", { name: /import passwords/i }).click();453 await popup.waitForTimeout(500);454 const fileInput = popup.locator('input[type="file"]');455 const jsonBWContent = JSON.stringify({456 items: [457 { name: "BW Import", login: { username: "bwuser", password: "bwpass", uris: [{ uri: "https://bw.com" }] } }458 ]459 });460 await fileInput.setInputFiles({461 name: "bw-export.json",462 mimeType: "application/json",463 buffer: Buffer.from(jsonBWContent)464 });465 await popup.waitForTimeout(1500);466 await ctx.close();467 });468469 test("JIP04 - JSON import with nested structure", async () => {470 const { popup } = await setupVault(ctx);471 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();472 await popup.waitForTimeout(300);473 await popup.getByRole("button", { name: /import passwords/i }).click();474 await popup.waitForTimeout(500);475 const fileInput = popup.locator('input[type="file"]');476 const jsonContent = JSON.stringify({477 data: {478 folders: [],479 items: [480 { id: "1", name: "Nested", login: { username: "[email protected]", password: "pass123" } }481 ]482 }483 });484 await fileInput.setInputFiles({485 name: "nested.json",486 mimeType: "application/json",487 buffer: Buffer.from(jsonContent)488 });489 await popup.waitForTimeout(1500);490 await ctx.close();491 });492493 test("JIP05 - JSON import with TOTP secrets", async () => {494 const { popup } = await setupVault(ctx);495 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();496 await popup.waitForTimeout(300);497 await popup.getByRole("button", { name: /import passwords/i }).click();498 await popup.waitForTimeout(500);499 const fileInput = popup.locator('input[type="file"]');500 const jsonContent = JSON.stringify({501 items: [502 { name: "TOTPImport", login: { username: "[email protected]", password: "pass123", totp: "JBSWY3DPEHPK3PXP" } }503 ]504 });505 await fileInput.setInputFiles({506 name: "totp-import.json",507 mimeType: "application/json",508 buffer: Buffer.from(jsonContent)509 });510 await popup.waitForTimeout(1500);511 await ctx.close();512 });513514 test("JIP06 - JSON import with custom fields", async () => {515 const { popup } = await setupVault(ctx);516 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();517 await popup.waitForTimeout(300);518 await popup.getByRole("button", { name: /import passwords/i }).click();519 await popup.waitForTimeout(500);520 const fileInput = popup.locator('input[type="file"]');521 const jsonContent = JSON.stringify({522 items: [523 {524 name: "CustomFields",525 login: { username: "[email protected]", password: "pass123" },526 extra: { notes: "some notes", favorite: true }527 }528 ]529 });530 await fileInput.setInputFiles({531 name: "custom-fields.json",532 mimeType: "application/json",533 buffer: Buffer.from(jsonContent)534 });535 await popup.waitForTimeout(1500);536 await ctx.close();537 });538539 test("JIP07 - JSON import validates file format", async () => {540 const { popup } = await setupVault(ctx);541 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();542 await popup.waitForTimeout(300);543 await popup.getByRole("button", { name: /import passwords/i }).click();544 await popup.waitForTimeout(500);545 const fileInput = popup.locator('input[type="file"]');546 await fileInput.setInputFiles({547 name: "invalid.json",548 mimeType: "application/json",549 buffer: Buffer.from("not valid json {")550 });551 await popup.waitForTimeout(500);552 const error = popup.locator("text=error, text=invalid, text=parse");553 const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false);554 expect(hasError).toBeTruthy();555 await ctx.close();556 });557});558559test.describe("Import/Export Edge Cases", () => {560 let ctx: BrowserContext;561 test.afterEach(async () => { await ctx.close(); });562 test.beforeEach(async () => { ctx = await makeContext(); });563564 test("IEC01 - import file larger than 10MB shows error", async () => {565 const { popup } = await setupVault(ctx);566 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();567 await popup.waitForTimeout(300);568 await popup.getByRole("button", { name: /import passwords/i }).click();569 await popup.waitForTimeout(500);570 const fileInput = popup.locator('input[type="file"]');571 const largeContent = "x".repeat(11 * 1024 * 1024);572 await fileInput.setInputFiles({573 name: "large.csv",574 mimeType: "text/csv",575 buffer: Buffer.from(largeContent)576 });577 await popup.waitForTimeout(500);578 const error = popup.locator("text=too large, text=file size, text=max");579 const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false);580 expect(hasError).toBeTruthy();581 await ctx.close();582 });583584 test("IEC02 - export to existing file prompts overwrite", async () => {585 const { popup } = await setupVault(ctx);586 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();587 await popup.waitForTimeout(300);588 await popup.getByRole("button", { name: /export backup/i }).click();589 await popup.waitForTimeout(500);590 const exportPw = popup.locator(".form-input[type='password']");591 await exportPw.fill("exportpassword");592 await popup.getByRole("button", { name: /download backup/i }).click();593 await popup.waitForTimeout(2000);594 await ctx.close();595 });596597 test("IEC03 - import with duplicate entries merges", async () => {598 const { popup } = await setupVault(ctx);599 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();600 await popup.waitForTimeout(300);601 await popup.getByRole("button", { name: /import passwords/i }).click();602 await popup.waitForTimeout(500);603 const fileInput = popup.locator('input[type="file"]');604 const csvContent = [605 "title,username,password,url",606 "Duplicate,[email protected],pass,https://dup.com",607 "Duplicate,[email protected],pass,https://dup.com"608 ].join("\n");609 await fileInput.setInputFiles({610 name: "duplicate.csv",611 mimeType: "text/csv",612 buffer: Buffer.from(csvContent)613 });614 await popup.waitForTimeout(1500);615 const entries = popup.locator(".peach-entry-row");616 const count = await entries.count();617 expect(count).toBeLessThanOrEqual(2);618 await ctx.close();619 });620621 test("IEC04 - export preserves entry order", async () => {622 const { popup } = await setupVault(ctx);623 const order = ["First", "Second", "Third"];624 for (const name of order) {625 await popup.locator('input[placeholder="search vault..."]').click();626 await popup.getByRole("button", { name: /new entry/i }).click();627 await popup.locator(".peach-new-name-input").fill(name);628 await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);629 await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);630 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");631 await popup.getByRole("button", { name: /create/i }).click();632 await popup.waitForTimeout(800);633 }634 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();635 await popup.waitForTimeout(300);636 await popup.getByRole("button", { name: /export backup/i }).click();637 await popup.waitForTimeout(500);638 const exportPw = popup.locator(".form-input[type='password']");639 await exportPw.fill("exportpassword");640 await popup.getByRole("button", { name: /download backup/i }).click();641 await popup.waitForTimeout(2000);642 await ctx.close();643 });644645 test("IEC05 - import with missing password field", async () => {646 const { popup } = await setupVault(ctx);647 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();648 await popup.waitForTimeout(300);649 await popup.getByRole("button", { name: /import passwords/i }).click();650 await popup.waitForTimeout(500);651 const fileInput = popup.locator('input[type="file"]');652 const csvContent = "title,username\nNoPassword,[email protected]";653 await fileInput.setInputFiles({654 name: "no-password.csv",655 mimeType: "text/csv",656 buffer: Buffer.from(csvContent)657 });658 await popup.waitForTimeout(1500);659 await ctx.close();660 });661662 test("IEC06 - export with unicode entries", async () => {663 const { popup } = await setupVault(ctx);664 await popup.locator('input[placeholder="search vault..."]').click();665 await popup.getByRole("button", { name: /new entry/i }).click();666 await popup.locator(".peach-new-name-input").fill("日本語エントリー");667 await popup.locator('input[placeholder="domain.com"]').fill("https://日本語.com");668 await popup.locator('input[placeholder="username or email"]').fill("ユーザー@例子.测试");669 await popup.locator(".peach-new-field-input[placeholder='password']").fill("密码🔐");670 await popup.getByRole("button", { name: /create/i }).click();671 await popup.waitForTimeout(1500);672 await popup.locator(".peach-icon-btn[aria-label='Settings']").click();673 await popup.waitForTimeout(300);674 await popup.getByRole("button", { name: /export backup/i }).click();675 await popup.waitForTimeout(500);676 const exportPw = popup.locator(".form-input[type='password']");677 await exportPw.fill("exportpassword");678 await popup.getByRole("button", { name: /download backup/i }).click();679 await popup.waitForTimeout(2000);680 const result = popup.locator("text=1 entries").first();681 const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);682 expect(hasResult).toBeTruthy();683 await ctx.close();684 });685});