Ctrl K
ComunidadGuías…

tests/e2e/slack-view-work-workspace.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import {
  fulfillJson,
  initializeTrackedEvents,
  mockActivationFlow,
  syncedRepo,
} from "./helpers/activation-fixtures";
import { buildSandboxFixture } from "./helpers/sandbox-fixtures";

for (const sandboxStatus of ["running", "paused", "stopped"]) {
  test(`View work opens ${sandboxStatus} workspace, streams chat, and reloads history without launching work`, async ({
    page,
  }, testInfo) => {
    await initializeTrackedEvents(page);
    await enableScopedE2EAuth(page);
    await mockActivationFlow(page, { initialRepos: [syncedRepo] });
    const runId = "00000000-0000-4000-8000-000000000901";
    const context = {
      runId,
      aiCallId: "call-1",
      prompt: "Fix overlapping mobile canvas controls",
      status: "streaming",
      sandboxRecordId: "sandbox-record-repo-1",
      workingBranch: "fix/mobile",
      canGuide: true,
      repo: {
        ...syncedRepo,
        user_id: "user-1",
        created_at: "2026-09-05T00:00:00Z",
      },
    };
    let terminal = false;
    let resumeId: string | undefined;
    let launches = 0;
    let chats = 0;
    let guidance: unknown;
    let savedConversation: unknown;
    await page.route(/\/api\/settings(?:\?.*)?$/, (route) =>
      fulfillJson(route, { default_model: "openai/current-workspace-default" })
    );
    await page.route(/\/api\/conversations(?:\?.*)?$/, async (route) => {
      if (route.request().method() === "PUT")
        savedConversation = route.request().postDataJSON();
      await route.fallback();
    });
    await page.route("**/api/runs/*/workspace", (route) =>
      fulfillJson(route, {
        ...context,
        status: terminal ? "failed" : "streaming",
      })
    );
    const event = (id: string, type: string, message: string, payload = {}) =>
      `id: ${id}\nevent: ${type}\ndata: ${JSON.stringify({ id, type, message, payload, toolName: "bash", createdAt: "2026-09-05T00:00:00Z" })}\n\n`;
    const history =
      event("e1", "log", "I found the mobile header overlap.", {
        kind: "assistant_delta",
      }) +
      event("e2", "tool_started", "bash started", { toolCallId: "tool-1" });
    await page.route("**/api/runs/*/stream?*", async (route) => {
      expect(new URL(route.request().url()).searchParams.get("dpl")).toBe(
        `playwright-${process.env.PLAYWRIGHT_PORT || 3000}`
      );
      resumeId = route.request().headers()["last-event-id"];
      await route.fulfill({
        contentType: "text/event-stream",
        body:
          `event: run\ndata: ${JSON.stringify(context)}\n\n` +
          (resumeId ? "" : history) +
          (terminal
            ? event("e3", "failed", "Run stopped")
            : "event: replay_complete\ndata: {}\n\n"),
      });
    });
    await page.route("**/api/runs/*/guidance", async (route) => {
      guidance = route.request().postDataJSON();
      await fulfillJson(route, { id: "receipt", status: "received" });
    });
    const sandbox = {
      ...buildSandboxFixture({
        repoId: "repo-1",
        billingSource: "platform",
        status: sandboxStatus,
        healthStatus: sandboxStatus,
        previewUrl: new URL(
          "/__e2e/preview/repo-1",
          testInfo.project.use.baseURL
        ).toString(),
      }),
      working_branch: "fix/mobile",
      snapshot_id: sandboxStatus === "paused" ? "saved-snapshot" : null,
    };
    await page.route("**/api/sandbox/sandbox-record-repo-1/health", (route) =>
      fulfillJson(route, { health: { status: sandboxStatus }, sandbox })
    );
    await page.route("**/api/sandbox/sandbox-record-repo-1", (route) =>
      fulfillJson(route, { sandbox })
    );
    await page.route(/\/api\/sandbox(?:\?.*)?$/, async (route) => {
      if (route.request().method() === "GET")
        await fulfillJson(route, { sandboxes: [sandbox] });
      else {
        launches++;
        await route.abort();
      }
    });
    await page.route("**/api/sandbox/restart", async (route) => {
      launches++;
      await route.abort();
    });
    await page.route(/\/api\/chat(?:\?.*)?$/, async (route) => {
      chats++;
      await route.abort();
    });
    await page.goto(scopedPath(`projects/workspace?run=${runId}`));
    await expect(
      page.getByText("Workspace Chat · demo-app", { exact: true })
    ).toBeVisible();
    await expect(page.getByText("Live Preview", { exact: true })).toBeVisible();
    if (sandboxStatus === "running")
      await expect(
        page
          .frameLocator("iframe")
          .getByRole("heading", { name: "Demo Preview" })
      ).toBeVisible();
    if (sandboxStatus === "paused")
      await expect(
        page.getByText("Sandbox Paused", { exact: true })
      ).toBeVisible();
    await expect(
      page.getByText("Terminal", { exact: true }).first()
    ).toBeVisible();
    await expect(
      page.getByText("I found the mobile header overlap.", { exact: true })
    ).toBeVisible();
    await expect(
      page.getByRole("button", { name: /bash.*running/ })
    ).toBeVisible();
    await page
      .getByRole("textbox", { name: "Guide this run" })
      .fill("Keep desktop behavior unchanged");
    await page
      .getByRole("button", { name: "Send guidance", exact: true })
      .click();
    await expect(page.getByText(/Guidance saved/)).toBeVisible();
    expect(guidance).toMatchObject({ text: "Keep desktop behavior unchanged" });
    terminal = true;
    await expect(page.getByText("Run failed", { exact: true })).toBeVisible();
    expect(resumeId).toBe("e2");
    await expect(
      page.getByRole("button", { name: /bash.*error/ })
    ).toBeVisible();
    await expect(
      page.getByRole("button", { name: /bash.*running/ })
    ).toHaveCount(0);
    await page.reload();
    await expect(page.getByText("Run failed", { exact: true })).toBeVisible();
    await expect(
      page.getByText("I found the mobile header overlap.", { exact: true })
    ).toBeVisible();
    const sessions = await page.evaluate(
      () =>
        JSON.parse(localStorage.getItem("mogplex-sessions") ?? "{}").state
          .sessions
    );
    expect(
      sessions.filter(
        (session: { externalRunId?: string }) => session.externalRunId === runId
      )
    ).toHaveLength(1);
    expect(launches).toBe(0);
    expect(chats).toBe(0);
    await page.screenshot({
      path: testInfo.outputPath("run-workspace.png"),
      fullPage: true,
    });
    if (sandboxStatus === "running") {
      await page.route("**/api/models", (route) =>
        fulfillJson(route, {
          models: [],
          catalog: [],
          default_model: "openai/new-chat-default",
        })
      );
      await page.route(
        /\/api\/settings(?:\?.*)?$/,
        (route) =>
          fulfillJson(
            route,
            { error: "Could not load the workspace default model" },
            500
          ),
        { times: 1 }
      );
      await page
        .getByRole("button", { name: "Continue in workspace chat" })
        .click();
      await expect(
        page.getByText("Could not load the workspace default model", {
          exact: true,
        })
      ).toBeVisible();
      expect(savedConversation).toBeUndefined();
      await page
        .getByRole("button", { name: "Continue in workspace chat" })
        .click();
      await expect(
        page.getByRole("textbox", {
          name: "Ask the agent what to build, fix, or explain. Type / for commands or drop files here.",
        })
      ).toBeVisible();
      expect(savedConversation).toMatchObject({
        repo_id: "repo-1",
        sandbox_id: "sandbox-record-repo-1",
        model: "openai/current-workspace-default",
      });
      await expect(
        page.getByRole("button", {
          name: "Model: current-workspace-default",
          exact: true,
        })
      ).toBeVisible();
      expect(chats).toBe(0);
      await page.getByRole("button", { name: "New chat", exact: true }).click();
      await expect(
        page.getByRole("button", {
          name: "Model: new-chat-default",
          exact: true,
        })
      ).toBeVisible();
    }
  });
}

