mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
e2e: seedSessionMessages seam + replayed-transcript specs
Seeds rich history per session so the reopen path is testable. Covers replayed tool groups, filter counts, connector cards, tail-error Retry.
This commit is contained in:
@@ -51,6 +51,14 @@ won't clash with a running `npm run dev` on 5173) and reuses it if already up.
|
||||
automation ("Daily AI News") with a running run — `POST .../run` appends a run, `PATCH`/`DELETE`
|
||||
toggle and remove.
|
||||
|
||||
- **Seeded transcripts**: every session's `GET /v1/sessions/{id}/messages` answers `[]`, so
|
||||
reopening starts blank. `seedSessionMessages(page, sessionId, messages)` (exported from
|
||||
fixtures) registers a later, winning route that stages full replayed history for one
|
||||
session — tool_calls + `role:"tool"` results (wired by `tool_call_id`), `_display`
|
||||
sidecars, `reasoning`, `notice` markers, connector `source` messages. Use it to assert
|
||||
the reopen path (`itemsFromMessages`) — replayed step groups, connector cards, tail-error
|
||||
Retry — which live echo-driving can't reach. See `seeded-history.spec.ts`.
|
||||
|
||||
## Adding a spec
|
||||
|
||||
```ts
|
||||
|
||||
@@ -2099,6 +2099,27 @@ export async function mockApi(page: import("@playwright/test").Page) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Seed a replayed transcript for one session. The shared mock answers every
|
||||
* GET /v1/sessions/{id}/messages with `[]`, so reopening a session always starts blank;
|
||||
* this registers a LATER route (later routes win) that stages rich history for that one
|
||||
* session — replayed tool calls with results, connector-sourced messages, notices,
|
||||
* reasoning — so specs can assert the reopen path (itemsFromMessages) directly instead
|
||||
* of driving every turn live through the fake agent. Call after the page has the mock
|
||||
* (any time before the session is opened). */
|
||||
export async function seedSessionMessages(
|
||||
page: Page,
|
||||
sessionId: string,
|
||||
messages: Record<string, unknown>[],
|
||||
): Promise<void> {
|
||||
await page.route(new RegExp(`/v1/sessions/${sessionId}/messages$`), (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ messages }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// A `test` whose page has the API mocked before navigation.
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Seeded-transcript replay (the reopen path). Everything here renders from
|
||||
// GET /v1/sessions/{id}/messages via itemsFromMessages — no live turns are driven —
|
||||
// which is the one path the fake agent's echo scripting could never reach: replayed
|
||||
// tool calls with results, privacy-filter counts, reasoning disclosures, persisted
|
||||
// notices, and connector-sourced inbound messages.
|
||||
import { expect } from "@playwright/test";
|
||||
import { test, seedSessionMessages } from "./fixtures";
|
||||
|
||||
const TS = 1755600000; // fixed epoch — replay must not depend on "now"
|
||||
|
||||
const RICH_HISTORY = [
|
||||
{ role: "user", content: "Audit the release branch", ts: TS },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
tool_calls: [
|
||||
{ id: "t1", function: { name: "run_shell", arguments: JSON.stringify({ command: "git log --oneline -5" }) } },
|
||||
{ id: "t2", function: { name: "read_file", arguments: JSON.stringify({ path: "CHANGELOG.md" }) } },
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "t1", content: "abc123 release: cut 0.1.7" },
|
||||
{ role: "tool", tool_call_id: "t2", content: "## 0.1.7 — fixes", _display: { hidden_by_filters: 3 } },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "The branch is clean — **two checks** passed.",
|
||||
reasoning: "Compared the log against the changelog; both entries line up.",
|
||||
ts: TS + 40,
|
||||
},
|
||||
{ role: "notice", kind: "compacted", text: "Context compacted" },
|
||||
{ role: "assistant", content: "Anything else before I file the summary?" },
|
||||
];
|
||||
|
||||
test("a reopened session replays rich history: tools, filters, reasoning, notices", async ({
|
||||
page,
|
||||
}) => {
|
||||
await seedSessionMessages(page, "pinned-cowork-1", RICH_HISTORY);
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
|
||||
// Plain items replay as they rendered live.
|
||||
await expect(page.getByText("Audit the release branch")).toBeVisible();
|
||||
await expect(page.locator(".md strong", { hasText: "two checks" })).toBeVisible();
|
||||
await expect(page.getByText("Context compacted")).toBeVisible();
|
||||
|
||||
// The turn's tools fold into a collapsed step group; the filter count rides the summary.
|
||||
const group = page.locator(".stepgroup").first();
|
||||
await expect(group).toContainText("2 steps");
|
||||
await expect(page.getByTestId("stepgroup-hidden")).toContainText("3 hidden");
|
||||
|
||||
// Expanding reveals the replayed rows with their results wired by tool_call_id.
|
||||
await group.locator("summary").click();
|
||||
await expect(page.getByTestId("turn-step")).toHaveCount(2);
|
||||
await expect(page.getByTestId("tool-hidden-count")).toBeVisible();
|
||||
|
||||
// Reasoning persists as the collapsed disclosure, not live "Thinking…".
|
||||
await expect(page.getByTestId("thinking-toggle")).toContainText("Thought process");
|
||||
});
|
||||
|
||||
test("a connector-sourced message replays as its structured card", async ({ page }) => {
|
||||
await seedSessionMessages(page, "pinned-cowork-1", [
|
||||
{
|
||||
role: "user",
|
||||
content: "[slack] Priya: Ship it when the checks are green",
|
||||
source: {
|
||||
connector: "slack",
|
||||
kind: "channel",
|
||||
channel_id: "C0REL",
|
||||
channel_name: "#release",
|
||||
sender_id: "U1",
|
||||
sender_name: "Priya",
|
||||
ts: TS,
|
||||
text: "Ship it when the checks are green",
|
||||
},
|
||||
},
|
||||
{ role: "assistant", content: "Will do — watching the checks now." },
|
||||
]);
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
|
||||
const card = page.locator(".connector-card[data-brand='slack']");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(card).toContainText("Priya");
|
||||
await expect(card).toContainText("Ship it when the checks are green");
|
||||
// The framed model-facing content must NOT double-render as a plain bubble.
|
||||
await expect(page.getByText("[slack] Priya:")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a replayed error notice at the tail offers Retry", async ({ page }) => {
|
||||
await seedSessionMessages(page, "pinned-cowork-1", [
|
||||
{ role: "user", content: "run the report", ts: TS },
|
||||
{ role: "notice", kind: "error", text: "provider unavailable" },
|
||||
]);
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
|
||||
await expect(page.getByText("Error: provider unavailable")).toBeVisible();
|
||||
await expect(page.getByTestId("notice-retry")).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user