Ctrl K
ComunidadGuías…

tests/e2e/control-workspace-panes.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import { mockControlWorkspace } from "./helpers/control-workspace-fixture";

test("task tools stay beside the conversation and preserve its draft", async ({
  page,
}, testInfo) => {
  await page.setViewportSize({ width: 1600, height: 1000 });
  const { mutations } = await mockControlWorkspace(page);
  const composer = page.getByRole("textbox", {
    name: "Ask for follow-up changes",
  });
  await composer.fill("Keep this draft while I inspect the result");
  await page.getByRole("button", { name: "Open preview", exact: true }).click();
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await expect(
    panel.getByTitle("Sandbox Preview", { exact: true })
  ).toHaveAttribute("src", "https://preview.example.test/one");
  await expect(composer).toBeVisible();
  await page.screenshot({ path: testInfo.outputPath("workspace-desktop.png") });
  await expect(
    page.getByText("The welcome page is ready for review.", { exact: true })
  ).toBeVisible();
  const frame = await panel
    .getByTitle("Sandbox Preview", { exact: true })
    .elementHandle();
  await page.getByRole("button", { name: "Close task tools" }).click();
  await page.getByRole("button", { name: "Open preview", exact: true }).click();
  expect(await frame!.evaluate((node) => node.isConnected)).toBe(true);
  await page.getByRole("button", { name: "Open changes", exact: true }).click();
  await expect(panel).toContainText("app/page.tsx");
  await panel.getByText("app/page.tsx", { exact: true }).click();
  await expect(panel).toContainText("Welcome home");
  await expect(page.getByRole("dialog")).toHaveCount(0);
  await page.getByRole("button", { name: "Open plan", exact: true }).click();
  await expect(panel).toContainText("Update welcome copy");
  await page.getByRole("button", { name: "Close task tools" }).click();
  await expect(composer).toHaveValue(
    "Keep this draft while I inspect the result"
  );
  await page
    .getByRole("button", { name: "Open terminal", exact: true })
    .click();
  await expect(
    page.getByRole("region", { name: "Task terminal" })
  ).toBeVisible();
  await expect(page).toHaveURL(/\/control\?mission=task-one/);
  await page.getByRole("tab", { name: "Agent commands", exact: true }).click();
  await expect(
    page.getByRole("region", { name: "Task terminal" })
  ).toContainText("12 checks passed");
  await page.getByRole("button", { name: "Close terminal" }).click();
  await expect(composer).toHaveValue(
    "Keep this draft while I inspect the result"
  );
  expect(mutations).toEqual([]);
});

test("mobile tools use a full-width view and return to the same conversation", async ({
  page,
}, testInfo) => {
  await page.setViewportSize({ width: 390, height: 844 });
  await mockControlWorkspace(page);
  await expect(page.locator(".app-statusbar")).toContainText("Sandboxes");
  await expect(page.locator(".app-statusbar")).not.toContainText(
    "Open a workspace"
  );
  const composer = page.getByRole("textbox", {
    name: "Ask for follow-up changes",
  });
  await composer.fill("Mobile draft");
  expect((await page.getByRole("banner").boundingBox())!.height).toBeLessThan(
    80
  );
  await page.getByRole("button", { name: "Open plan", exact: true }).click();
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await expect(panel).toContainText("Update welcome copy");
  await page.screenshot({ path: testInfo.outputPath("workspace-mobile.png") });
  await expect(panel).toHaveCSS("transform", "none");
  const box = await panel.boundingBox();
  expect(box!.width).toBeGreaterThan(350);
  expect(box!.x + box!.width).toBeLessThanOrEqual(390);
  await page.getByRole("button", { name: "Close task tools" }).click();
  await expect(composer).toBeVisible();
  await expect(composer).toHaveValue("Mobile draft");
  expect(
    await page.evaluate(() => document.documentElement.scrollWidth)
  ).toBeLessThanOrEqual(390);
});

