Files
kandev/apps/web/e2e/tests/task/comment-run-not-queued.spec.ts
T

131 lines
4.7 KiB
TypeScript

import { type Page } from "@playwright/test";
import { test, expect } from "../../fixtures/test-base";
import type { SeedData } from "../../fixtures/test-base";
import type { ApiClient } from "../../helpers/api-client";
import { SessionPage } from "../../pages/session-page";
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
/** MCP script that creates plan content the agent can interact with. */
const PLAN_SCRIPT = [
'e2e:thinking("Creating plan...")',
"e2e:delay(100)",
'e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
"e2e:delay(100)",
'e2e:message("Plan created.")',
].join("\n");
/** Create a task with agent, navigate to session, wait for idle. */
async function seedTaskAndWaitForIdle(
testPage: Page,
apiClient: ApiClient,
seedData: SeedData,
title: string,
description = "/e2e:simple-message",
) {
const task = await apiClient.createTaskWithAgent(
seedData.workspaceId,
title,
seedData.agentProfileId,
{
description,
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
await testPage.goto(`/t/${task.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
await session.waitForChatIdle({ timeout: 30_000 });
return { session, taskId: task.id, sessionId: task.session_id! };
}
/**
* Open the plan comment popover, type a comment, and click Run.
* Returns the comment text so callers can assert on it.
*/
async function addPlanCommentAndRun(
testPage: Page,
session: SessionPage,
commentText: string,
): Promise<void> {
const editor = session.planPanel.locator(".ProseMirror");
await editor.click();
const modifier = process.platform === "darwin" ? "Meta" : "Control";
await testPage.keyboard.press(`${modifier}+a`);
await testPage.keyboard.press(`${modifier}+Shift+c`);
const textarea = testPage.locator('textarea[placeholder="Add your comment or instruction..."]');
await expect(textarea).toBeVisible({ timeout: 5_000 });
await textarea.fill(commentText);
const runBtn = testPage.getByRole("button", { name: "Run", exact: true });
await expect(runBtn).toBeVisible({ timeout: 5_000 });
await runBtn.click();
}
// ---------------------------------------------------------------------------
// Tests: Comment "Run" should send directly when agent is idle, not queue
// ---------------------------------------------------------------------------
test.describe("Comment run sends directly when agent is idle", () => {
test.describe.configure({ retries: 1 });
test("plan comment Run after agent was previously running sends directly, not queued", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(120_000);
// 1. Create a task that produces plan content. Agent runs and finishes.
const { session } = await seedTaskAndWaitForIdle(
testPage,
apiClient,
seedData,
"Comment queue bug test",
PLAN_SCRIPT,
);
// 2. Toggle plan mode — the plan panel should show content.
await session.togglePlanMode();
await expect(session.planModeInput()).toBeVisible({ timeout: 10_000 });
await expect(session.planPanel.getByText("Analyze requirements", { exact: false })).toBeVisible(
{
timeout: 15_000,
},
);
// 3. Run a slow command so the session goes through RUNNING -> WAITING_FOR_INPUT again.
// This ensures isAgentBusy was recently true.
await session.sendMessage("/slow 3s");
await expect(session.agentStatus()).toBeVisible({ timeout: 15_000 });
await expect(session.planModeInput()).toBeVisible({ timeout: 30_000 });
// 4. Agent is now idle. Add a plan comment and click Run.
await addPlanCommentAndRun(testPage, session, "Refactor step 1 to use dependency injection");
// 5. The comment should NOT be queued — no queue indicator should appear.
const queueIndicator = testPage.getByTitle("Cancel queued message");
await expect(queueIndicator).not.toBeVisible({ timeout: 3_000 });
// 6. The comment should appear in the chat as a direct message.
await expect(
session.chat.getByText("Refactor step 1 to use dependency injection", { exact: false }),
).toBeVisible({ timeout: 15_000 });
// 7. The agent should start processing (proves it was sent as message.add, not queued).
await expect(session.agentStatus()).toBeVisible({ timeout: 15_000 });
// 8. Wait for agent to complete.
await expect(session.planModeInput()).toBeVisible({ timeout: 30_000 });
});
});