Files
kandev/apps/web/e2e/tests/settings/agent-profile-cli-flags.spec.ts
T
2026-07-24 16:36:44 +01:00

175 lines
7.4 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
/**
* Verifies the unified CLI-flags feature on agent profiles:
*
* - The profile editor renders a "Agent CLI flags" section.
* - Users can add a custom flag via the form, save, and reload; the flag
* persists in the DB (assert via the PATCH round-trip).
* - Toggling a flag off persists across save/reload.
* - Removing a flag persists across save/reload.
*
* Full argv-assembly (tokenisation + launch path) is covered by
* backend unit tests in apps/backend/internal/agent/lifecycle — we don't
* exercise a full task launch here because that's both slow and redundant.
*/
test.describe("Agent profile — CLI flags", () => {
test("cli-flags section renders on profile editor", async ({ testPage, apiClient }) => {
test.setTimeout(60_000);
const { agents } = await apiClient.listAgents();
const agent = agents[0];
const profileId = agent.profiles[0].id;
await testPage.goto(`/settings/agents/${agent.name}/profiles/${profileId}`);
// The Agent CLI flags card is always rendered on the profile editor.
const field = testPage.getByTestId("custom-cli-flags-card");
await expect(field).toBeVisible({ timeout: 15_000 });
// Header and helper copy anchoring the section for the user.
await expect(field).toContainText(/Agent CLI flags/i);
await expect(field).toContainText(/Flags passed to the agent CLI/i);
// The Add form inputs and button are always available.
await expect(testPage.getByTestId("cli-flag-new-flag-input")).toBeVisible();
await expect(testPage.getByTestId("cli-flag-add-button")).toBeVisible();
});
test("adding a custom flag persists across reload", async ({ testPage, apiClient }) => {
test.setTimeout(90_000);
// Create a fresh profile so we don't pollute other tests' fixtures.
const { agents } = await apiClient.listAgents();
const agent = agents[0];
const profile = await apiClient.createAgentProfile(agent.id, "CLI flags test", {
model: "mock-fast",
});
try {
await testPage.goto(`/settings/agents/${agent.name}/profiles/${profile.id}`);
await expect(testPage.getByTestId("custom-cli-flags-card")).toBeVisible({ timeout: 15_000 });
// Add a custom flag via the UI.
const flagText = "--my-custom-flag=value";
await testPage.getByTestId("cli-flag-new-flag-input").fill(flagText);
await testPage.getByTestId("cli-flag-add-button").click();
// The row should appear immediately, enabled by default. Flag text lives
// in an <input> element; use toHaveValue on the last flag-input because
// the newly added flag is always appended last.
await expect(testPage.locator('[data-testid^="cli-flag-flag-"]').last()).toHaveValue(
flagText,
);
// Save via the dirty-state save button; wait for unsaved badge to clear.
const saveButton = testPage.getByRole("button", { name: /^Save( changes)?$/i }).first();
await expect(saveButton).toBeEnabled({ timeout: 10_000 });
await saveButton.click();
await expect(testPage.getByText(/unsaved changes/i)).toBeHidden({ timeout: 15_000 });
// Reload — the row must still be there.
await testPage.reload();
await expect(testPage.getByTestId("custom-cli-flags-card")).toBeVisible({ timeout: 15_000 });
await expect(testPage.locator('[data-testid^="cli-flag-flag-"]').last()).toHaveValue(
flagText,
);
// Direct DB-path verification: fetch the profile via the API and assert
// the normalized CLI flags contain our entry, enabled.
const stored = await apiClient.getAgentProfile(profile.id);
const found = stored.cliFlags.find((f) => f.flag === flagText);
expect(found, `cliFlags should include ${flagText}`).toBeDefined();
expect(found?.enabled).toBe(true);
} finally {
await apiClient.deleteAgentProfile(profile.id, true);
}
});
test("toggling a flag off persists across reload", async ({ testPage, apiClient }) => {
test.setTimeout(90_000);
const { agents } = await apiClient.listAgents();
const agent = agents[0];
// Seed a profile with one enabled flag via the API so the UI has
// something to toggle off.
const profile = await apiClient.createAgentProfile(agent.id, "CLI flags toggle test", {
model: "mock-fast",
cli_flags: [{ description: "toggle target", flag: "--toggle-me", enabled: true }],
});
try {
await testPage.goto(`/settings/agents/${agent.name}/profiles/${profile.id}`);
await expect(testPage.getByTestId("custom-cli-flags-card")).toBeVisible({ timeout: 15_000 });
// The first row should be enabled; toggle it off.
const toggle = testPage.getByTestId("cli-flag-enabled-0");
await expect(toggle).toHaveAttribute("data-state", "checked");
await toggle.click();
await expect(toggle).toHaveAttribute("data-state", "unchecked");
const saveButton = testPage.getByRole("button", { name: /^Save( changes)?$/i }).first();
await expect(saveButton).toBeEnabled({ timeout: 10_000 });
await saveButton.click();
await expect(testPage.getByText(/unsaved changes/i)).toBeHidden({ timeout: 15_000 });
// Reload and confirm the toggle remained off.
await testPage.reload();
await expect(testPage.getByTestId("cli-flag-enabled-0")).toHaveAttribute(
"data-state",
"unchecked",
{ timeout: 15_000 },
);
const stored = await apiClient.getAgentProfile(profile.id);
const found = stored.cliFlags.find((f) => f.flag === "--toggle-me");
expect(found?.enabled).toBe(false);
} finally {
await apiClient.deleteAgentProfile(profile.id, true);
}
});
test("removing a flag persists across reload", async ({ testPage, apiClient }) => {
test.setTimeout(90_000);
const { agents } = await apiClient.listAgents();
const agent = agents[0];
const profile = await apiClient.createAgentProfile(agent.id, "CLI flags remove test", {
model: "mock-fast",
cli_flags: [
{ description: "keep", flag: "--keep-me", enabled: true },
{ description: "remove", flag: "--remove-me", enabled: true },
],
});
try {
await testPage.goto(`/settings/agents/${agent.name}/profiles/${profile.id}`);
await expect(testPage.getByTestId("custom-cli-flags-card")).toBeVisible({ timeout: 15_000 });
// Two rows present initially.
await expect(testPage.getByTestId("cli-flag-row-0")).toBeVisible();
await expect(testPage.getByTestId("cli-flag-row-1")).toBeVisible();
// Remove the second (--remove-me).
await testPage.getByTestId("cli-flag-remove-1").click();
await expect(testPage.getByTestId("cli-flag-row-1")).toBeHidden();
const saveButton = testPage.getByRole("button", { name: /^Save( changes)?$/i }).first();
await expect(saveButton).toBeEnabled({ timeout: 10_000 });
await saveButton.click();
await expect(testPage.getByText(/unsaved changes/i)).toBeHidden({ timeout: 15_000 });
await testPage.reload();
await expect(testPage.getByTestId("cli-flag-row-0")).toBeVisible({ timeout: 15_000 });
await expect(testPage.getByTestId("cli-flag-row-1")).toBeHidden();
const stored = await apiClient.getAgentProfile(profile.id);
expect(stored.cliFlags.map((f) => f.flag)).toEqual(["--keep-me"]);
} finally {
await apiClient.deleteAgentProfile(profile.id, true);
}
});
});