Ctrl K
ComunidadGuías…

tests/e2e/control-durable-turn.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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();
    }
  });
}