Files
kandev/apps/web/e2e/tests/session/agent-error-indicator.spec.ts
T
2026-09-14 09:28:11 +01:00

130 lines
5.1 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
import { waitForSessionDone } from "../../helpers/session";
import { SessionPage } from "../../pages/session-page";
import {
seedWorkflowResetFailure,
WORKFLOW_RESET_ERROR_DETAILS,
WORKFLOW_RESET_ERROR_MESSAGE,
} from "./workflow-reset-error-helpers";
const ERROR_MESSAGE = "peer disconnected before response";
test.describe("Task agent error indicator", () => {
test("shows the last recoverable agent error in the sidebar and chat", async ({
testPage,
apiClient,
seedData,
}) => {
const taskTitle = `Recoverable Agent Error ${Date.now()}`;
// Stamp the error in the future so the seeded agent messages from
// /e2e:simple-message (created_at = now) are NOT considered "after" the
// error — otherwise the sidebar icon auto-hides before we can assert it.
const ERROR_OCCURRED_AT = new Date(Date.now() + 60 * 60 * 1000).toISOString();
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
taskTitle,
seedData.agentProfileId,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
if (!task.session_id) throw new Error("createTaskWithAgent did not return a session_id");
await waitForSessionDone(apiClient, task.id, task.session_id, "Waiting for seeded session");
await apiClient.seedTaskSession(task.id, {
state: "WAITING_FOR_INPUT",
sessionId: task.session_id,
agentProfileId: seedData.agentProfileId,
metadata: {
last_agent_error: {
message: ERROR_MESSAGE,
occurred_at: ERROR_OCCURRED_AT,
agent_execution_id: "agent-exec-e2e",
},
},
});
await expect
.poll(async () => {
const { sessions } = await apiClient.listTaskSessions(task.id);
const session = sessions.find((item) => item.id === task.session_id);
const error = session?.metadata?.last_agent_error as { message?: string } | undefined;
return error?.message ?? "";
})
.toBe(ERROR_MESSAGE);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
// .first() - desktop and mobile sidebars can both be mounted in this layout.
const taskRow = session.sidebarTaskItem(taskTitle).first();
const errorIcon = taskRow.getByTestId("task-agent-error-icon");
await expect(errorIcon).toBeVisible({ timeout: 15_000 });
const notice = testPage.getByTestId("last-agent-error-notice");
await expect(notice).toBeVisible({ timeout: 15_000 });
await expect(notice).toContainText("Previous agent error");
await expect(notice).toContainText(ERROR_MESSAGE);
await errorIcon.hover();
await expect(testPage.getByRole("tooltip")).toContainText(ERROR_MESSAGE);
// Dismissing the chat notice should also clear the sidebar icon. Both
// surfaces share the dismissal state through the UI store and the server
// persists the dismissal for other browsers.
await notice.getByRole("button", { name: "Hide previous agent error" }).click();
await expect(notice).toBeHidden();
await expect(errorIcon).toBeHidden();
await expect
.poll(async () => {
const { sessions } = await apiClient.listTaskSessions(task.id);
const sessionMeta = sessions.find((item) => item.id === task.session_id)?.metadata;
const error = sessionMeta?.last_agent_error as { dismissed_at?: string } | undefined;
return error?.dismissed_at ?? "";
})
.not.toBe("");
await testPage.evaluate(() => window.localStorage.removeItem("kandev.dismissedAgentErrors"));
await testPage.reload();
await session.waitForLoad();
await expect(testPage.getByTestId("last-agent-error-notice")).toBeHidden();
await expect(
session.activeSidebarTaskItem(taskTitle).getByTestId("task-agent-error-icon"),
).toBeHidden();
});
test("keeps a workflow reset failure visible after reload", async ({
testPage,
apiClient,
seedData,
}) => {
const { task } = await seedWorkflowResetFailure(
apiClient,
seedData,
`Workflow Reset Failure ${Date.now()}`,
);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
const notice = session.activeChat().getByTestId("last-agent-error-notice");
await expect(notice).toBeVisible({ timeout: 15_000 });
await expect(notice).toContainText("Previous agent error");
await expect(notice).toContainText(WORKFLOW_RESET_ERROR_MESSAGE);
await testPage.reload();
await session.waitForLoad();
const reloadedNotice = session.activeChat().getByTestId("last-agent-error-notice");
await expect(reloadedNotice).toBeVisible({ timeout: 15_000 });
await expect(reloadedNotice).toContainText("Context reset failed");
await expect(reloadedNotice).toContainText("workflow step prompt did not start");
await reloadedNotice.locator("summary").click();
await expect(reloadedNotice).toContainText(WORKFLOW_RESET_ERROR_DETAILS);
});
});