test: add unit/integration/E2E test suites, fix create_session bug, update docs
- Unit tests: test_session.py (27), test_error_kb.py (24), test_agent.py hardened - Integration tests: test_api_integration.py (25) with FastAPI TestClient - E2E tests: main-flows.spec.ts (8) with Playwright + API mocking - Bug fix: backend/session.py create_session() missing session_id parameter - Config: frontend/playwright.config.ts, npm run test:e2e - Docs: update CLAUDE.md v9, .gitignore for test artifacts/eval reports
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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: "<jasperReport/>" },
|
||||
},
|
||||
});
|
||||
}
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user