for (const state of ["paused", "stopped"] as const) {
  test(`${state} task previews show recovery controls without loading a stopped provider page`, async ({
    page,
  }) => {
    const { sandboxes } = await mockControlWorkspace(page);
    const sandbox = sandboxes[0];
    sandbox.status = state;
    sandbox.health_status = state;
    sandbox.runtime_summary = {
      ...sandbox.runtime_summary!,
      status: state,
      health_status: state,
      persistent: true,
    };
    await page.route("**/api/sandbox/*/health", (route) =>
      route.fulfill({
        json: { status: state, healthStatus: state, sandbox },
      })
    );
    let providerRequests = 0;
    page.on("request", (request) => {
      if (request.url().startsWith("https://preview.example.test/"))
        providerRequests++;
    });
    await page.reload();
    await expect(
      page.getByRole("button", {
        name: new RegExp(
          `Selected sandbox acme/widgets, branch work/one, ${state === "paused" ? "Paused" : "Stopped"}`
        ),
      })
    ).toBeVisible();
    await page
      .getByRole("button", { name: "Open preview", exact: true })
      .click();
    const panel = page.getByRole("complementary", { name: "Task tools" });
    await expect(
      panel.getByRole("button", {
        name: state === "paused" ? "Resume preview" : "Restart on this branch",
        exact: true,
      })
    ).toBeVisible();
    await expect(panel.locator("iframe")).toHaveCount(0);
    expect(providerRequests).toBe(0);
  });
}

for (const missingBinding of [false, true]) {
  test(`preview requires a task environment when ${missingBinding ? "the saved sandbox is missing" : "multiple sandboxes are available"}`, async ({
    page,
  }) => {
    const { sessions, mutations } = await mockControlWorkspace(page);
    if (missingBinding)
      sessions[1].messages = [
        {
          id: "old-execution",
          role: "assistant",
          parts: [
            {
              type: "dynamic-tool",
              toolName: "run_command",
              toolCallId: "old-command",
              state: "output-available",
              input: { command: "pwd" },
              output: { sandboxId: "missing-sandbox", stdout: "/workspace" },
            },
          ],
        },
      ];
    let providerRequests = 0;
    page.on("request", (request) => {
      if (request.url().startsWith("https://preview.example.test/"))
        providerRequests++;
    });
    await page.getByRole("button", { name: /^Review billing flow/ }).click();
    await page
      .getByRole("button", { name: "Open preview", exact: true })
      .click();
    const panel = page.getByRole("complementary", { name: "Task tools" });
    await expect(panel.locator("iframe")).toHaveCount(0);
    await expect(panel).toContainText("Select a sandbox for this task");
    await panel
      .getByRole("button", { name: "Choose sandbox", exact: true })
      .click();
    await expect(panel).toContainText("work/one");
    await expect(panel).toContainText("work/two");
    expect(providerRequests).toBe(0);
    expect(mutations).toEqual([]);
  });
}

test("sandbox choices and drafts stay attached to their task across switches", async ({
  page,
}) => {
  await page.setViewportSize({ width: 1600, height: 1000 });
  const { mutations } = await mockControlWorkspace(page);
  const composer = page.getByRole("textbox", {
    name: "Ask for follow-up changes",
  });
  await composer.fill("Welcome draft");
  await page.getByRole("button", { name: "Open preview", exact: true }).click();
  await page.getByRole("button", { name: /^Review billing flow/ }).click();
  await expect(
    page.getByRole("button", {
      name: "Choose sandbox for this task",
      exact: true,
    })
  ).toBeVisible();
  await composer.fill("Billing draft");
  await page
    .getByRole("button", { name: "Choose sandbox for this task", exact: true })
    .click();
  await page
    .getByRole("menuitem", { name: /Select acme\/widgets, branch work\/two/ })
    .click();
  await page.getByRole("button", { name: "Open preview", exact: true }).click();
  await expect(
    page.getByTitle("Sandbox Preview", { exact: true })
  ).toHaveAttribute("src", "https://preview.example.test/two");
  await page.getByRole("button", { name: /^Refresh welcome page/ }).click();
  await expect(composer).toHaveValue("Welcome draft");
  await expect(
    page.getByTitle("Sandbox Preview", { exact: true })
  ).toHaveAttribute("src", "https://preview.example.test/one");
  await page.getByRole("button", { name: /^Review billing flow/ }).click();
  await expect(composer).toHaveValue("Billing draft");
  await page.reload();
  await expect(
    page.getByRole("button", {
      name: /Selected sandbox acme\/widgets, branch work\/two/,
    })
  ).toBeVisible();
  expect(mutations).toEqual([]);
});

