import-export-robust.spec.ts

27.8 KB · raw

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});