From 294bd57317289e99c7a7f0c997978a0b8d575426 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 16:37:26 +0000 Subject: [PATCH] =?UTF-8?q?test(e2e):=20playwright=20smoke=20=E2=80=94=20l?= =?UTF-8?q?ogin/create/edit/save=20happy=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + frontend/e2e/fixtures.ts | 13 +++++++++++ frontend/e2e/smoke.spec.ts | 27 ++++++++++++++++++++++ frontend/playwright.config.ts | 43 +++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 frontend/e2e/fixtures.ts create mode 100644 frontend/e2e/smoke.spec.ts create mode 100644 frontend/playwright.config.ts diff --git a/.gitignore b/.gitignore index e5330e7..317b145 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ frontend/dist/ frontend/.vite/ frontend/playwright-report/ frontend/test-results/ +frontend/.pw-data/ # SPA build output — .gitkeep tracks the folder, everything inside is generated src/VolcanicCore.Api/wwwroot/* diff --git a/frontend/e2e/fixtures.ts b/frontend/e2e/fixtures.ts new file mode 100644 index 0000000..e4abe5d --- /dev/null +++ b/frontend/e2e/fixtures.ts @@ -0,0 +1,13 @@ +import { spawnSync } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default async function globalSetup() { + const dataDir = path.resolve(__dirname, "../.pw-data"); + // Shell out to `rm -rf` — Node's fs.rm can trip ENOTEMPTY on overlay + // filesystems (rootless containers, docker layered volumes) where the + // dir looks empty via readdir but rmdir still refuses. + spawnSync("rm", ["-rf", dataDir], { stdio: "ignore" }); +} diff --git a/frontend/e2e/smoke.spec.ts b/frontend/e2e/smoke.spec.ts new file mode 100644 index 0000000..f93ada6 --- /dev/null +++ b/frontend/e2e/smoke.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from "@playwright/test"; + +test("login → create workflow → drop node → save", async ({ page }) => { + await page.goto("/login"); + await page.getByLabel(/password/i).fill("hunter2"); + await page.getByRole("button", { name: /sign in/i }).click(); + + await expect(page).toHaveURL(/\/workflows$/); + await page.getByRole("button", { name: /new workflow/i }).click(); + await page.getByPlaceholder(/name/i).fill("Smoke Test"); + await page.getByRole("button", { name: /^create$/i }).click(); + + await expect(page.getByText("Smoke Test")).toBeVisible(); + await page.getByText("Smoke Test").click(); + + // On editor. Drag the Webhook tile onto the canvas. + const tile = page.locator("aside").filter({ hasText: /Nodes/i }).getByText("Webhook"); + const canvas = page.locator(".react-flow__pane"); + + await tile.dragTo(canvas); + await expect(page.locator(".react-flow__node")).toHaveCount(1); + + await page.getByRole("button", { name: /save/i }).click(); + // No visible confirmation — reload and assert the node persisted. + await page.reload(); + await expect(page.locator(".react-flow__node")).toHaveCount(1); +}); diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..7a7cebb --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,43 @@ +import { defineConfig, devices } from "@playwright/test"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const dataDir = path.resolve(__dirname, "./.pw-data"); +const apiPort = 5099; +const uiPort = 5173; + +export default defineConfig({ + testDir: "./e2e", + timeout: 60_000, + fullyParallel: false, + workers: 1, + reporter: "list", + use: { + baseURL: `http://127.0.0.1:${uiPort}`, + trace: "off", + ...devices["Desktop Chrome"], + }, + webServer: [ + { + // Fresh DB per run — the data dir is wiped in globalSetup. + command: `dotnet run --project ../src/VolcanicCore.Api --urls http://127.0.0.1:${apiPort}`, + cwd: __dirname, + env: { + ADMIN_PASSWORD: "hunter2", + DATA_DIRECTORY: dataDir, + }, + url: `http://127.0.0.1:${apiPort}/api/health`, + timeout: 120_000, + reuseExistingServer: false, + }, + { + command: `pnpm dev --host 127.0.0.1 --port ${uiPort}`, + cwd: __dirname, + url: `http://127.0.0.1:${uiPort}`, + timeout: 60_000, + reuseExistingServer: false, + }, + ], + globalSetup: "./e2e/fixtures.ts", +});