Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 26 additions & 8 deletions web/monitor-ui/tests/trajectory-export.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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;
Expand All @@ -38,15 +50,21 @@ 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 }) => {
await page.route("**/api/sessions/export-session/trajectory", (route) => route.fulfill({ status: 404, json: { error: "Session has no recorded requests" } }));
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();
});
Loading