test("files can be inspected without writing to the sandbox", async ({
  page,
}) => {
  const { mutations } = await mockControlWorkspace(page);
  await page.getByRole("button", { name: "Open files", exact: true }).click();
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await panel.getByRole("treeitem", { name: "page.tsx", exact: true }).click();
  await expect(panel.locator(".monaco-editor")).toContainText("Welcome home");
  await panel.getByRole("button", { name: "Back to files" }).click();
  await expect(
    panel.getByRole("treeitem", { name: "page.tsx", exact: true })
  ).toBeVisible();
  expect(mutations).toEqual([]);
});

test("workspace tools remain usable at tablet width in light mode", async ({
  page,
}, testInfo) => {
  await page.setViewportSize({ width: 820, height: 1024 });
  await mockControlWorkspace(page, "light");
  await page.getByRole("button", { name: "Open preview", exact: true }).click();
  await expect(page.locator("html")).toHaveClass(/light/);
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await expect(
    panel.getByTitle("Sandbox Preview", { exact: true })
  ).toHaveAttribute("src", "https://preview.example.test/one");
  for (const tool of ["preview", "changes", "files", "plan", "terminal"]) {
    await expect(
      page.getByRole("button", { name: `Open ${tool}`, exact: true })
    ).toBeInViewport({ ratio: 1 });
  }
  const bounds = await panel.boundingBox();
  expect(bounds!.width).toBeGreaterThan(400);
  expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(820);
  await page.screenshot({
    path: testInfo.outputPath("workspace-tablet-light.png"),
  });
  await page.getByRole("button", { name: "Close task tools" }).click();
  await expect(
    page.getByRole("button", { name: "Open preview", exact: true })
  ).toBeFocused();
  await expect(
    page.getByRole("textbox", { name: "Ask for follow-up changes" })
  ).toBeVisible();
});

test("failed change requests surface an error and can be retried", async ({
  page,
}) => {
  await mockControlWorkspace(page);
  let fail = true;
  await page.route("**/api/sandbox/sandbox-one/changes", (route) =>
    route.fulfill({
      status: fail ? 503 : 200,
      contentType: "application/json",
      body: JSON.stringify(
        fail
          ? { error: "Sandbox is reconnecting" }
          : {
              branch: "work/one",
              baseBranch: "main",
              ahead: 0,
              behind: 0,
              files: [],
            }
      ),
    })
  );
  await page.getByRole("button", { name: "Open changes", exact: true }).click();
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await expect(panel.getByRole("alert")).toHaveText("Sandbox is reconnecting");
  fail = false;
  await panel.getByRole("button", { name: "Retry changes" }).click();
  await expect(panel).toContainText("No uncommitted changes.");
  await expect(panel.getByRole("alert")).toHaveCount(0);
});

test("generated files open as task artifacts and can be reopened", async ({
  page,
}) => {
  const { sessions } = await mockControlWorkspace(page);
  sessions[0].messages.push({
    id: "attachment",
    role: "assistant",
    parts: [
      {
        type: "file",
        url: "data:text/plain,Ready%20to%20review",
        mediaType: "text/plain",
        filename: "handoff.txt",
      },
    ],
  });
  await page.reload();
  const panel = page.getByRole("complementary", { name: "Task tools" });
  await expect(
    panel.getByRole("link", { name: "Open handoff.txt" })
  ).toBeVisible();
  await page.getByRole("button", { name: "Close task tools" }).click();
  await expect(panel).toBeHidden();
  await page
    .getByRole("button", { name: "Open artifacts", exact: true })
    .click();
  await expect(
    panel.getByRole("link", { name: "Open handoff.txt" })
  ).toBeVisible();
});

test("compact navigation expands consistently after keyboard resizing", async ({
  page,
}) => {
  await page.setViewportSize({ width: 1600, height: 1000 });
  await mockControlWorkspace(page);
  const navigation = page.getByTestId("app-sidebar");
  await expect(navigation).toHaveAttribute("data-compact", "true");
  await page.getByRole("separator", { name: "Resize app navigation" }).focus();
  await page.keyboard.press("ArrowRight");
  expect((await navigation.boundingBox())!.width).toBeLessThan(100);
  await page
    .getByRole("button", { name: "Expand navigation", exact: true })
    .click();
  await expect(navigation).toHaveAttribute("data-compact", "false");
  await page
    .getByRole("button", { name: "Collapse navigation", exact: true })
    .click();
  await expect(navigation).toHaveAttribute("data-compact", "true");
});