mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
**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>
152 lines
4.9 KiB
TypeScript
152 lines
4.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
|
|
const SHOTS = "test-results/persona-model-combobox";
|
|
|
|
async function waitForInvokeBridge(page: import("@playwright/test").Page) {
|
|
await page.waitForFunction(
|
|
() => {
|
|
const w = window as Window & {
|
|
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: unknown;
|
|
__TAURI_INTERNALS__?: { invoke?: unknown };
|
|
};
|
|
return (
|
|
typeof w.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ === "function" ||
|
|
typeof w.__TAURI_INTERNALS__?.invoke === "function"
|
|
);
|
|
},
|
|
null,
|
|
{ timeout: 8_000 },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Open the Agents view, click "New agent", and open the persona create dialog.
|
|
* Returns the dialog locator.
|
|
*/
|
|
async function openNewPersonaDialog(page: import("@playwright/test").Page) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await waitForInvokeBridge(page);
|
|
|
|
await page.getByTestId("open-agents-view").click();
|
|
await expect(page.getByTestId("agents-library-personas")).toBeVisible({
|
|
timeout: 8_000,
|
|
});
|
|
|
|
await page.getByTestId("new-agent-card").click();
|
|
|
|
const dialog = page.getByTestId("persona-dialog");
|
|
await expect(dialog).toBeVisible({ timeout: 8_000 });
|
|
await dialog.getByRole("tab", { name: "Customize for this agent" }).click();
|
|
return dialog;
|
|
}
|
|
|
|
/**
|
|
* The mock bridge selects Goose as the default runtime (available + preferred).
|
|
* Wait for model discovery to populate the combobox options so the list is
|
|
* non-trivial before opening the popover.
|
|
*/
|
|
async function waitForModelCombobox(
|
|
dialog: import("@playwright/test").Locator,
|
|
) {
|
|
// The model field only appears after a runtime is selected. Goose is the
|
|
// mock default; the field should appear without manual interaction.
|
|
const trigger = dialog.getByRole("combobox", { name: /model/i });
|
|
await expect(trigger).toBeVisible({ timeout: 8_000 });
|
|
return trigger;
|
|
}
|
|
|
|
test.describe("persona model combobox screenshots", () => {
|
|
test.use({ viewport: { width: 1280, height: 900 } });
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on("pageerror", (err) => {
|
|
console.error(
|
|
"PAGE ERROR:",
|
|
err.message,
|
|
err.stack?.split("\n").slice(0, 5).join("\n"),
|
|
);
|
|
});
|
|
page.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
console.error("CONSOLE ERROR:", msg.text().slice(0, 500));
|
|
}
|
|
});
|
|
await installMockBridge(page);
|
|
});
|
|
|
|
test("01 — closed trigger (model not yet selected)", async ({ page }) => {
|
|
const dialog = await openNewPersonaDialog(page);
|
|
await waitForModelCombobox(dialog);
|
|
await waitForAnimations(page);
|
|
|
|
await dialog.screenshot({ path: `${SHOTS}/01-closed-trigger.png` });
|
|
});
|
|
|
|
test("02 — open popover with full model list", async ({ page }) => {
|
|
const dialog = await openNewPersonaDialog(page);
|
|
const trigger = await waitForModelCombobox(dialog);
|
|
|
|
await trigger.click();
|
|
|
|
// Wait for the search input to appear (popover is open).
|
|
await expect(page.getByPlaceholder("Search models…")).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// Wait for model discovery to populate at least one non-loading row.
|
|
await expect(
|
|
page.getByRole("button", { name: /claude|gpt|default/i }).first(),
|
|
).toBeVisible({ timeout: 8_000 });
|
|
|
|
await waitForAnimations(page);
|
|
await dialog.screenshot({ path: `${SHOTS}/02-open-full-list.png` });
|
|
});
|
|
|
|
test("03 — filtered results (query: gpt)", async ({ page }) => {
|
|
const dialog = await openNewPersonaDialog(page);
|
|
const trigger = await waitForModelCombobox(dialog);
|
|
|
|
await trigger.click();
|
|
|
|
const searchInput = page.getByPlaceholder("Search models…");
|
|
await expect(searchInput).toBeVisible({ timeout: 5_000 });
|
|
await expect(
|
|
page.getByRole("button", { name: /claude|gpt|default/i }).first(),
|
|
).toBeVisible({ timeout: 8_000 });
|
|
|
|
await searchInput.fill("gpt");
|
|
|
|
// At least one GPT option should be visible; Claude options gone.
|
|
await expect(
|
|
page.getByRole("button", { name: /gpt/i }).first(),
|
|
).toBeVisible({ timeout: 3_000 });
|
|
|
|
await waitForAnimations(page);
|
|
await dialog.screenshot({ path: `${SHOTS}/03-filtered-gpt.png` });
|
|
});
|
|
|
|
test("04 — empty state (no models match)", async ({ page }) => {
|
|
const dialog = await openNewPersonaDialog(page);
|
|
const trigger = await waitForModelCombobox(dialog);
|
|
|
|
await trigger.click();
|
|
|
|
const searchInput = page.getByPlaceholder("Search models…");
|
|
await expect(searchInput).toBeVisible({ timeout: 5_000 });
|
|
await expect(
|
|
page.getByRole("button", { name: /claude|gpt|default/i }).first(),
|
|
).toBeVisible({ timeout: 8_000 });
|
|
|
|
await searchInput.fill("zzznomatch");
|
|
|
|
await expect(page.getByText("No models match")).toBeVisible({
|
|
timeout: 3_000,
|
|
});
|
|
await waitForAnimations(page);
|
|
await dialog.screenshot({ path: `${SHOTS}/04-empty-state.png` });
|
|
});
|
|
});
|