179 lines
6.5 KiB
TypeScript
179 lines
6.5 KiB
TypeScript
import { test, expect } from "../../fixtures/test-base";
|
|
import { SidebarTasksPage } from "../../pages/sidebar-tasks-page";
|
|
import { SessionPage } from "../../pages/session-page";
|
|
import { openBlockedTaskLoadingState } from "./task-loading-state-helpers";
|
|
|
|
test.describe("Task loading state", () => {
|
|
test("shows a spinner instead of a blank task detail pane while task data loads", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const unblockTaskDetailRequest = await openBlockedTaskLoadingState({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
title: "Task Loading State Anchor",
|
|
unresolvedTaskId: "unresolved-task-detail-desktop",
|
|
});
|
|
|
|
try {
|
|
await expect(testPage.getByTestId("task-loading-state")).toBeVisible({ timeout: 10_000 });
|
|
await expect(testPage.getByText("Loading task...")).toBeVisible();
|
|
} finally {
|
|
await unblockTaskDetailRequest();
|
|
}
|
|
});
|
|
|
|
test("keeps sibling tasks available after a missing task route", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const sibling = await apiClient.createTask(seedData.workspaceId, "Missing route sibling", {
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [seedData.repositoryId],
|
|
});
|
|
|
|
await testPage.goto(`/t/missing-task-route-desktop?workspaceId=${seedData.workspaceId}`);
|
|
await expect(testPage.getByTestId("task-load-error-state")).toBeVisible({ timeout: 10_000 });
|
|
await expect(testPage.getByTestId("task-unavailable-overview-link")).toBeVisible();
|
|
|
|
const sidebar = new SidebarTasksPage(testPage);
|
|
await expect(sidebar.row(sibling.id)).toBeVisible({ timeout: 10_000 });
|
|
await expect(testPage.getByText("No tasks yet.", { exact: true })).toHaveCount(0);
|
|
|
|
await sidebar.plainClick(sibling.id);
|
|
await expect(testPage).toHaveURL(new RegExp(`/t/${sibling.id}$`));
|
|
await expect(testPage.getByTestId("task-load-error-state")).toBeHidden({ timeout: 10_000 });
|
|
});
|
|
|
|
test("keeps a session-backed sibling active", async ({ testPage, apiClient, seedData }) => {
|
|
const sibling = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"Missing route session sibling",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: "/e2e:simple-message",
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [seedData.repositoryId],
|
|
},
|
|
);
|
|
|
|
if (!sibling.session_id) throw new Error("session-backed sibling has no session");
|
|
|
|
await testPage.goto(`/t/missing-task-route-session?workspaceId=${seedData.workspaceId}`);
|
|
await expect(testPage.getByTestId("task-load-error-state")).toBeVisible({ timeout: 10_000 });
|
|
|
|
const sidebar = new SidebarTasksPage(testPage);
|
|
await expect(sidebar.row(sibling.id)).toBeVisible({ timeout: 10_000 });
|
|
await sidebar.plainClick(sibling.id);
|
|
|
|
await expect(testPage).toHaveURL(new RegExp(`/t/${sibling.id}$`));
|
|
await expect(testPage.getByTestId("task-load-error-state")).toBeHidden({ timeout: 10_000 });
|
|
await expect(testPage.getByTestId("dockview-task-layout")).toBeVisible({ timeout: 10_000 });
|
|
await expect(sidebar.row(sibling.id)).toHaveAttribute("data-active", "true", {
|
|
timeout: 10_000,
|
|
});
|
|
});
|
|
|
|
test("restores the last selected session after an A to B to A switch", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
test.setTimeout(120_000);
|
|
|
|
const taskA = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"Session route sync task A",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: "/e2e:simple-message",
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [seedData.repositoryId],
|
|
},
|
|
);
|
|
const taskB = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"Session route sync task B",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: "/e2e:simple-message",
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [seedData.repositoryId],
|
|
},
|
|
);
|
|
|
|
if (!taskA.session_id || !taskB.session_id) {
|
|
throw new Error("session-backed route sync tasks have no primary session");
|
|
}
|
|
|
|
await testPage.goto(`/t/${taskA.id}`);
|
|
const session = new SessionPage(testPage);
|
|
await session.waitForLoad();
|
|
|
|
await session.openNewSessionDialog();
|
|
await expect(session.newSessionDialog()).toBeVisible({ timeout: 5_000 });
|
|
await session.newSessionPromptInput().fill("/e2e:simple-message");
|
|
await session.newSessionStartButton().click();
|
|
await expect(session.newSessionDialog()).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
let secondarySessionId: string | undefined;
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const { sessions } = await apiClient.listTaskSessions(taskA.id);
|
|
secondarySessionId = sessions.find((item) => item.id !== taskA.session_id)?.id;
|
|
return secondarySessionId ?? null;
|
|
},
|
|
{ timeout: 30_000, message: "Waiting for task A secondary session" },
|
|
)
|
|
.toBeTruthy();
|
|
|
|
await expect(session.sessionTabBySessionId(secondarySessionId!)).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
await session.sessionTabBySessionId(secondarySessionId!).click();
|
|
await expect
|
|
.poll(async () =>
|
|
testPage.evaluate(
|
|
() =>
|
|
(
|
|
window as unknown as {
|
|
__KANDEV_E2E_STORE__?: { getState: () => { tasks: { activeSessionId: string } } };
|
|
}
|
|
).__KANDEV_E2E_STORE__?.getState().tasks.activeSessionId,
|
|
),
|
|
)
|
|
.toBe(secondarySessionId);
|
|
|
|
await session.clickTaskInSidebar("Session route sync task B");
|
|
await expect(testPage).toHaveURL(new RegExp(`/t/${taskB.id}$`), { timeout: 15_000 });
|
|
await session.waitForLoad();
|
|
await expect(session.sessionTabBySessionId(taskB.session_id)).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
await session.clickTaskInSidebar("Session route sync task A");
|
|
await expect(testPage).toHaveURL(new RegExp(`/t/${taskA.id}$`), { timeout: 15_000 });
|
|
await session.waitForLoad();
|
|
await expect
|
|
.poll(async () =>
|
|
testPage.evaluate(
|
|
() =>
|
|
(
|
|
window as unknown as {
|
|
__KANDEV_E2E_STORE__?: { getState: () => { tasks: { activeSessionId: string } } };
|
|
}
|
|
).__KANDEV_E2E_STORE__?.getState().tasks.activeSessionId,
|
|
),
|
|
)
|
|
.toBe(secondarySessionId);
|
|
});
|
|
});
|