diff --git a/CHANGELOG.md b/CHANGELOG.md index 5345f7e..b0db98b 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 b742cc8..3729138 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(); });