310 lines
12 KiB
TypeScript
310 lines
12 KiB
TypeScript
import { test, expect } from "../../fixtures/test-base";
|
|
import { useRegularMode } from "../../helpers/regular-mode";
|
|
import { KanbanPage } from "../../pages/kanban-page";
|
|
import { SessionPage } from "../../pages/session-page";
|
|
|
|
// Exercises the regular task-create dialog (New Task in the sidebar); run with office off.
|
|
useRegularMode();
|
|
|
|
test.describe("Workflow start step placement", () => {
|
|
test.describe("without explicit start step (Todo -> In Progress -> Done)", () => {
|
|
test("task lands in first step by position (Todo)", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const workflow = await apiClient.createWorkflow(
|
|
seedData.workspaceId,
|
|
"No Start Step Workflow",
|
|
);
|
|
const todoStep = await apiClient.createWorkflowStep(workflow.id, "Todo", 0);
|
|
await apiClient.createWorkflowStep(workflow.id, "In Progress", 1);
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 2);
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// Create task without explicit step — should resolve to position 0 (Todo)
|
|
await apiClient.createTask(seedData.workspaceId, "No Start Step Task", {
|
|
workflow_id: workflow.id,
|
|
});
|
|
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
const card = kanban.taskCardInColumn("No Start Step Task", todoStep.id);
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("plan mode task also lands in first step (Todo)", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const workflow = await apiClient.createWorkflow(
|
|
seedData.workspaceId,
|
|
"No Start Step Plan Workflow",
|
|
);
|
|
const todoStep = await apiClient.createWorkflowStep(workflow.id, "Todo", 0);
|
|
await apiClient.createWorkflowStep(workflow.id, "In Progress", 1);
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 2);
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// plan_mode task with no explicit step — both modes resolve to position 0
|
|
await apiClient.createTask(seedData.workspaceId, "Plan No Start Step Task", {
|
|
workflow_id: workflow.id,
|
|
plan_mode: true,
|
|
});
|
|
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
const card = kanban.taskCardInColumn("Plan No Start Step Task", todoStep.id);
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|
|
|
|
test.describe("with explicit start step (Todo -> In Progress [start] -> Done)", () => {
|
|
test("task lands on start step (In Progress)", async ({ testPage, apiClient, seedData }) => {
|
|
const workflow = await apiClient.createWorkflow(
|
|
seedData.workspaceId,
|
|
"Explicit Start Step Workflow",
|
|
);
|
|
await apiClient.createWorkflowStep(workflow.id, "Todo", 0);
|
|
const inProgressStep = await apiClient.createWorkflowStep(workflow.id, "In Progress", 1, {
|
|
is_start_step: true,
|
|
});
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 2);
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// No plan_mode, no explicit step — should resolve via is_start_step (In Progress)
|
|
await apiClient.createTask(seedData.workspaceId, "Start Step Task", {
|
|
workflow_id: workflow.id,
|
|
});
|
|
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
const card = kanban.taskCardInColumn("Start Step Task", inProgressStep.id);
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("plan mode task lands in first step (Todo), ignoring start step", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const workflow = await apiClient.createWorkflow(
|
|
seedData.workspaceId,
|
|
"Plan Mode Start Step Workflow",
|
|
);
|
|
const todoStep = await apiClient.createWorkflowStep(workflow.id, "Todo", 0);
|
|
await apiClient.createWorkflowStep(workflow.id, "In Progress", 1, {
|
|
is_start_step: true,
|
|
});
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 2);
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// plan_mode: true — should ignore is_start_step and use position 0 (Todo)
|
|
await apiClient.createTask(seedData.workspaceId, "Plan Mode Start Step Task", {
|
|
workflow_id: workflow.id,
|
|
plan_mode: true,
|
|
});
|
|
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
const card = kanban.taskCardInColumn("Plan Mode Start Step Task", todoStep.id);
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Full plan-mode-to-agent lifecycle:
|
|
*
|
|
* Workflow: Todo (on_turn_start: move_to_next) → In Progress (on_turn_complete: move_to_next) → Done
|
|
*
|
|
* 1. Create task via dialog in plan mode (title only, no description)
|
|
* 2. Session page opens with plan panel visible
|
|
* 3. Toggle off plan mode → plan panel disappears
|
|
* 4. Send a delayed mock script to start the agent
|
|
* 5. on_turn_start fires on Todo → task moves to In Progress (assert before response)
|
|
* 6. Agent responds after 3s delay → on_turn_complete fires → task moves to Done
|
|
*/
|
|
test("plan mode to agent: create in plan mode, toggle off, send message, cascades through steps", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const workflow = await apiClient.createWorkflow(seedData.workspaceId, "Plan To Agent Workflow");
|
|
|
|
const todoStep = await apiClient.createWorkflowStep(workflow.id, "Todo", 0);
|
|
const inProgressStep = await apiClient.createWorkflowStep(workflow.id, "In Progress", 1);
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 2);
|
|
|
|
// Todo: move to next step when agent turn starts
|
|
await apiClient.updateWorkflowStep(todoStep.id, {
|
|
events: {
|
|
on_turn_start: [{ type: "move_to_next" }],
|
|
},
|
|
});
|
|
|
|
// In Progress: move to next step when agent turn completes
|
|
await apiClient.updateWorkflowStep(inProgressStep.id, {
|
|
events: {
|
|
on_turn_complete: [{ type: "move_to_next" }],
|
|
},
|
|
});
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// --- Create task via dialog in plan mode ---
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
await kanban.createTaskButton.first().click();
|
|
const dialog = testPage.getByTestId("create-task-dialog");
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Fill title only — no description triggers plan mode as the primary submit action
|
|
await testPage.getByTestId("task-title-input").fill("Plan To Agent Task");
|
|
|
|
// The default submit button shows "Start Plan Mode" when there's no description.
|
|
// It's a type="submit" button so clicking it submits the form.
|
|
const submitBtn = dialog.getByRole("button", { name: /Start Plan Mode/ });
|
|
await expect(submitBtn).toBeEnabled({ timeout: 10_000 });
|
|
await submitBtn.click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
// Navigates to session page with plan layout
|
|
await expect(testPage).toHaveURL(/\/t\//, { timeout: 15_000 });
|
|
|
|
const session = new SessionPage(testPage);
|
|
await session.waitForLoad();
|
|
|
|
// Plan panel is visible (plan layout was activated)
|
|
await expect(session.planPanel).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Stepper shows Todo as current step (task starts at position 0)
|
|
await expect(session.stepperStep("Todo")).toHaveAttribute("aria-current", "step", {
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// --- Toggle off plan mode ---
|
|
// Wait for the editor to be fully interactive before sending the shortcut
|
|
await testPage.waitForTimeout(1_000);
|
|
await session.togglePlanMode();
|
|
await expect(session.planPanel).not.toBeVisible({ timeout: 15_000 });
|
|
|
|
// --- Send message to start the agent ---
|
|
// Use script mode with a delay so we can observe the In Progress intermediate state.
|
|
// on_turn_start fires immediately → task moves to In Progress.
|
|
// The 3s delay gives us time to assert before on_turn_complete fires.
|
|
await session.sendMessage('e2e:delay(3000)\ne2e:message("delayed mock response")');
|
|
|
|
// on_turn_start fires on Todo → task moves to In Progress
|
|
await expect(session.stepperStep("In Progress")).toHaveAttribute("aria-current", "step", {
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// Agent responds after the delay
|
|
await expect(session.chat.getByText("delayed mock response", { exact: false })).toBeVisible({
|
|
timeout: 30_000,
|
|
});
|
|
|
|
// on_turn_complete fires on In Progress → task moves to Done
|
|
await expect(session.stepperStep("Done")).toHaveAttribute("aria-current", "step", {
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// Session transitions to idle
|
|
await session.waitForChatIdle({ timeout: 15_000 });
|
|
});
|
|
|
|
/**
|
|
* Plan mode bypasses is_start_step — task lands in Backlog (position 0):
|
|
*
|
|
* Workflow: Backlog → In Progress [start] (auto_start_agent) → Review → Done
|
|
*
|
|
* 1. Create task via dialog in plan mode (title only, no description)
|
|
* 2. Session page opens with plan panel visible
|
|
* 3. Task lands in Backlog, not In Progress — plan mode ignores is_start_step
|
|
*/
|
|
test("plan mode with start step: task lands in Backlog, not In Progress", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
const workflow = await apiClient.createWorkflow(seedData.workspaceId, "Plan Mode Dev Workflow");
|
|
|
|
const backlogStep = await apiClient.createWorkflowStep(workflow.id, "Backlog", 0);
|
|
await apiClient.createWorkflowStep(workflow.id, "In Progress", 1, { is_start_step: true });
|
|
await apiClient.createWorkflowStep(workflow.id, "Review", 2);
|
|
await apiClient.createWorkflowStep(workflow.id, "Done", 3);
|
|
|
|
await apiClient.saveUserSettings({
|
|
workspace_id: seedData.workspaceId,
|
|
workflow_filter_id: workflow.id,
|
|
enable_preview_on_click: false,
|
|
});
|
|
|
|
// --- Create task via dialog in plan mode ---
|
|
const kanban = new KanbanPage(testPage);
|
|
await kanban.goto();
|
|
|
|
await kanban.createTaskButton.first().click();
|
|
const dialog = testPage.getByTestId("create-task-dialog");
|
|
await expect(dialog).toBeVisible();
|
|
|
|
// Fill title only — "Start Plan Mode" becomes the default submit action
|
|
await testPage.getByTestId("task-title-input").fill("Plan Dev Task");
|
|
|
|
const submitBtn = dialog.getByRole("button", { name: /Start Plan Mode/ });
|
|
await expect(submitBtn).toBeEnabled({ timeout: 10_000 });
|
|
await submitBtn.click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 10_000 });
|
|
|
|
// Navigates to session page with plan layout
|
|
await expect(testPage).toHaveURL(/\/t\//, { timeout: 15_000 });
|
|
|
|
const session = new SessionPage(testPage);
|
|
await session.waitForLoad();
|
|
|
|
// Plan panel is visible
|
|
await expect(session.planPanel).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Stepper shows Backlog as current step — plan mode ignores is_start_step
|
|
await expect(session.stepperStep("Backlog")).toHaveAttribute("aria-current", "step", {
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// Task card is in the Backlog column on the kanban board
|
|
await testPage.goto("/");
|
|
await kanban.board.waitFor({ state: "visible" });
|
|
const card = kanban.taskCardInColumn("Plan Dev Task", backlogStep.id);
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|