test("an inaccessible run does not fall through to another workspace", async ({
  page,
}) => {
  await initializeTrackedEvents(page);
  await enableScopedE2EAuth(page);
  await mockActivationFlow(page);
  await page.route("**/api/runs/*/workspace", (route) =>
    route.fulfill({ status: 404, json: { error: "Run not found" } })
  );
  await page.goto(scopedPath("projects/workspace?run=missing"));
  await expect(
    page
      .getByRole("alert")
      .filter({ hasText: "Run not found or you do not have access." })
  ).toBeVisible();
  await expect(page.getByText("Live Preview", { exact: true })).toHaveCount(0);
});

test("continuing a completed run waits for its delayed recorded history", async ({
  page,
}) => {
  await initializeTrackedEvents(page);
  await enableScopedE2EAuth(page);
  await mockActivationFlow(page, { initialRepos: [syncedRepo] });
  const runId = "00000000-0000-4000-8000-000000000903";
  await page.route("**/api/runs/*/workspace", (route) =>
    fulfillJson(route, {
      runId,
      aiCallId: "call-delayed",
      prompt: "Fix mobile controls",
      status: "success",
      sandboxRecordId: null,
      workingBranch: "fix/mobile",
      canGuide: false,
      repo: {
        ...syncedRepo,
        user_id: "user-1",
        created_at: "2026-09-05T00:00:00Z",
      },
    })
  );
  let releaseReplay = () => {};
  const replayGate = new Promise<void>((resolve) => {
    releaseReplay = resolve;
  });
  let savedConversation:
    | { messages?: { parts: { type: string; text?: string }[] }[] }
    | undefined;
  await page.route(/\/api\/conversations(?:\?.*)?$/, async (route) => {
    if (route.request().method() === "PUT")
      savedConversation = route.request().postDataJSON();
    await route.fallback();
  });
  await page.route("**/api/runs/*/stream?*", async (route) => {
    expect(new URL(route.request().url()).searchParams.get("dpl")).toBe(
      `playwright-${process.env.PLAYWRIGHT_PORT || 3000}`
    );
    await replayGate;
    const event = (id: string, type: string, message: string, payload = {}) =>
      `id: ${id}\nevent: ${type}\ndata: ${JSON.stringify({ id, type, message, payload, toolName: null, createdAt: "2026-09-05T00:00:00Z" })}\n\n`;
    await route.fulfill({
      contentType: "text/event-stream",
      body:
        'event: run\ndata: {"status":"success"}\n\n' +
        event(
          "report",
          "log",
          "Fixed the mobile overlap and verified desktop.",
          { kind: "assistant_final" }
        ) +
        // The production stream closes at the terminal event without replay_complete.
        event("done", "finished", "Completed"),
    });
  });
  try {
    await page.goto(scopedPath(`projects/workspace?run=${runId}`));
    await expect(page.getByText("Run complete", { exact: true })).toBeVisible();
    const continueButton = page.getByRole("button", {
      name: "Continue in workspace chat",
    });
    await expect(continueButton).toBeDisabled();
    expect(savedConversation).toBeUndefined();
    releaseReplay();
    await expect(
      page.getByText("Fixed the mobile overlap and verified desktop.", {
        exact: true,
      })
    ).toBeVisible();
    await expect(continueButton).toBeEnabled();
    await continueButton.click();
    await expect(
      page.getByRole("textbox", {
        name: "Ask the agent what to build, fix, or explain. Type / for commands or drop files here.",
      })
    ).toBeVisible();
    expect(JSON.stringify(savedConversation)).toContain(
      "Fixed the mobile overlap and verified desktop."
    );
  } finally {
    releaseReplay();
  }
});