tests/e2e/flows-pane-keyboard-nodes.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import { selectAppOption } from "./helpers/app-select";
import { AUTOMATION_MODEL_DEFAULT_TIMEOUT_MS } from "../../lib/workflows/automation-model-defaults";
import {
stubFlowsPage,
primaryModifier,
} from "./helpers/flows-pane-keyboard-fixtures";
import type { FlowNode } from "../../lib/types";
const defaultTimeoutSeconds = Math.round(
AUTOMATION_MODEL_DEFAULT_TIMEOUT_MS / 1000
);
const defaultTimeoutPlaceholder = `${defaultTimeoutSeconds} (default)`;
test("select all and escape only affect agent-node selection and preserve structural nodes", async ({
page,
}) => {
// Width must be >= 1520 so the flows container (minus ~240px sidebar) exceeds
// the 1280px dock-mode threshold.
await page.setViewportSize({ width: 1600, height: 900 });
await stubFlowsPage(page);
await page.goto("/alex/workflows");
await page.waitForLoadState("networkidle");
const selectedNodes = page.locator(".react-flow__node.selected");
const initialNodeCount = await page.locator(".react-flow__node").count();
await page.locator(".flows-pane-grid > section").click();
await page.keyboard.press(`${primaryModifier}+A`);
await expect(
page.locator(".react-flow__node.selected").filter({ hasText: "Reviewer A" })
).toHaveCount(1);
await expect(
page.locator(".react-flow__node.selected").filter({ hasText: "Reviewer B" })
).toHaveCount(1);
await expect(
page.locator(".react-flow__node.selected").filter({ hasText: "PR opened" })
).toHaveCount(0);
await expect(
page.locator(".react-flow__node.selected").filter({ hasText: "Done" })
).toHaveCount(0);
await page
.locator(".react-flow__node")
.filter({ hasText: "PR opened" })
.click();
await page.keyboard.press("Delete");
expect(
await page.locator(".react-flow__node").count()
).toBeGreaterThanOrEqual(initialNodeCount);
await expect(
page.locator(".react-flow__node").filter({ hasText: "PR opened" })
).toHaveCount(1);
await expect(
page.locator(".react-flow__node").filter({ hasText: "Done" })
).toHaveCount(1);
await page.keyboard.press("Escape");
await expect(selectedNodes).toHaveCount(0);
});
test("agent node overrides persist to the flow graph without mutating the base agent catalog", async ({
page,
}) => {
const { getFlow, getAgentRequestMethods } = await stubFlowsPage(page);
await page.goto("/alex/workflows");
await page.waitForLoadState("networkidle");
await page
.locator(".react-flow__node")
.filter({ hasText: "Reviewer A" })
.click();
await selectAppOption(page.getByLabel("Agent task"), "edit");
await selectAppOption(
page.getByLabel("Model", { exact: true }),
"minimax/minimax-m2.5"
);
await expect(page.getByLabel("Max steps override")).toHaveCount(0);
await page.screenshot({
path: "test-results/flow-agent-without-step-limit.png",
});
const timeoutOverride = page.getByLabel("Timeout override (seconds)");
await expect(timeoutOverride).toHaveAttribute(
"placeholder",
defaultTimeoutPlaceholder
);
await timeoutOverride.fill("18");
await page
.getByLabel("System prompt override")
.fill("Focus on correctness regressions before style issues.");
await page.keyboard.press(`${primaryModifier}+S`);
await expect
.poll(() => {
const agentNode = (getFlow().draft_graph.nodes as FlowNode[]).find(
(node) => node.id === "agent-a"
);
return agentNode?.data;
})
.toMatchObject({
label: "Reviewer A",
agentId: "agent-a",
role: "edit",
modelOverride: "minimax/minimax-m2.5",
timeoutMsOverride: 18000,
systemPromptOverride:
"Focus on correctness regressions before style issues.",
});
expect(
getAgentRequestMethods().every((method) => method === "GET")
).toBeTruthy();
});
test("agent nodes persist an available CLI harness and disable missing-key harnesses", async ({
page,
}) => {
// Width must be >= 1520 so the flows container (minus ~240px sidebar) exceeds
// the 1280px dock-mode threshold.
await page.setViewportSize({ width: 1600, height: 900 });
const { getFlow } = await stubFlowsPage(page);
await page.goto("/alex/workflows?tab=editor");
await page.waitForLoadState("networkidle");
const agentNode = page
.locator(".react-flow__node")
.filter({ hasText: "Reviewer A" });
await expect(
agentNode.getByTestId("flow-canvas-harness-icon-mogplex")
).toBeVisible();
await expect(agentNode).toContainText("Mogplex");
await expect(agentNode).not.toContainText("agent-a");
await agentNode.click();
const harnessGroup = page.getByRole("radiogroup", { name: "Harness" });
await expect(
harnessGroup.getByRole("radio", { name: "Mogplex" })
).toHaveAttribute("aria-checked", "true");
await expect(
harnessGroup.getByRole("radio", { name: "Codex" })
).toBeDisabled();
await harnessGroup.getByRole("radio", { name: "Claude Code" }).click();
await expect(
agentNode.getByTestId("flow-canvas-harness-icon-claude-code")
).toBeVisible();
await expect(agentNode).toContainText("Claude Code");
await expect(
page.getByText("Claude Code runs this node inside a fresh repo sandbox")
).toBeVisible();
await expect(page.getByLabel("Mogplex agent")).toHaveCount(0);
await page.keyboard.press(`${primaryModifier}+S`);
await expect
.poll(() => {
const agentNode = (getFlow().draft_graph.nodes as FlowNode[]).find(
(node) => node.id === "agent-a"
);
return agentNode?.data;
})
.toMatchObject({
label: "Reviewer A",
agentId: null,
harness: "claude-code",
});
});