/** * E2E tests: key user flows for the JRXML Agent frontend. * * Pre-requisites: npm run dev (reuseExistingServer in playwright.config). * API calls are intercepted by page.route() — no real backend needed. */ import { test, expect } from "@playwright/test"; // ── helpers ──────────────────────────────────────────────────── function mockApi(page: any) { page.route("**/api/health", (route: any) => route.fulfill({ json: { status: "ok", version: "5.0" } }) ); page.route("**/api/sessions", (route: any) => { if (route.request().method() === "POST") { return route.fulfill({ json: { session_id: "test12345678", session_name: "新建报表 2026-05-22", created_at: "2026-05-22T10:00:00.000Z", updated_at: "2026-05-22T10:00:00.000Z", }, }); } return route.fulfill({ json: { sessions: [] } }); }); page.route("**/api/sessions/*/chat", (route: any) => { const sseBody = [ "event: node_start", 'data: {"node":"classify_intent","label":"识别意图","step_index":1}', "", "event: node_complete", 'data: {"node":"classify_intent","label":"识别意图","detail":"意图: 新建报表"}', "", "event: agent_complete", 'data: {"reason":"done","intent":"initial_generation","status":"pass","jrxml_length":42,"versions":1,"total_duration_ms":1200}', "", "", ].join("\n"); return route.fulfill({ status: 200, headers: { "content-type": "text/event-stream" }, body: sseBody, }); }); page.route("**/api/upload", (route: any) => route.fulfill({ json: { file_id: "f001122334455", filename: "test.png", size: 1024 }, }) ); // Catch-all for GET/DELETE /api/sessions/:id (must fallback for POST to let chat route match) page.route("**/api/sessions/**", (route: any) => { if (route.request().method() === "DELETE") { return route.fulfill({ json: { status: "deleted" } }); } if (route.request().method() === "GET") { return route.fulfill({ json: { session_id: "test12345678", session_name: "测试会话", agent_state: { current_jrxml: "" }, }, }); } return route.fallback(); }); } // ── tests ────────────────────────────────────────────────────── test.describe("Page load", () => { test("renders sidebar and input area", async ({ page }) => { await mockApi(page); await page.goto("/"); await expect(page.locator("aside.sidebar")).toBeVisible(); await expect(page.locator("h2")).toContainText("JRXML"); await expect(page.locator(".unified-input")).toBeVisible(); }); test("sidebar shows session list header and new button", async ({ page }) => { await mockApi(page); await page.goto("/"); await expect(page.getByText("会话列表")).toBeVisible(); await expect(page.locator('button[title="新建会话"]')).toBeVisible(); }); }); test.describe("Session management", () => { test("creates new session on button click", async ({ page }) => { await mockApi(page); await page.goto("/"); await page.locator('button[title="新建会话"]').click(); await expect(page.locator(".session-item")).toBeVisible({ timeout: 5000 }); await expect(page.locator(".session-item.active")).toBeVisible(); }); test("can delete current session", async ({ page }) => { await mockApi(page); await page.goto("/"); await page.locator('button[title="新建会话"]').click(); await expect(page.locator(".session-item")).toBeVisible({ timeout: 5000 }); page.on("dialog", (dialog) => dialog.accept()); await page.locator(".btn-delete").click(); await expect(page.locator(".session-item")).toHaveCount(0, { timeout: 5000 }); }); }); test.describe("Chat flow", () => { test("sends text and displays user message + process section", async ({ page }) => { await mockApi(page); await page.goto("/"); await page.locator('button[title="新建会话"]').click(); await expect(page.locator(".session-item")).toBeVisible({ timeout: 5000 }); const textarea = page.locator(".unified-input textarea"); await textarea.fill("生成一个员工名册报表"); await page.locator(".send-btn").click(); await expect( page.locator(".chat-messages .message.msg-user").filter({ hasText: "员工名册" }) ).toBeVisible({ timeout: 10000 }); await expect(page.locator(".process-section")).toBeVisible({ timeout: 10000 }); }); test("summary card appears after stream complete", async ({ page }) => { await mockApi(page); await page.goto("/"); await page.locator('button[title="新建会话"]').click(); await expect(page.locator(".session-item")).toBeVisible({ timeout: 5000 }); await page.locator(".unified-input textarea").fill("生成报表"); await page.locator(".send-btn").click(); await expect(page.locator(".summary-card")).toBeVisible({ timeout: 15000 }); }); }); test.describe("Input UX", () => { test("send button disabled when input empty", async ({ page }) => { await mockApi(page); await page.goto("/"); await expect(page.locator(".send-btn")).toBeDisabled(); }); test("send button enabled when text entered", async ({ page }) => { await mockApi(page); await page.goto("/"); await page.locator(".unified-input textarea").fill("Hi"); await expect(page.locator(".send-btn")).toBeEnabled(); }); });