tests/e2e/control-durable-turn.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import {
createUIMessageStream,
createUIMessageStreamResponse,
type UIMessage,
} from "ai";
import { controlTranscriptDatabase } from "../support/control-transcript-database";
import { persistControlTerminalOutcome } from "../../lib/control/terminal-outcome";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import {
fulfillJson,
mockBaseChrome,
mockControlSessionBootstrap,
} from "./helpers/automation-control-plane-fixtures";
import { captureRecoveryEvents } from "./helpers/control-recovery-fixtures";
for (const viewport of [
{ width: 1440, height: 1000 },
{ width: 390, height: 844 },
]) {
test(`durable turn survives reload and shows saved failure evidence at ${viewport.width}px`, async ({
page,
}) => {
await page.setViewportSize(viewport);
const f = await controlTranscriptDatabase();
try {
await enableScopedE2EAuth(page);
await mockBaseChrome(page);
await mockControlSessionBootstrap(page);
await captureRecoveryEvents(page);
await f.save([
{
id: "request",
role: "user",
parts: [{ type: "text", text: "Check this repository" }],
},
]);
let status = "idle";
let posts = 0;
await page.route("**/api/control/sessions**", async (route) => {
if (route.request().method() === "PUT")
return fulfillJson(route, { error: "Browser writes disabled" }, 503);
const saved = await f.save([]);
return fulfillJson(
route,
new URL(route.request().url()).searchParams.has("id")
? saved
: [saved]
);
});
await page.route("**/api/control/turns?**", async (route) =>
fulfillJson(route, {
sessionId: f.sessionId,
messages: (await f.save([])).messages,
call: status === "idle" ? null : { id: "call", status, error: null },
})
);
await page.route("**/api/control/chat", async (route) => {
posts++;
await f.save(
(route.request().postDataJSON() as { messages: UIMessage[] }).messages
);
status = "streaming";
const response = createUIMessageStreamResponse({
stream: createUIMessageStream({
execute: ({ writer }) =>
writer.write({
type: "data-control-turn",
data: { aiCallId: "call", sessionId: f.sessionId },
transient: true,
}),
}),
});
await route.fulfill({
contentType: "text/event-stream",
headers: { "x-vercel-ai-ui-message-stream": "v1" },
body: await response.text(),
});
});
await page.goto(`${scopedPath("control")}?mission=${f.sessionId}`);
const input = page.getByPlaceholder(
"Ask for follow-up changes or attach images"
);
await input.fill("Run checks");
await input.press("Enter");
await expect(
page.getByRole("button", { name: /stop/i }).first()
).toBeVisible();
await f.save([
{
id: "partial",
role: "assistant",
parts: [
{ type: "text", text: "Tests passed. Checking the build." },
{
type: "dynamic-tool",
toolName: "run_command",
toolCallId: "build",
state: "output-available",
input: { command: "pnpm build | tail; echo finished" },
output: {
exitCode: 0,
failures: [137],
stdout: "finished",
stderr: "Killed",
},
},
],
metadata: {
workspace_changes: {
sandboxId: "sandbox",
capturedAt: new Date().toISOString(),
files: [
{
path: "public/generated.js",
additions: 1,
deletions: 0,
patch:
"--- /dev/null\n+++ b/public/generated.js\n@@ -0,0 +1 @@\n+generated();",
},
],
},
},
},
]);
await page.reload();
await expect(
page.getByText("Tests passed. Checking the build.", { exact: true })
).toBeVisible();
await expect(
page.getByRole("button", { name: /stop/i }).first()
).toBeVisible();
await persistControlTerminalOutcome(
{
userId: f.owner,
sessionId: f.sessionId,
aiCallId: "call",
cancelled: false,
},
f.client
);
status = "failed";
await page.evaluate(() => {
for (const source of (
window as unknown as { recoveryEventSources: EventSource[] }
).recoveryEventSources) {
source.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify({ table: "ai_calls", op: "UPDATE" }),
})
);
}
});
await expect(
page.getByText(/The agent stopped before finishing/)
).toBeVisible();
await page.getByRole("button", { name: /^Agent commands/ }).click();
await expect(page.getByTestId("control-terminal-activity")).toContainText(
"Failed"
);
await expect(page.getByTestId("control-terminal-activity")).toContainText(
"Killed"
);
await expect(page.getByTestId("control-terminal-activity")).toContainText(
"137"
);
await page.getByRole("button", { name: "Close terminal" }).click();
await page.getByRole("button", { name: "Open changes" }).click();
await expect(
page.getByText("generated.js", { exact: true })
).toBeVisible();
expect(posts).toBe(1);
} finally {
await f.db.close();
}
});
}