From cc11700621032caa8e6d63ad5494cb989430a379 Mon Sep 17 00:00:00 2001 From: yaroslav k Date: Sun, 16 Aug 2026 11:49:00 +0300 Subject: [PATCH] e2e: persistence tests, verified against a live server - campaign name survives a reload (autosave debounce respected) - export/import round trip: Save downloads dashboard.json with the current state, Clear blanks it via the confirm modal, Load restores it - specs take BASE_URL env override (default stays localhost:3000) - both new tests and the three smoke tests pass on chromium --- end2end/tests/persistence.spec.ts | 74 +++++++++++++++++++++++++++++++ end2end/tests/smoke.spec.ts | 3 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 end2end/tests/persistence.spec.ts diff --git a/end2end/tests/persistence.spec.ts b/end2end/tests/persistence.spec.ts new file mode 100644 index 0000000..d8814c5 --- /dev/null +++ b/end2end/tests/persistence.spec.ts @@ -0,0 +1,74 @@ +import { test, expect, type Page } from "@playwright/test"; +import * as fs from "node:fs"; + + +// Override with BASE_URL (e.g. when the dev server runs on another port). +const BASE = process.env.BASE_URL ?? "http://localhost:3000"; + +// Collects browser console errors and page errors for the duration of a test. +function trackErrors(page: Page): string[] { + const errors: string[] = []; + page.on("console", (msg) => { + if (msg.type() === "error") { + errors.push(msg.text()); + } + }); + page.on("pageerror", (err) => { + errors.push(String(err)); + }); + return errors; +} + +// Autosave is debounced at 500 ms; give it time before reloading. +const DEBOUNCE_MS = 700; + +test("campaign name survives a reload", async ({ page }) => { + const errors = trackErrors(page); + await page.goto(`${BASE}/`); + + const name = "Кампания с сохранением"; + const input = page.locator("#campaign-name"); + await input.fill(name); + await page.waitForTimeout(DEBOUNCE_MS); + + await page.reload(); + await expect(input).toHaveValue(name); + + expect(errors).toEqual([]); +}); + +test("export/import round trip through the file", async ({ page }) => { + const errors = trackErrors(page); + await page.goto(`${BASE}/`); + + // Save: the header button downloads dashboard.json with the current state. + const name = "Кампания экспорта"; + await page.locator("#campaign-name").fill(name); + const [download] = await Promise.all([ + page.waitForEvent("download"), + page.getByRole("button", { name: "Сохранить" }).click(), + ]); + expect(download.suggestedFilename()).toBe("dashboard.json"); + + const filePath = await download.path(); + expect(filePath).not.toBeNull(); + const saved = JSON.parse(fs.readFileSync(filePath!, "utf8")); + expect(saved.campaign).toBe(name); + + // Clear: confirm the dialog, the dashboard goes blank. + await page.getByRole("button", { name: "Очистить" }).click(); + await page.locator(".modal").waitFor(); + await page.locator(".modal").getByRole("button", { name: "Очистить" }).click(); + await expect(page.locator(".modal")).toBeHidden(); + await expect(page.locator("#campaign-name")).toHaveValue(""); + + // Load: pick the file we just saved, the state comes back. + const [chooser] = await Promise.all([ + page.waitForEvent("filechooser"), + page.getByRole("button", { name: "Загрузить" }).click(), + ]); + await chooser.setFiles(filePath!); + await expect(page.locator("#campaign-name")).toHaveValue(name); + + expect(errors).toEqual([]); +}); diff --git a/end2end/tests/smoke.spec.ts b/end2end/tests/smoke.spec.ts index fe9bc20..29d2313 100644 --- a/end2end/tests/smoke.spec.ts +++ b/end2end/tests/smoke.spec.ts @@ -1,6 +1,7 @@ import { test, expect, type Page } from "@playwright/test"; -const BASE = "http://localhost:3000"; +// Override with BASE_URL (e.g. when the dev server runs on another port). +const BASE = process.env.BASE_URL ?? "http://localhost:3000"; // Collects browser console errors and page errors for the duration of a test. function trackErrors(page: Page): string[] {