feat: open canvas setup dialog directly (#3587)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { expect, test } from "../../fixtures/test-base";
|
||||
import { waitForHttp } from "../../helpers/causal-waits";
|
||||
import { SessionPage } from "../../pages/session-page";
|
||||
import { expectTaskDescription, readTaskDescription } from "../../pages/task-description-editor";
|
||||
import type { ApiClient } from "../../helpers/api-client";
|
||||
import type { Page } from "@playwright/test";
|
||||
import {
|
||||
@@ -117,6 +118,26 @@ test.describe("Plugin-backed canvases on mobile", () => {
|
||||
localProfile!.name,
|
||||
);
|
||||
|
||||
const tasksBeforeCancel = await apiClient.listTasks(seedData.workspaceId);
|
||||
await dialog.getByTestId("task-title-input").fill("Cancelled canvas task");
|
||||
await dialog
|
||||
.getByTestId("task-description-input")
|
||||
.fill("This draft must not create a task or canvas.");
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).tap();
|
||||
await expect(dialog).toBeHidden();
|
||||
const tasksAfterCancel = await apiClient.listTasks(seedData.workspaceId);
|
||||
expect(tasksAfterCancel.tasks.map((task) => task.id)).toEqual(
|
||||
tasksBeforeCancel.tasks.map((task) => task.id),
|
||||
);
|
||||
|
||||
await testPage.getByTestId("settings-create-canvas").tap();
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByTestId("task-title-input")).toHaveValue("Create a canvas");
|
||||
await expectTaskDescription(
|
||||
dialog.getByTestId("task-description-input"),
|
||||
"Create a new Kandev canvas with a coordinator view that lists the existing tasks.\n\n@create-canvas",
|
||||
);
|
||||
|
||||
const agentSelector = dialog.getByTestId("agent-profile-selector");
|
||||
await expect(agentSelector).toBeEnabled();
|
||||
await agentSelector.tap();
|
||||
@@ -137,14 +158,10 @@ test.describe("Plugin-backed canvases on mobile", () => {
|
||||
// the option, so this remains deterministic under strict locators.
|
||||
await testPage.getByRole("button", { name: "E2E Workflow", exact: true }).last().tap();
|
||||
|
||||
const defaultPrompt = await dialog.getByTestId("task-description-input").inputValue();
|
||||
for (const tool of [
|
||||
"create_canvas_kandev",
|
||||
"read_canvas_authoring_skill_kandev",
|
||||
"publish_canvas_kandev",
|
||||
]) {
|
||||
expect(defaultPrompt, `mobile preset is missing ${tool}`).toContain(tool);
|
||||
}
|
||||
const defaultPrompt = await readTaskDescription(dialog.getByTestId("task-description-input"));
|
||||
expect(defaultPrompt).toBe(
|
||||
"Create a new Kandev canvas with a coordinator view that lists the existing tasks.\n\n@create-canvas",
|
||||
);
|
||||
expect(defaultPrompt).not.toContain("e2e:mcp:");
|
||||
await expect
|
||||
.poll(() =>
|
||||
@@ -160,12 +177,96 @@ test.describe("Plugin-backed canvases on mobile", () => {
|
||||
summary: "Canvas created through the guided settings task flow.",
|
||||
})})`,
|
||||
'e2e:message("Canvas created from settings.")',
|
||||
"Create a canvas that shows the current task list.",
|
||||
...Array.from(
|
||||
{ length: 20 },
|
||||
(_, index) =>
|
||||
`Canvas detail ${index + 1}: keep this longer edited goal inside the form scroll area.`,
|
||||
),
|
||||
"",
|
||||
"@create-canvas",
|
||||
].join("\n");
|
||||
await dialog.getByTestId("task-title-input").fill(taskTitle);
|
||||
await dialog.getByTestId("task-description-input").fill(description);
|
||||
await dialog.getByTestId("task-create-advanced-settings-trigger").tap();
|
||||
|
||||
const formBody = dialog.getByTestId("task-create-form-body");
|
||||
const formGeometry = await testPage.evaluate(() => {
|
||||
const readRect = (testId: string) => {
|
||||
const rect = document.querySelector(`[data-testid="${testId}"]`)?.getBoundingClientRect();
|
||||
if (!rect) return null;
|
||||
return {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
height: rect.height,
|
||||
};
|
||||
};
|
||||
const viewport = window.visualViewport;
|
||||
return {
|
||||
viewport: {
|
||||
left: viewport?.offsetLeft ?? 0,
|
||||
top: viewport?.offsetTop ?? 0,
|
||||
right: (viewport?.offsetLeft ?? 0) + (viewport?.width ?? window.innerWidth),
|
||||
bottom: (viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight),
|
||||
},
|
||||
dialog: readRect("create-task-dialog"),
|
||||
body: readRect("task-create-form-body"),
|
||||
footer: readRect("task-create-dialog-footer"),
|
||||
cancel: readRect("submit-cancel"),
|
||||
start: readRect("submit-start-agent"),
|
||||
};
|
||||
});
|
||||
expect(formGeometry.dialog).not.toBeNull();
|
||||
expect(formGeometry.body).not.toBeNull();
|
||||
expect(formGeometry.footer).not.toBeNull();
|
||||
expect(formGeometry.cancel?.height).toBeGreaterThanOrEqual(44);
|
||||
expect(formGeometry.start?.height).toBeGreaterThanOrEqual(44);
|
||||
expect(formGeometry.dialog!.left).toBeGreaterThanOrEqual(formGeometry.viewport.left - 1);
|
||||
expect(formGeometry.dialog!.top).toBeGreaterThanOrEqual(formGeometry.viewport.top - 1);
|
||||
expect(formGeometry.dialog!.right).toBeLessThanOrEqual(formGeometry.viewport.right + 1);
|
||||
expect(formGeometry.dialog!.bottom).toBeLessThanOrEqual(formGeometry.viewport.bottom + 1);
|
||||
expect(formGeometry.body!.bottom).toBeLessThanOrEqual(formGeometry.footer!.top + 1);
|
||||
expect(formGeometry.footer!.bottom).toBeLessThanOrEqual(formGeometry.dialog!.bottom + 1);
|
||||
|
||||
const scrollMetrics = await formBody.evaluate((element) => ({
|
||||
clientHeight: element.clientHeight,
|
||||
scrollHeight: element.scrollHeight,
|
||||
scrollTop: element.scrollTop,
|
||||
}));
|
||||
expect(scrollMetrics.scrollHeight).toBeGreaterThan(scrollMetrics.clientHeight);
|
||||
await formBody.evaluate((element) => {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
});
|
||||
await expect
|
||||
.poll(() => formBody.evaluate((element) => element.scrollTop))
|
||||
.toBeGreaterThan(scrollMetrics.scrollTop);
|
||||
|
||||
let failNextTaskCreate = true;
|
||||
await testPage.route("**/api/v1/tasks", async (route) => {
|
||||
if (failNextTaskCreate && route.request().method() === "POST") {
|
||||
failNextTaskCreate = false;
|
||||
await route.fulfill({
|
||||
status: 500,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ error: "controlled canvas task creation failure" }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await route.continue();
|
||||
});
|
||||
|
||||
const startAgent = dialog.getByTestId("submit-start-agent");
|
||||
await expect(startAgent).toBeEnabled();
|
||||
await startAgent.tap();
|
||||
await expect(
|
||||
testPage
|
||||
.locator('[data-testid="toast-message"]')
|
||||
.filter({ hasText: "Failed to create task" }),
|
||||
).toBeVisible();
|
||||
await expectTaskDescription(dialog.getByTestId("task-description-input"), description);
|
||||
|
||||
const responsePromise = waitForHttp(testPage, "POST", /\/api\/v1\/tasks$/);
|
||||
await startAgent.tap();
|
||||
const response = await responsePromise;
|
||||
@@ -214,6 +315,7 @@ test.describe("Plugin-backed canvases on mobile", () => {
|
||||
sessions.find((candidate) => candidate.id === taskSessionId)?.executor_profile_id,
|
||||
).toBe(localProfile!.id);
|
||||
} finally {
|
||||
await testPage.unroute("**/api/v1/tasks").catch(() => undefined);
|
||||
await Promise.all(canvasIds.map((canvasId) => removeCanvas(apiClient, canvasId)));
|
||||
if (alternateWorkflowId)
|
||||
await apiClient.deleteWorkflow(alternateWorkflowId).catch(() => undefined);
|
||||
|
||||
Reference in New Issue
Block a user