From 26935d335c8458dec010c4dec1dd2f156902806f Mon Sep 17 00:00:00 2001 From: kingfs Date: Wed, 14 Oct 2026 13:03:28 +0800 Subject: [PATCH] test(monitor): follow the trajectory preview and the toast in the export spec tests/trajectory-export.spec.js was written when the export was the first read of `/api/sessions/{id}/trajectory` and a failed export printed one line into the page, and both of those stopped being true without the spec being updated. The session page previews the reconstructed trajectory on mount, over the same URL the export downloads, so `expect(calls).toBe(0)` before the click now sees the panel's own read. The export's outcome is a toast rather than page state, so the 404 message lands twice - in the trajectory panel and in the notification - which strict mode rejects as an ambiguous locator. The spec waits for the preview read, asserts the one request the click adds instead of a count from zero, keeps the authentication assertion on every request, and names the page for the failure text so it does not collide with the notification. --- CHANGELOG.md | 3 ++ .../tests/trajectory-export.spec.js | 34 ++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5345f7e7..b0db98bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## [Unreleased] +### Fixed +- The session-export browser spec follows the trajectory preview and the toast reporting. `tests/trajectory-export.spec.js` still assumed the export was the first read of `/api/sessions/{id}/trajectory` and that a failure was printed once, but the session page has previewed the reconstructed trajectory on mount ever since the trajectory view was refactored, and the export's outcome has been a toast ever since the writes moved onto `useMutation` - so `expect(calls).toBe(0)` failed on the panel's own read, and the 404 message matched twice, in the panel and in the notification, which strict mode rejects. The spec waits for that read now, asserts the single request the click adds rather than a count from zero, keeps the authentication assertion on every request, and scopes the failure assertion to the page so it does not collide with the notification. + ## [3.2.0] - 2026-10-10 ### Added diff --git a/web/monitor-ui/tests/trajectory-export.spec.js b/web/monitor-ui/tests/trajectory-export.spec.js index b742cc87..37291389 100644 --- a/web/monitor-ui/tests/trajectory-export.spec.js +++ b/web/monitor-ui/tests/trajectory-export.spec.js @@ -5,6 +5,14 @@ const session = { traces: [], timeline: [], analysis: [], }; +const record = { + schema_version: "ATIF-v1.8", + session_id: "export-session", + agent: { name: "unknown", version: "unknown" }, + steps: [{ step_id: 1, source: "user", message: "hello" }], + extra: { warnings: [] }, +}; + test.beforeEach(async ({ page }) => { await page.addInitScript(() => { localStorage.setItem("trajecta.monitor.language", "en"); @@ -19,17 +27,21 @@ test.beforeEach(async ({ page }) => { }); test("session ATIF export requires a click and downloads authenticated JSONL", async ({ page }) => { - let calls = 0; - const record = { schema_version: "ATIF-v1.8", session_id: "export-session", agent: { name: "unknown", version: "unknown" }, steps: [{ step_id: 1, source: "user", message: "hello" }], extra: { warnings: [] } }; + const reads = []; await page.route("**/api/sessions/export-session/trajectory", async (route) => { - calls++; - expect(route.request().headers().authorization).toBe("Bearer test-monitor-jwt"); + reads.push(route.request().headers().authorization); await route.fulfill({ contentType: "application/x-ndjson", body: JSON.stringify(record) + "\n" }); }); + // The detail page previews the trajectory, so this URL is already read once + // when the panel mounts. The click is what turns the same URL into a download, + // so what is asserted here is the request the click adds - and the fact that + // nothing is downloaded before it. + const previewRead = page.waitForResponse("**/api/sessions/export-session/trajectory"); await page.goto("/sessions/export-session"); + await previewRead; + expect(reads).toHaveLength(1); const button = page.getByRole("button", { name: "Export trajectory (ATIF)" }); await expect(button).toBeEnabled(); - expect(calls).toBe(0); const downloadPromise = page.waitForEvent("download"); await button.click(); const download = await downloadPromise; @@ -38,8 +50,9 @@ test("session ATIF export requires a click and downloads authenticated JSONL", a let body = ""; for await (const chunk of stream) body += chunk.toString(); expect(body).toBe(JSON.stringify(record) + "\n"); - await expect(page.getByText("Trajectory downloaded. One complete session per JSONL line.")).toBeVisible(); - expect(calls).toBe(1); + await expect(page.getByRole("region", { name: /Notifications/ })).toContainText("Trajectory downloaded. One complete session per JSONL line."); + expect(reads).toHaveLength(2); + expect(reads.every((authorization) => authorization === "Bearer test-monitor-jwt")).toBe(true); }); test("session ATIF export shows a failure and allows retry", async ({ page }) => { @@ -47,6 +60,11 @@ test("session ATIF export shows a failure and allows retry", async ({ page }) => await page.goto("/sessions/export-session"); const button = page.getByRole("button", { name: "Export trajectory (ATIF)" }); await button.click(); - await expect(page.getByText("Session has no recorded requests")).toBeVisible(); + // The panel's read runs into the same 404 as the click, so the message shows + // up twice - once in the trajectory panel and once in the toast that reports + // the failed download. The assertion names the page rather than the whole + // document so it does not collide with the notification. + await expect(page.getByRole("main").getByText("Session has no recorded requests")).toBeVisible(); + await expect(page.getByRole("region", { name: /Notifications/ })).toContainText("Session has no recorded requests"); await expect(button).toBeEnabled(); });