Files
kandev/apps/web/e2e/tests/office/advanced-mode.spec.ts
T
2026-05-16 20:07:57 +01:00

205 lines
7.3 KiB
TypeScript

import { type Page } from "@playwright/test";
import { test as base, expect } from "../../fixtures/test-base";
import { OfficeApiClient } from "../../helpers/office-api-client";
/**
* E2E tests for the office advanced mode dockview layout.
*
* Covers:
* 1. Dockview panels render correctly (chat, files, terminal, changes)
* 2. Chat shows agent messages
* 3. Quick-chat workspace path is displayed (not skeleton)
* 4. Mode toggle between simple and advanced preserves state
* 5. Terminal connects to agent execution workspace
*/
type AdvancedModeFixtures = {
officeApi: OfficeApiClient;
advancedSeed: {
workspaceId: string;
agentId: string;
taskId: string;
};
};
const test = base.extend<{ testPage: Page }, AdvancedModeFixtures>({
officeApi: [
async ({ backend }, use) => {
await use(new OfficeApiClient(backend.baseUrl));
},
{ scope: "worker" },
],
advancedSeed: [
async ({ officeApi, seedData }, use) => {
const result = (await officeApi.completeOnboarding({
workspaceName: "Advanced Mode Workspace",
taskPrefix: "AM",
agentName: "CEO",
agentProfileId: seedData.agentProfileId,
executorPreference: "local_pc",
taskTitle: "present yourself",
taskDescription: "say your name",
})) as { workspaceId: string; agentId: string; projectId: string; taskId?: string };
if (!result.taskId) {
throw new Error("completeOnboarding did not return a taskId");
}
// Wait for the agent to leave the pre-launch states. The task
// state surfaces from the API in canonical lowercase form
// (`in_progress`, `in_review`, `done`, …); legacy SCREAMING_SNAKE_CASE
// values are accepted defensively. We only need the agent to have
// *started* — the test pages exercise the live session once the
// dockview mounts, they don't require a finished turn.
const launched = new Set([
"in_progress",
"in_review",
"done",
"completed",
"waiting_for_input",
"review",
]);
const deadline = Date.now() + 25_000;
while (Date.now() < deadline) {
const issue = await officeApi.getTask(result.taskId);
const raw = issue as Record<string, unknown>;
const inner = (raw.task as Record<string, unknown>) ?? raw;
const state = ((inner.state as string) ?? (inner.status as string) ?? "").toLowerCase();
if (state === "failed") throw new Error("Task entered FAILED state");
if (launched.has(state)) break;
await new Promise((r) => setTimeout(r, 500));
}
await use({
workspaceId: result.workspaceId,
agentId: result.agentId,
taskId: result.taskId,
});
},
{ scope: "worker" },
],
testPage: async ({ testPage: basePage, apiClient, advancedSeed, seedData }, use) => {
await apiClient.saveUserSettings({
workspace_id: advancedSeed.workspaceId,
workflow_filter_id: seedData.workflowId,
keyboard_shortcuts: {},
enable_preview_on_click: false,
sidebar_views: [],
});
await use(basePage);
},
});
/**
* Navigate to issue simple mode first (so sessions load), then switch to
* advanced mode. Direct navigation to ?mode=advanced can race with session
* loading, causing the page to render in simple mode.
*/
async function enterAdvancedMode(testPage: Page, taskId: string) {
// Navigate directly to ?mode=advanced. The page initially renders in simple
// mode while sessions load async. Once hasSession becomes true, React
// re-renders into TaskAdvancedMode with dockview.
await testPage.goto(`/office/tasks/${taskId}?mode=advanced`);
// Wait for dockview to render. The page may briefly show simple mode while
// sessions load — dockview appears once hasSession becomes true.
// The session-chat test-id comes from the dockview chat panel.
await expect(testPage.getByTestId("session-chat")).toBeVisible({ timeout: 30_000 });
}
test.describe("Office advanced mode", () => {
test.describe.configure({ retries: 1 });
test("dockview layout renders with chat, files, changes, and terminal panels", async ({
testPage,
advancedSeed,
}) => {
test.setTimeout(45_000);
await enterAdvancedMode(testPage, advancedSeed.taskId);
// Chat panel must be visible (dockview center group)
await expect(testPage.getByTestId("session-chat")).toBeVisible();
// Files panel must be visible (dockview right group)
await expect(testPage.getByTestId("files-panel")).toBeVisible({ timeout: 15_000 });
// Terminal panel must be visible (dockview right-bottom group)
await expect(testPage.getByTestId("terminal-panel")).toBeVisible({ timeout: 15_000 });
});
test("chat panel shows agent messages from completed session", async ({
testPage,
advancedSeed,
}) => {
test.setTimeout(45_000);
await enterAdvancedMode(testPage, advancedSeed.taskId);
// The chat panel should show messages from the completed agent session.
// The mock agent responds with a status message like "Environment prepared"
// or "Started agent" — look for any of those indicators.
const chatPanel = testPage.getByTestId("session-chat");
await expect(chatPanel).toBeVisible({ timeout: 10_000 });
// The chat should contain session messages (not the "No messages" empty state)
await expect(chatPanel.getByText("No messages yet")).not.toBeVisible({ timeout: 15_000 });
});
test("files panel shows workspace content", async ({ testPage, advancedSeed }) => {
test.setTimeout(45_000);
await enterAdvancedMode(testPage, advancedSeed.taskId);
// Wait for files panel to load with tree content
const filesPanel = testPage.getByTestId("files-panel");
await expect(filesPanel).toBeVisible({ timeout: 15_000 });
// Quick-chat workspaces have a .gitkeep file — the tree should show it
await expect(filesPanel.getByText(".gitkeep")).toBeVisible({ timeout: 15_000 });
});
test("terminal connects to agent execution workspace", async ({ testPage, advancedSeed }) => {
test.setTimeout(60_000);
await enterAdvancedMode(testPage, advancedSeed.taskId);
// Wait for the terminal panel to be visible
const terminalPanel = testPage.getByTestId("terminal-panel");
await expect(terminalPanel).toBeVisible({ timeout: 10_000 });
// Terminal should connect — "Connecting terminal..." should disappear
await expect(terminalPanel.getByText("Connecting terminal...")).not.toBeVisible({
timeout: 30_000,
});
});
test("navigating between simple and advanced mode preserves session", async ({
testPage,
advancedSeed,
}) => {
test.setTimeout(45_000);
// Start in advanced mode
await enterAdvancedMode(testPage, advancedSeed.taskId);
// Chat should show messages
const chatPanel = testPage.getByTestId("session-chat");
await expect(chatPanel).toBeVisible();
// Navigate back to simple mode
await testPage.goto(`/office/tasks/${advancedSeed.taskId}`);
// Verify simple mode — heading should be visible
await expect(testPage.getByRole("heading", { name: "present yourself" })).toBeVisible({
timeout: 10_000,
});
// Navigate to advanced mode again — session should still be there
await testPage.goto(`/office/tasks/${advancedSeed.taskId}?mode=advanced`);
await expect(testPage.getByTestId("session-chat")).toBeVisible({ timeout: 20_000 });
});
});