Files
buzz/desktop/tests/e2e/inline-custom-harness.spec.ts
02f640bc45 feat(desktop): unify add agent flows (#5015)
**Category:** improvement
**User Impact:** Users can create, discover, and import agents from one
consistent Add agent dialog.

**Problem:** Agent creation, discovery, and import were split across a
dropdown and separate dialogs, making the Add agent flow fragmented. The
existing E2E suite also continued targeting the deleted dropdown after
the flows were unified.

**Solution:** Route the new-agent card directly into a unified dialog
with dedicated Create, catalog, and Import navigation, then update the
affected E2E coverage to exercise that interface and its current empty
state.

<details>
<summary>File changes</summary>

**desktop/src/features/agents/ui/AgentDefinitionDialog.tsx**
Supports rendering the agent definition form inside the unified Add
agent experience while retaining the standalone dialog behavior.

**desktop/src/features/agents/ui/AgentDefinitionDialogShell.tsx**
Adds the shared shell used to present agent-definition content
consistently in embedded and standalone contexts.

**desktop/src/features/agents/ui/AgentDialog.tsx**
Passes the revised dialog state and close behavior through the existing
agent dialog entry point.

**desktop/src/features/agents/ui/AgentsView.tsx**
Connects the Agents page to the unified Add agent dialog and opens newly
added catalog agents in their profile panel.

**desktop/src/features/agents/ui/PersonaCatalogDialog.tsx**
Combines catalog browsing, agent creation, and snapshot import behind
persistent navigation, including dirty-navigation confirmation.

**desktop/src/features/agents/ui/UnifiedAgentsSection.tsx**
Replaces the new-agent dropdown with a direct Add agent entry point and
adjusts the responsive card grid.

**desktop/src/features/agents/ui/personaLibraryCopy.ts**
Updates catalog-facing copy for the unified experience.

**desktop/src/features/agents/ui/usePersonaActions.ts**
Returns the resolved local persona after catalog activation so the
caller can open the added agent.

**desktop/tests/e2e/agent-readiness-screenshots.spec.ts**
Opens the embedded create pane directly for readiness screenshots.

**desktop/tests/e2e/agents.spec.ts**
Covers unified Create, catalog, and Import navigation and asserts the
current shared-agent empty state.

**desktop/tests/e2e/global-agent-config-screenshots.spec.ts**
Updates global configuration screenshot setup for direct create-pane
entry.

**desktop/tests/e2e/inline-custom-harness.spec.ts**
Updates custom harness setup for the embedded create form.

**desktop/tests/e2e/persona-env-vars.spec.ts**
Updates environment-variable and model-provider scenarios for direct
create-pane entry.

**desktop/tests/e2e/persona-model-combobox-screenshots.spec.ts**
Updates model combobox screenshot setup for direct create-pane entry.

**desktop/tests/e2e/smoke.spec.ts**
Updates agent-creation smoke coverage for the unified Add agent dialog.

**desktop/tests/e2e/where-to-run-config.spec.ts**
Updates provider-selection coverage for the embedded create form.

</details>

## Reproduction steps

1. Open the Agents page and select the new-agent card.
2. Confirm the Add agent dialog opens directly on Create without an
intermediate dropdown.
3. Use the left navigation to browse shared agents and open Import.
4. Select a catalog agent and confirm the dialog closes and the added
agent's profile panel opens.
5. Run the affected desktop Playwright smoke and integration specs and
confirm all scenarios pass.

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
2026-08-07 16:25:49 -07:00

194 lines
7.0 KiB
TypeScript

/**
* E2E spec for the inline "Add custom harness…" entry in the agent dialogs.
*
* Registering a custom harness used to be reachable only from Settings →
* Agents, so anyone whose first touchpoint was "New agent" never learned the
* path existed. The harness dropdowns now carry the entry directly.
*
* Covers, on all three surfaces (create, edit definition, edit instance):
* - choosing the entry opens the registration form
* - saving registers the harness and selects it in the dropdown
*
* Create and instance edit additionally assert the sentinel never becomes the
* selection; create alone covers dismissing the form leaving the previous
* selection untouched. The three surfaces share the same routing, so those
* checks are not repeated on every one.
*/
import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
const ADD_ENTRY = "Add custom harness…";
const HARNESS_LABEL = "My Weird Agent";
const HARNESS_COMMAND = "my-weird-acp";
type Page = import("@playwright/test").Page;
type Locator = import("@playwright/test").Locator;
/** Open a PersonaDropdownField (button trigger + menuitemradio options). */
async function openDropdown(trigger: Locator) {
await expect(trigger).toBeVisible({ timeout: 10_000 });
await trigger.click();
}
/** Register a harness through the inline form and wait for it to close. */
async function registerHarness(page: Page) {
const form = page.getByTestId("custom-harness-form");
await expect(form).toBeVisible({ timeout: 8_000 });
await page.fill("#ch-label", HARNESS_LABEL);
await page.fill("#ch-command", HARNESS_COMMAND);
// The id auto-derives from the label; it is what the dropdown selects on.
await expect(page.locator("#ch-id")).toHaveValue("my-weird-agent");
await form.getByRole("button", { name: "Save", exact: true }).click();
await expect(page.getByTestId("add-custom-harness-dialog")).not.toBeVisible({
timeout: 8_000,
});
}
/** Open the create-agent dialog (AgentDefinitionDialog, create mode). */
async function openCreateDialog(page: Page) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-agents-view").click();
await page.getByTestId("new-agent-card").click();
const dialog = page.getByTestId("persona-dialog");
await expect(dialog).toBeVisible({ timeout: 10_000 });
await dialog.getByRole("tab", { name: "Customize for this agent" }).click();
return dialog;
}
/** Open the edit dialog for a saved definition (same dialog, edit mode). */
async function openDefinitionEditDialog(page: Page, name: string) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agents-library-personas")).toBeVisible({
timeout: 10_000,
});
await page.getByRole("button", { name: `Open actions for ${name}` }).click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const dialog = page.getByTestId("persona-dialog");
await expect(dialog).toBeVisible({ timeout: 10_000 });
await dialog.getByRole("tab", { name: "Customize for this agent" }).click();
return dialog;
}
test.describe("inline add custom harness", () => {
test("create dialog registers a harness inline and selects it", async ({
page,
}) => {
await installMockBridge(page);
const dialog = await openCreateDialog(page);
const harness = dialog.locator("#persona-runtime");
await openDropdown(harness);
await page.getByRole("menuitemradio", { name: ADD_ENTRY }).click();
// The sentinel opens the form; it must never become the selection.
await expect(page.getByTestId("add-custom-harness-dialog")).toBeVisible({
timeout: 8_000,
});
await expect(harness).not.toContainText(ADD_ENTRY);
await registerHarness(page);
// The saved harness is now the selected harness.
await expect(harness).toContainText(HARNESS_LABEL, { timeout: 8_000 });
});
test("dismissing the form leaves the create dialog's harness unchanged", async ({
page,
}) => {
await installMockBridge(page);
const dialog = await openCreateDialog(page);
const harness = dialog.locator("#persona-runtime");
await expect(harness).toBeVisible({ timeout: 10_000 });
const before = await harness.textContent();
await openDropdown(harness);
await page.getByRole("menuitemradio", { name: ADD_ENTRY }).click();
await expect(page.getByTestId("add-custom-harness-dialog")).toBeVisible({
timeout: 8_000,
});
await page.keyboard.press("Escape");
await expect(
page.getByTestId("add-custom-harness-dialog"),
).not.toBeVisible();
// No harness was registered, so the prior selection must survive.
await expect(harness).toHaveText(before ?? "");
});
test("definition edit dialog registers a harness inline and selects it", async ({
page,
}) => {
await installMockBridge(page, {
personas: [
{
displayName: "Editable Agent",
systemPrompt: "An agent whose harness gets replaced.",
},
],
});
const dialog = await openDefinitionEditDialog(page, "Editable Agent");
const harness = dialog.locator("#persona-runtime");
await openDropdown(harness);
await page.getByRole("menuitemradio", { name: ADD_ENTRY }).click();
await expect(page.getByTestId("add-custom-harness-dialog")).toBeVisible({
timeout: 8_000,
});
await registerHarness(page);
await expect(harness).toContainText(HARNESS_LABEL, { timeout: 8_000 });
});
test("instance edit dialog registers a harness inline and keeps Custom command", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
pubkey:
"npub1e2e00000000000000000000000000000000000000000000000000000000",
name: "Instance Agent",
status: "stopped",
channelNames: ["agents"],
},
],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-agents-view").click();
await page
.getByRole("button", { name: "Instance Agent agent profile" })
.click();
await expect(page.getByTestId("user-profile-panel")).toBeVisible({
timeout: 10_000,
});
await page.getByTestId("user-profile-edit-agent").click();
await expect(page.getByTestId("edit-agent-dialog")).toBeVisible({
timeout: 10_000,
});
const provider = page.locator("#edit-agent-runtime");
await openDropdown(provider);
// "Custom command" is a different feature (ad-hoc command override) and
// must survive alongside the new entry.
await expect(
page.getByRole("menuitemradio", { name: "Custom command" }),
).toBeVisible();
await page.getByRole("menuitemradio", { name: ADD_ENTRY }).click();
await expect(page.getByTestId("add-custom-harness-dialog")).toBeVisible({
timeout: 8_000,
});
await expect(provider).not.toContainText(ADD_ENTRY);
await registerHarness(page);
await expect(provider).toContainText(HARNESS_LABEL, { timeout: 8_000 });
});
});