Files
kandev/apps/web/e2e/tests/workflow/workflow-move-preview.spec.ts
T
2026-09-16 11:16:47 +01:00

145 lines
5.9 KiB
TypeScript

import { expectPreviewFooter } from "./workflow-move-preview-assertions";
import type { Page } from "@playwright/test";
import { expect, test } from "../../fixtures/test-base";
import { SessionPage } from "../../pages/session-page";
import {
createWorkflowAgentProfiles,
waitForWorkflowProfileSession,
} from "./workflow-agent-switch-helpers";
import { seedMoveOverrideFixture } from "./workflow-step-move-overrides-helpers";
function waitForPreviewResponse(page: Page, taskId: string) {
return page.waitForResponse(
(response) =>
response.request().method() === "POST" &&
new URL(response.url()).pathname === `/api/v1/tasks/${taskId}/move-preview` &&
response.ok(),
);
}
async function openStepPreview(page: Page, taskId: string, stepName: string) {
const response = waitForPreviewResponse(page, taskId);
await page.getByTestId(`workflow-step-${stepName}`).hover();
const popover = page.locator('[data-testid="workflow-step-popover"]:visible');
await expect(popover).toBeVisible();
await expect(popover.getByTestId("workflow-move-preview")).toBeVisible({ timeout: 15_000 });
return { popover, preview: await response.then((value) => value.json()) };
}
test.describe("Workflow move preview", () => {
test("shows a retained session model override before an in-place move", async ({
testPage,
apiClient,
seedData,
}) => {
const fixture = await seedMoveOverrideFixture(
testPage,
apiClient,
seedData,
"Desktop Move Preview Override",
);
await apiClient.setSessionModel(fixture.primarySessionId, "mock-slow");
await testPage.reload();
await fixture.session.waitForLoad();
const { popover, preview } = await openStepPreview(testPage, fixture.taskId, "Verify");
await expectPreviewFooter(popover, "workflow-step-move-here");
expect(preview.outcome).toBe("reuse_current");
expect(preview.recipient.session_id).toBe(fixture.primarySessionId);
expect(preview.model.after.label).toBe("mock-slow");
expect(preview.model.after_source).toBe("override");
await expect(popover.getByTestId("workflow-move-preview")).toContainText("override retained");
const moveRequest = testPage.waitForRequest(
(request) =>
request.method() === "POST" &&
new URL(request.url()).pathname === `/api/v1/tasks/${fixture.taskId}/move`,
);
await popover.getByTestId("workflow-step-move-here").click();
expect((await moveRequest).postDataJSON()).toMatchObject({
workflow_step_id: fixture.targetStepId,
});
await expect
.poll(() => apiClient.getTask(fixture.taskId).then((task) => task.primary_session_id))
.toBe(fixture.primarySessionId);
});
test("matches other-session reuse and fresh-session creation", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(120_000);
const { agentId, profileA, profileB } = await createWorkflowAgentProfiles(apiClient);
const profileC = await apiClient.createAgentProfile(agentId, "Profile C (new)", {
model: "mock-fast",
});
const workflow = await apiClient.createWorkflow(seedData.workspaceId, "Move Preview Routing");
const source = await apiClient.createWorkflowStep(workflow.id, "Source", 0, {
is_start_step: true,
agent_profile_id: profileA.id,
events: { on_enter: [{ type: "auto_start_agent" }] },
});
const bridge = await apiClient.createWorkflowStep(workflow.id, "Bridge", 1, {
agent_profile_id: profileB.id,
events: { on_enter: [{ type: "auto_start_agent" }] },
});
await apiClient.createWorkflowStep(workflow.id, "Return A", 2, {
agent_profile_id: profileA.id,
events: { on_enter: [{ type: "auto_start_agent" }] },
});
await apiClient.createWorkflowStep(workflow.id, "Fresh C", 3, {
agent_profile_id: profileC.id,
profile_session_start_policy: "new",
events: { on_enter: [{ type: "auto_start_agent" }] },
});
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Move Preview Routing Task",
profileA.id,
{
workflow_id: workflow.id,
workflow_step_id: source.id,
repository_ids: [seedData.repositoryId],
},
);
const initialSessionId = await waitForWorkflowProfileSession(apiClient, task.id, profileA.id);
await apiClient.moveTask(task.id, workflow.id, bridge.id);
await waitForWorkflowProfileSession(apiClient, task.id, profileB.id);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
const returnPreview = await openStepPreview(testPage, task.id, "Return A");
expect(returnPreview.preview.outcome).toBe("reuse_other");
expect(returnPreview.preview.recipient.session_id).toBe(initialSessionId);
expect(returnPreview.preview.model.after.known).toBe(true);
const returnMove = testPage.waitForRequest(
(request) =>
request.method() === "POST" &&
new URL(request.url()).pathname === `/api/v1/tasks/${task.id}/move`,
);
await returnPreview.popover.getByTestId("workflow-step-move-here").click();
await returnMove;
await expect
.poll(() => apiClient.getTask(task.id).then((current) => current.primary_session_id))
.toBe(initialSessionId);
const freshPreview = await openStepPreview(testPage, task.id, "Fresh C");
expect(freshPreview.preview.outcome).toBe("create_new");
expect(freshPreview.preview.recipient.session_id ?? "").toBe("");
expect(freshPreview.preview.model.after.known).toBe(true);
const freshMove = testPage.waitForRequest(
(request) =>
request.method() === "POST" &&
new URL(request.url()).pathname === `/api/v1/tasks/${task.id}/move`,
);
await freshPreview.popover.getByTestId("workflow-step-move-here").click();
await freshMove;
const freshSessionId = await waitForWorkflowProfileSession(apiClient, task.id, profileC.id);
expect(freshSessionId).not.toBe(initialSessionId);
});
});