Files
buzz/desktop/tests/e2e/agents.spec.ts
T
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

2592 lines
86 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "@playwright/test";
import type { RelayEvent } from "@/shared/api/types";
import { emojiAvatarDataUrl } from "@/features/profile/ui/ProfileAvatarEditor.utils";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
function createCatalogEvent(input: {
ownerPubkey: string;
sourcePersonaId: string;
displayName: string;
systemPrompt: string;
createdAt?: number;
shared?: boolean;
avatarUrl?: string;
}): RelayEvent {
return {
id: "1".repeat(64),
pubkey: input.ownerPubkey,
created_at: input.createdAt ?? 1_721_750_400,
kind: 30175,
tags: [
["d", input.sourcePersonaId],
...(input.shared === false ? [] : [["shared", "true"]]),
],
content: JSON.stringify({
display_name: input.displayName,
system_prompt: input.systemPrompt,
avatar_url: input.avatarUrl ?? null,
runtime: null,
model: null,
provider: null,
name_pool: [],
}),
sig: "2".repeat(128),
};
}
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
});
async function gotoApp(page: import("@playwright/test").Page) {
let lastError: unknown = null;
for (const attempt of [0, 1]) {
await page.goto("/", { waitUntil: "domcontentloaded" });
await waitForInvokeBridge(page);
try {
await expect(page.getByTestId("open-agents-view")).toBeVisible({
timeout: 10_000,
});
return;
} catch (error) {
lastError = error;
if (attempt === 1) {
throw error;
}
}
}
throw lastError;
}
async function openPersonaCatalog(page: import("@playwright/test").Page) {
await page.getByTestId("new-agent-card").click();
}
async function getCatalogOrder(page: import("@playwright/test").Page) {
return page
.locator('[data-testid^="persona-catalog-list-item-"]')
.evaluateAll((elements) =>
elements.map((element) => element.getAttribute("data-testid") ?? ""),
);
}
async function selectCatalogPersona(
page: import("@playwright/test").Page,
personaId: string,
) {
await page.getByTestId(`persona-catalog-list-item-${personaId}`).click();
}
async function sharePersonaToCatalog(
page: import("@playwright/test").Page,
displayName: string,
) {
await page.getByLabel(`Open actions for ${displayName}`).click();
await page.getByRole("menuitem", { name: "Share" }).click();
const catalogToggle = page.getByTestId("persona-share-catalog-access");
await expect(catalogToggle).not.toBeChecked();
await catalogToggle.click();
await expect(catalogToggle).toBeChecked();
await page
.getByTestId("persona-share-dialog")
.getByRole("button", { name: "Close" })
.click();
}
async function waitForInvokeBridge(page: import("@playwright/test").Page) {
await page.waitForFunction(
() => {
const tauriWindow = window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: unknown;
__TAURI_INTERNALS__?: {
invoke?: unknown;
};
};
return (
typeof tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ === "function" ||
typeof tauriWindow.__TAURI_INTERNALS__?.invoke === "function"
);
},
null,
{ timeout: 5_000 },
);
}
async function invokeTauri<T>(
page: import("@playwright/test").Page,
command: string,
payload?: Record<string, unknown>,
): Promise<T> {
await waitForInvokeBridge(page);
return page.evaluate(
async ({ command: targetCommand, payload: targetPayload }) => {
const tauriWindow = window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
payload?: Record<string, unknown>,
) => Promise<unknown>;
__TAURI_INTERNALS__?: {
invoke?: (
command: string,
payload?: Record<string, unknown>,
) => Promise<unknown>;
};
};
const invoke =
tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ??
tauriWindow.__TAURI_INTERNALS__?.invoke;
if (!invoke) {
throw new Error("Mock invoke bridge is unavailable.");
}
return (await invoke(targetCommand, targetPayload)) as T;
},
{ command, payload },
);
}
async function invokeTauriExpectError(
page: import("@playwright/test").Page,
command: string,
payload?: Record<string, unknown>,
) {
await waitForInvokeBridge(page);
return page.evaluate(
async ({ command: targetCommand, payload: targetPayload }) => {
const tauriWindow = window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
payload?: Record<string, unknown>,
) => Promise<unknown>;
__TAURI_INTERNALS__?: {
invoke?: (
command: string,
payload?: Record<string, unknown>,
) => Promise<unknown>;
};
};
const invoke =
tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ??
tauriWindow.__TAURI_INTERNALS__?.invoke;
if (!invoke) {
throw new Error("Mock invoke bridge is unavailable.");
}
try {
await invoke(targetCommand, targetPayload);
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
},
{ command, payload },
);
}
async function countCommandInvocations(
page: import("@playwright/test").Page,
command: string,
): Promise<number> {
return page.evaluate(
(targetCommand) =>
(
window as Window & {
__BUZZ_E2E_COMMANDS__?: string[];
}
).__BUZZ_E2E_COMMANDS__?.filter((invoked) => invoked === targetCommand)
.length ?? 0,
command,
);
}
test("catalog hides built-ins and shows the shared-agent empty state", async ({
page,
}) => {
await page.setViewportSize({ width: 1280, height: 420 });
await installMockBridge(page, {
activePersonaIds: ["builtin:fizz", "builtin:honey", "builtin:bumble"],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agents-library-personas")).toBeVisible();
for (const personaName of ["Fizz", "Honey", "Bumble"]) {
await expect(page.getByTestId("agents-library-personas")).toContainText(
personaName,
);
}
await openPersonaCatalog(page);
for (const personaName of ["Fizz", "Honey", "Bumble"]) {
await expect(page.getByTestId("persona-catalog-dialog")).not.toContainText(
personaName,
);
}
await expect(page.getByTestId("persona-catalog-dialog-header")).toBeVisible();
await expect(page.getByTestId("persona-catalog-dialog-body")).toBeVisible();
await expect(
page.getByText("No shared agents", { exact: true }),
).toBeVisible();
await expect(
page.locator('[data-testid^="persona-catalog-list-item-"]'),
).toHaveCount(0);
await expect(
page.getByTestId("persona-catalog-use-agent-target"),
).toHaveCount(0);
await page
.getByTestId("persona-catalog-dialog")
.getByRole("button", { name: "Close" })
.click();
await page.getByLabel("Open actions for Fizz").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(page.getByTestId("persona-share-catalog")).toHaveCount(0);
await expect(page.getByTestId("persona-share-catalog-access")).toHaveCount(0);
});
test("catalog empty state remains available after reopening", async ({
page,
}) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
await expect(
page.getByText("No shared agents", { exact: true }),
).toBeVisible();
await page
.getByTestId("persona-catalog-dialog")
.getByRole("button", { name: "Close" })
.click();
await expect(page.getByTestId("persona-catalog-dialog")).not.toBeVisible();
await openPersonaCatalog(page);
await expect(
page.getByText("No shared agents", { exact: true }),
).toBeVisible();
});
test("built-in persona edits persist", async ({ page }) => {
await installMockBridge(page, {
activePersonaIds: ["builtin:fizz"],
globalAgentConfig: {
env_vars: { ANTHROPIC_API_KEY: "sk-ant-test" },
provider: "anthropic",
model: "claude-opus-4-5",
},
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Fizz").click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const dialog = page.getByTestId("persona-dialog");
await dialog.getByLabel("Agent name").fill("My Fizz");
await dialog.getByLabel("Agent instruction").fill("User-edited instructions");
await dialog.getByRole("button", { name: "Save changes" }).click();
await expect(dialog).toHaveCount(0);
await expect(page.getByTestId("agents-library-personas")).toContainText(
"My Fizz",
);
const personas = await invokeTauri<
Array<{ id: string; display_name: string; system_prompt: string }>
>(page, "list_personas");
expect(
personas.find((persona) => persona.id === "builtin:fizz"),
).toMatchObject({
display_name: "My Fizz",
system_prompt: "User-edited instructions",
});
});
test("searches agent avatar emoji with focus on open", async ({ page }) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByTestId("new-agent-card").click();
await expect(page.getByTestId("persona-dialog")).toBeVisible();
await page.getByLabel("Add avatar").click();
await page.getByRole("tab", { name: "Emoji" }).click();
const picker = page.locator("em-emoji-picker");
const searchInput = picker.locator("input[type='search']");
await expect(searchInput).toBeVisible();
await expect(searchInput).toBeFocused();
await searchInput.fill("rocket");
await picker.locator("button[aria-label='🚀']").first().click();
await expect(page.getByRole("img", { name: "Agent name avatar" })).toHaveText(
"🚀",
);
});
test("agent avatar emoji picker scrolls inside its popover", async ({
page,
}) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByTestId("new-agent-card").click();
await expect(page.getByTestId("persona-dialog")).toBeVisible();
await page.getByLabel("Add avatar").click();
await page.getByRole("tab", { name: "Emoji" }).click();
await expect(page.locator("em-emoji-picker")).toBeVisible();
await page.waitForFunction(() => {
const picker = document.querySelector("em-emoji-picker");
const scroll = picker?.shadowRoot?.querySelector(".scroll");
return (
scroll instanceof HTMLElement && scroll.scrollHeight > scroll.clientHeight
);
});
const before = await page.locator("em-emoji-picker").evaluate((picker) => {
const scroll = picker.shadowRoot?.querySelector(".scroll");
return scroll instanceof HTMLElement ? scroll.scrollTop : -1;
});
const box = await page.locator("em-emoji-picker").boundingBox();
if (!box) {
throw new Error("Could not measure emoji picker bounds.");
}
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.wheel(0, 500);
await expect
.poll(async () =>
page.locator("em-emoji-picker").evaluate((picker) => {
const scroll = picker.shadowRoot?.querySelector(".scroll");
return scroll instanceof HTMLElement ? scroll.scrollTop : -1;
}),
)
.toBeGreaterThan(before);
});
test("the new agent card opens unified create, catalog, and import flows", async ({
page,
}) => {
await installMockBridge(page, {
activePersonaIds: ["builtin:fizz", "builtin:honey", "builtin:bumble"],
personas: [
{
id: "custom:code-reviewer",
displayName: "Code Reviewer",
systemPrompt: "Review code changes.",
},
{
id: "custom:layout-auditor",
displayName: "Layout Auditor",
systemPrompt: "Review responsive layouts.",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
const newAgentCard = page.getByTestId("new-agent-card");
await expect(newAgentCard).toHaveText("");
await expect(newAgentCard.locator(".lucide-plus")).toBeVisible();
const agentCards = page.locator(
'[data-testid^="persona-agent-row-"], [data-testid="new-agent-card"]',
);
await expect(agentCards.first()).toBeVisible();
const headerBox = await page
.getByRole("heading", { level: 1, name: "Agents" })
.locator("../..")
.boundingBox();
const cardBoxes = await agentCards.evaluateAll((cards) =>
cards.map((card) => {
const box = card.getBoundingClientRect();
return { left: box.left, right: box.right, top: box.top };
}),
);
const firstRowTop = Math.min(...cardBoxes.map(({ top }) => top));
expect(
cardBoxes.filter(({ top }) => Math.abs(top - firstRowTop) < 1),
).toHaveLength(5);
const rightmostFirstRowCard = Math.max(
...cardBoxes
.filter(({ top }) => Math.abs(top - firstRowTop) < 1)
.map(({ right }) => right),
);
const leftmostFirstRowCard = Math.min(
...cardBoxes
.filter(({ top }) => Math.abs(top - firstRowTop) < 1)
.map(({ left }) => left),
);
expect(headerBox).not.toBeNull();
expect(Math.abs((headerBox?.x ?? 0) - leftmostFirstRowCard)).toBeLessThan(1);
expect(rightmostFirstRowCard).toBeLessThanOrEqual(
(headerBox?.x ?? 0) + (headerBox?.width ?? 0) + 1,
);
await newAgentCard.click();
const catalogDialog = page.getByTestId("persona-catalog-dialog");
await expect(catalogDialog).toBeVisible();
await expect(page.getByTestId("agent-catalog-create")).toHaveAttribute(
"aria-current",
"true",
);
const dialog = page.getByTestId("persona-dialog");
await expect(dialog).toBeVisible();
await expect(
dialog.getByTestId("import-agent-snapshot-dialog-action"),
).toHaveCount(0);
await expect(dialog).not.toContainText("Enter a name for this agent.");
await page.getByTestId("agent-catalog-import").click();
await expect(page.getByTestId("agent-catalog-import-dropzone")).toBeVisible();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("agent-catalog-import-dropzone").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles({
buffer: Buffer.from("{}"),
mimeType: "application/json",
name: "imported.agent.json",
});
await expect(page.getByTestId("agent-snapshot-import-dialog")).toBeVisible();
});
test("embedded create keeps its draft when discard is cancelled", async ({
page,
}) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByTestId("new-agent-card").click();
const dialog = page.getByTestId("persona-dialog");
const name = dialog.locator("#persona-display-name");
const instructions = dialog.locator("#persona-system-prompt");
await name.fill("Draft Keeper");
await instructions.fill("Preserve this draft through confirmation.");
await dialog.getByRole("button", { name: "Cancel" }).click();
const discardDialog = page.getByTestId("discard-create-agent-dialog");
await expect(discardDialog).toBeVisible();
await discardDialog.getByRole("button", { name: "Keep editing" }).click();
await expect(dialog).toBeVisible();
await expect(name).toHaveValue("Draft Keeper");
await expect(instructions).toHaveValue(
"Preserve this draft through confirmation.",
);
});
test("the new team card offers create and import", async ({ page }) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
const newTeamCard = page.getByTestId("new-team-card");
await expect(newTeamCard).toHaveText("");
await expect(newTeamCard.locator(".lucide-plus")).toBeVisible();
await newTeamCard.click();
await expect(
page.getByRole("menuitem", { exact: true, name: "Create team" }),
).toBeVisible();
await expect(
page.getByRole("menuitem", { exact: true, name: "Import" }),
).toBeVisible();
});
test("team cards follow the agents grid alignment at compact widths", async ({
page,
}) => {
await installMockBridge(page, {
personas: [
{
id: "custom:team-layout",
displayName: "Team layout agent",
systemPrompt: "A test agent for team layout alignment.",
},
],
teams: [
{
id: "team-layout",
name: "Team layout",
personaIds: ["custom:team-layout"],
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
const agentsContent = page.getByTestId("agents-page-content");
const firstAgentCard = page.getByTestId(
"persona-agent-row-custom:team-layout",
);
const firstTeamCard = page.getByTestId("team-card-team-layout");
const agentGrid = firstAgentCard.locator("xpath=..");
const teamGrid = firstTeamCard.locator("xpath=..");
const firstAgentGridCard = agentGrid.locator(":scope > *").first();
const firstTeamGridCard = teamGrid.locator(":scope > *").first();
await agentsContent.evaluate((element) => {
(element as HTMLElement).style.width = "650px";
});
const wideAgentBox = await firstAgentGridCard.boundingBox();
const wideTeamBox = await firstTeamGridCard.boundingBox();
expect(wideAgentBox).not.toBeNull();
expect(wideTeamBox).not.toBeNull();
expect(Math.abs((wideAgentBox?.x ?? 0) - (wideTeamBox?.x ?? 0))).toBeLessThan(
1,
);
await agentsContent.evaluate((element) => {
(element as HTMLElement).style.width = "600px";
});
const compactAgentBox = await firstAgentGridCard.boundingBox();
const compactTeamBox = await firstTeamGridCard.boundingBox();
expect(compactAgentBox?.width ?? 0).toBeLessThan(wideAgentBox?.width ?? 0);
expect(
Math.abs((compactAgentBox?.width ?? 0) - (compactTeamBox?.width ?? 0)),
).toBeLessThan(1);
expect(
Math.abs((compactAgentBox?.x ?? 0) - (compactTeamBox?.x ?? 0)),
).toBeLessThan(1);
});
test("team cards use the thread-style overlapping avatar stack", async ({
page,
}) => {
const personaIds = ["custom:design", "custom:build", "custom:ship"];
await installMockBridge(page, {
personas: [
{
avatarUrl: "/onboarding/starter-team/fizz.png",
id: personaIds[0],
displayName: "Design",
systemPrompt: "You design interfaces.",
},
{
id: personaIds[1],
displayName: "Build",
systemPrompt: "You build interfaces.",
},
{
id: personaIds[2],
displayName: "Ship",
systemPrompt: "You ship interfaces.",
},
],
teams: [
{
name: "Product crew",
personaIds,
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
const stack = page.getByLabel("Product crew member avatars");
const avatars = stack.locator('[data-team-member-avatar="avatar"]');
await expect(avatars).toHaveCount(3);
await expect(avatars.nth(1)).toHaveClass(/-ml-5/);
await expect(avatars.nth(2)).toHaveClass(/-ml-5/);
const boxes = await avatars.evaluateAll((elements) =>
elements.map((element) => {
const box = element.getBoundingClientRect();
return { left: box.left, right: box.right };
}),
);
expect(boxes[1]?.left).toBeLessThan(boxes[0]?.right ?? 0);
expect(boxes[2]?.left).toBeLessThan(boxes[1]?.right ?? 0);
await expect(avatars.first()).not.toHaveCSS("mask-image", "none");
await expect(avatars.last()).toHaveCSS("mask-image", "none");
const avatarSurfaceStyles = await avatars
.locator(":scope > *")
.evaluateAll((elements) =>
elements.map((element) => {
const styles = getComputedStyle(element);
const hasVisibleShadow = [
...styles.boxShadow.matchAll(/rgba?\(([^)]+)\)/g),
].some((match) => {
if (match[0].startsWith("rgb(")) return true;
const channels = match[1]?.split(/[\s,/]+/).filter(Boolean) ?? [];
return Number(channels.at(-1)) > 0;
});
return {
borderWidth: styles.borderTopWidth,
hasVisibleShadow,
};
}),
);
expect(avatarSurfaceStyles).toEqual([
{ borderWidth: "0px", hasVisibleShadow: false },
{ borderWidth: "0px", hasVisibleShadow: false },
{ borderWidth: "0px", hasVisibleShadow: false },
]);
});
test("agent defaults stays in the header without an actions menu", async ({
page,
}) => {
await installMockBridge(page, {
acpRuntimesCatalog: [
{
auth_status: { status: "logged_in" },
availability: "available",
avatar_url: "",
binary_path: "/usr/local/bin/codex",
can_auto_install: false,
command: "codex",
default_args: [],
id: "codex",
install_hint: "",
install_instructions_url: "https://example.com",
label: "Codex",
login_hint: null,
mcp_command: null,
node_required: false,
underlying_cli_path: null,
},
],
globalAgentConfig: {
env_vars: {},
model: "gpt-5.5[high]",
preferred_runtime: "codex",
provider: null,
},
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agent-header-actions-button")).toHaveCount(0);
await expect(
page.getByRole("menuitem", { name: "Import agent" }),
).toHaveCount(0);
const defaultsButton = page.getByTestId("agent-defaults-button");
await expect(defaultsButton).toHaveText("Agent defaults");
await defaultsButton.click();
const defaultsDialog = page.getByTestId("agent-ai-defaults-dialog");
await expect(defaultsDialog).toBeVisible();
await expect(
defaultsDialog.getByTestId("global-agent-default-harness"),
).toHaveAttribute("data-value", "codex");
await expect(
defaultsDialog.getByTestId("global-agent-default-harness"),
).toContainText("Codex");
await expect(
defaultsDialog.getByTestId("global-agent-model"),
).toHaveAttribute("data-value", "gpt-5.5[high]");
await expect(defaultsDialog.getByTestId("global-agent-model")).toContainText(
"gpt-5.5[high]",
);
await page.keyboard.press("Escape");
await expect(defaultsDialog).toHaveCount(0);
});
test("unconfigured agent defaults use the setup label", async ({ page }) => {
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agent-defaults-button")).toHaveText(
"Set agent defaults",
);
});
test("moves agent actions into an overflow menu in a narrow view", async ({
page,
}) => {
await installMockBridge(page, {
personas: [
{
id: "custom:compact-actions",
displayName: "Compact actions agent",
isActive: true,
systemPrompt: "A test agent for compact header actions.",
},
],
managedAgents: [
{
name: "Compact actions instance",
personaId: "custom:compact-actions",
pubkey: "cd".repeat(32),
status: "running",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByTestId("agents-page-content").evaluate((element) => {
(element as HTMLElement).style.width = "650px";
});
await expect(page.getByTestId("agent-defaults-button")).toBeVisible();
await expect(
page.getByText("Set up and manage your agents.", { exact: true }),
).toHaveJSProperty("scrollHeight", 24);
await page.getByTestId("agents-page-content").evaluate((element) => {
(element as HTMLElement).style.width = "600px";
});
await expect(page.getByTestId("agent-defaults-button")).toBeHidden();
await page.getByTestId("agent-actions-menu-trigger").click();
await expect(
page.getByRole("menuitem", { name: "Set agent defaults" }),
).toBeVisible();
await expect(
page.getByRole("menuitem", { name: "Stop running agents" }),
).toBeVisible();
await page.getByRole("menuitem", { name: "Set agent defaults" }).click();
await expect(page.getByTestId("agent-ai-defaults-dialog")).toBeVisible();
await page.getByTestId("agents-page-content").evaluate((element) => {
(element as HTMLElement).style.width = "650px";
});
await expect(page.getByTestId("agent-defaults-button")).toBeVisible();
await page.keyboard.press("Escape");
await expect(page.getByTestId("agent-ai-defaults-dialog")).toHaveCount(0);
await expect(page.getByTestId("agent-defaults-button")).toBeFocused();
});
test("agent catalog chooser order stays stable when selection changes", async ({
page,
}) => {
await installMockBridge(page, {
personas: [
{
id: "custom:builder",
displayName: "Builder",
systemPrompt: "Build the requested change.",
},
{
id: "custom:reviewer",
displayName: "Reviewer",
systemPrompt: "Review the requested change.",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await sharePersonaToCatalog(page, "Builder");
await sharePersonaToCatalog(page, "Reviewer");
await openPersonaCatalog(page);
const before = await getCatalogOrder(page);
await selectCatalogPersona(page, "custom:reviewer");
expect(await getCatalogOrder(page)).toEqual(before);
});
test("catalog detail pane shows the full persona details", async ({ page }) => {
const personaId = "custom:researcher";
await installMockBridge(page, {
personas: [
{
id: personaId,
displayName: "Researcher",
systemPrompt: "Research the question and cite the evidence.",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await sharePersonaToCatalog(page, "Researcher");
await openPersonaCatalog(page);
await selectCatalogPersona(page, personaId);
const useAgentTarget = page.getByTestId(
`persona-catalog-use-agent-target-${personaId}`,
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Researcher",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Added by You",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Research the question and cite the evidence.",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Custom agent",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Preferred model",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Preferred runtime",
);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Agent instruction",
);
await expect(useAgentTarget).toHaveAttribute(
"aria-label",
"Researcher is already in My Agents",
);
await expect(useAgentTarget).toHaveText("Added to My Agents");
await expect(useAgentTarget).toBeDisabled();
});
type AgentShareCommand = { command: string; payload: unknown };
async function openSafetyShareDialog(
page: import("@playwright/test").Page,
options: Parameters<typeof installMockBridge>[1] = {},
) {
await installMockBridge(page, {
personas: [
{
id: "custom:safety-auditor",
displayName: "Safety Auditor",
systemPrompt: "You audit safety boundaries.",
},
],
searchProfiles: [
{
pubkey: TEST_IDENTITIES.charlie.pubkey,
displayName: "Charlie",
},
],
uploadDescriptors: [
{
url: `https://mock.relay/media/${"d".repeat(64)}.png`,
sha256: "d".repeat(64),
size: 1234,
type: "image/png",
uploaded: Math.floor(Date.now() / 1000),
filename: "safety-auditor.agent.png",
},
],
...options,
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Safety Auditor").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(page.getByTestId("persona-share-dialog")).toBeVisible();
}
async function selectCharlieRecipient(page: import("@playwright/test").Page) {
const search = page.getByTestId("persona-share-recipient-search");
await expect(search).toBeEnabled({ timeout: 5_000 });
await search.fill("charlie");
await page
.getByTestId(
`persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`,
)
.click();
}
async function readAgentShareCommands(
page: import("@playwright/test").Page,
): Promise<AgentShareCommand[]> {
return page.evaluate(
() =>
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: AgentShareCommand[];
}
).__BUZZ_E2E_COMMAND_LOG__ ?? [],
);
}
test("custom personas share with people and keep export separate", async ({
page,
}) => {
const sharedAgentUrl = `https://mock.relay/media/${"b".repeat(64)}.png`;
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
await installMockBridge(page, {
personas: [
{
id: "custom:analyst",
displayName: "Animation Auditor",
systemPrompt: "You audit animations.",
},
],
searchProfiles: [
{
pubkey: TEST_IDENTITIES.charlie.pubkey,
displayName: "Charlie",
},
{
pubkey: TEST_IDENTITIES.bob.pubkey,
displayName: "Bob",
},
...Array.from({ length: 12 }, (_, index) => ({
pubkey: (index + 10).toString(16).padStart(64, "0"),
displayName: `Person ${index + 1}`,
})),
],
uploadDescriptors: [
{
url: sharedAgentUrl,
sha256: "b".repeat(64),
size: 1234,
type: "image/png",
uploaded: Math.floor(Date.now() / 1000),
filename: "analyst.agent.png",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await expect(page.getByTestId("agents-library-personas")).toContainText(
"Animation Auditor",
);
const actionsButton = page.getByLabel("Open actions for Animation Auditor");
await expect(actionsButton.locator("svg")).toHaveClass(
/lucide-ellipsis-vertical/,
);
await actionsButton.click();
await expect(page.getByRole("menuitem")).toHaveText([
"Edit",
"Duplicate",
"Share",
"Delete",
]);
await page.getByRole("menuitem", { name: "Share" }).click();
const shareDialog = page.getByTestId("persona-share-dialog");
await expect(shareDialog).toBeVisible();
const emptyRecipientSearch = shareDialog.getByTestId(
"persona-share-recipient-search",
);
const [searchIconColor, searchPlaceholderColor, searchInputColor] =
await Promise.all([
shareDialog
.locator("svg.lucide-search")
.evaluate((element) => getComputedStyle(element).color),
emptyRecipientSearch.evaluate(
(element) => getComputedStyle(element, "::placeholder").color,
),
emptyRecipientSearch.evaluate(
(element) => getComputedStyle(element).color,
),
]);
expect(searchIconColor).toBe(searchPlaceholderColor);
expect(searchPlaceholderColor).not.toBe(searchInputColor);
const shareCloseButton = shareDialog.getByRole("button", { name: "Close" });
expect(
await shareCloseButton.evaluate(
(button) => getComputedStyle(button).boxShadow,
),
).not.toContain("1px");
await expect(
page.getByRole("heading", { name: "Share Animation Auditor" }),
).toBeVisible();
await expect(shareDialog.getByText("Added by You")).toHaveCount(0);
const shareDescription = shareDialog.getByTestId(
"persona-share-share-description",
);
await expect(shareDescription).toHaveText(
"Anyone you share this agent with will receive a copy they can add and use. Changes you make later wont sync.",
);
await expect(shareDescription).toHaveClass(/text-sm.*text-muted-foreground/);
const shareDescriptionId = await shareDescription.getAttribute("id");
expect(shareDescriptionId).toBeTruthy();
await expect(shareDialog).toHaveAttribute(
"aria-describedby",
shareDescriptionId ?? "",
);
const shareDescriptionMetrics = await shareDescription.evaluate(
(element) => ({
height: element.getBoundingClientRect().height,
lineHeight: Number.parseFloat(getComputedStyle(element).lineHeight),
}),
);
expect(shareDescriptionMetrics.height).toBeLessThanOrEqual(
shareDescriptionMetrics.lineHeight * 2 + 1,
);
await expect(
shareDialog.getByRole("heading", { name: "Who has access" }),
).toHaveCount(0);
await expect(shareDialog.getByText("Owner", { exact: true })).toHaveCount(0);
await expect(shareDialog.getByText("(You)", { exact: true })).toHaveCount(0);
const linkRow = page.getByTestId("persona-share-link-row");
await expect(page.getByTestId("persona-share-copy-link")).toBeVisible();
await expect(
shareDialog.getByRole("heading", { name: "Share with a link" }),
).toHaveCount(0);
await expect(
shareDialog.getByText("Anyone with the link can add and use a copy."),
).toHaveCount(0);
await expect(page.getByTestId("persona-share-send")).toHaveCount(0);
const copyLinkButton = page.getByTestId("persona-share-copy-link");
const catalogSection = page.getByTestId("persona-share-catalog");
const shareMainCard = page.getByTestId("persona-share-main-card");
const exportAgentRow = page.getByTestId("persona-share-export");
await waitForAnimations(page);
const [
linkRowBox,
initialCopyLinkButtonBox,
catalogSectionBox,
shareMainCardBox,
exportAgentRowBox,
] = await Promise.all([
linkRow.boundingBox(),
copyLinkButton.boundingBox(),
catalogSection.boundingBox(),
shareMainCard.boundingBox(),
exportAgentRow.boundingBox(),
]);
const shareDescriptionBox = await shareDescription.boundingBox();
const recipientFieldBox = await page
.getByTestId("persona-share-recipient-field")
.boundingBox();
// Without memories, the recipient field flows directly into the link action.
expect(recipientFieldBox?.y ?? 0).toBeGreaterThanOrEqual(
(shareDescriptionBox?.y ?? 0) + (shareDescriptionBox?.height ?? 0),
);
await expect(page.getByTestId("persona-share-link-settings")).toHaveCount(0);
await expect(page.getByTestId("persona-share-share-level-row")).toHaveCount(
0,
);
expect(linkRowBox?.y ?? 0).toBeGreaterThanOrEqual(
(recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0),
);
// Copy link stays inside the main card, after the shared settings divider, while catalog and
// export are separate rows below it.
expect(initialCopyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual(
linkRowBox?.y ?? 0,
);
expect(
(initialCopyLinkButtonBox?.y ?? 0) +
(initialCopyLinkButtonBox?.height ?? 0),
).toBeLessThanOrEqual((linkRowBox?.y ?? 0) + (linkRowBox?.height ?? 0) + 1);
await expect(page.getByTestId("persona-share-link-divider")).toHaveClass(
/bg-input\/40/,
);
expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual(
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0) + 12,
);
expect(exportAgentRowBox?.y ?? 0).toBeGreaterThanOrEqual(
(catalogSectionBox?.y ?? 0) + (catalogSectionBox?.height ?? 0) + 12,
);
await expect(copyLinkButton).toHaveClass(
/border.*bg-background.*border-border/,
);
const copyLinkHasVisibleShadow = () =>
copyLinkButton.evaluate((element) => {
const boxShadow = getComputedStyle(element).boxShadow;
if (boxShadow === "none") return false;
return [...boxShadow.matchAll(/rgba?\(([^)]+)\)/g)].some((match) => {
if (match[0].startsWith("rgb(")) return true;
const channels = match[1]?.split(/[\s,/]+/).filter(Boolean) ?? [];
return Number(channels.at(-1)) > 0;
});
});
await expect.poll(copyLinkHasVisibleShadow).toBe(false);
await copyLinkButton.hover();
await expect.poll(copyLinkHasVisibleShadow).toBe(false);
await expect(page.getByTestId("persona-share-share-level")).toHaveCount(0);
await expect(page.getByTestId("persona-share-recipient-access")).toHaveCount(
0,
);
await expect(page.getByTestId("persona-share-link-access")).toHaveCount(0);
await expect(
shareDialog.getByLabel("What to include in the link"),
).toHaveCount(0);
await expect(
shareDialog.getByLabel("What to include", { exact: true }),
).toHaveCount(0);
await expect(shareDialog.getByText("Memories", { exact: true })).toHaveCount(
0,
);
await expect(
shareDialog.getByText("File format", { exact: true }),
).toHaveCount(0);
await expect(exportAgentRow).toHaveText("Export agent");
await expect(shareMainCard.getByTestId("persona-share-export")).toHaveCount(
0,
);
const [shareMainCardStyles, exportAgentRowStyles] = await Promise.all([
shareMainCard.evaluate((element) => ({
borderRadius: getComputedStyle(element).borderRadius,
boxShadow: getComputedStyle(element).boxShadow,
})),
exportAgentRow.evaluate((element) => ({
borderRadius: getComputedStyle(element).borderRadius,
boxShadow: getComputedStyle(element).boxShadow,
})),
]);
expect(exportAgentRowStyles.borderRadius).toBe(
shareMainCardStyles.borderRadius,
);
const shareMainCardShadow = shareMainCardStyles.boxShadow;
const exportAgentRowShadow = exportAgentRowStyles.boxShadow;
expect(exportAgentRowShadow).toBe(shareMainCardShadow);
expect(exportAgentRowShadow).not.toBe("none");
await expect(exportAgentRow).toHaveCSS("position", "relative");
expect(exportAgentRowBox?.y ?? 0).toBeGreaterThanOrEqual(
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0) + 12,
);
await expect(page.getByTestId("agent-snapshot-export-dialog")).toHaveCount(0);
await exportAgentRow.click();
await expect(shareDialog).toHaveCount(0);
const exportDialog = page.getByTestId("agent-snapshot-export-dialog");
await expect(exportDialog).toBeVisible();
const exportDialogBox = await exportDialog.boundingBox();
expect(exportDialogBox?.width).toBeLessThanOrEqual(448);
await expect(
exportDialog.getByRole("heading", {
name: "Export Animation Auditor",
exact: true,
}),
).toBeVisible();
await expect(exportDialog.getByText("portable snapshot")).toHaveCount(0);
await expect(
exportDialog.getByRole("button", { name: "Send in Buzz" }),
).toHaveCount(0);
await expect(exportDialog.getByLabel("Memories")).toHaveCount(0);
await expect(
exportDialog.getByTestId("agent-snapshot-memory-value"),
).toHaveText("Agent only");
await expect(exportDialog.getByText("Start this agent")).toHaveCount(0);
await expect(
exportDialog.getByTestId("agent-snapshot-memory-value").locator("svg"),
).toHaveCount(0);
const formatTrigger = exportDialog.getByLabel("File format");
await expect(formatTrigger).toHaveText("PNG");
expect((await formatTrigger.boundingBox())?.width).toBeLessThan(80);
expect(await formatTrigger.evaluate((element) => element.tagName)).toBe(
"BUTTON",
);
await formatTrigger.click();
await expect(page.getByRole("menuitemradio", { name: "JSON" })).toBeVisible();
await page.getByRole("menuitemradio", { name: "PNG" }).click();
const exportFooter = exportDialog.getByTestId("agent-snapshot-export-footer");
const cancelButtonBox = await exportFooter
.getByRole("button", { name: "Cancel" })
.boundingBox();
const exportButtonBox = await exportFooter
.getByRole("button", { name: "Export" })
.boundingBox();
expect(cancelButtonBox?.x).toBeLessThan(exportButtonBox?.x ?? 0);
expect(
(exportButtonBox?.x ?? 0) -
((cancelButtonBox?.x ?? 0) + (cancelButtonBox?.width ?? 0)),
).toBeLessThanOrEqual(12);
await exportDialog.getByRole("button", { name: "Cancel" }).click();
await actionsButton.click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(shareDialog).toBeVisible();
await page.getByTestId("persona-share-copy-link").click();
await expect
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
.toBe(sharedAgentUrl);
const copiedAgent = await page.evaluate(() => {
const commands =
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: { html?: string; text?: string };
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? [];
return commands.findLast(
(entry) => entry.command === "copy_text_to_clipboard",
)?.payload;
});
expect(copiedAgent?.text).toBe(sharedAgentUrl);
expect(copiedAgent?.html).toContain("data-buzz-agent-snapshot");
await page.keyboard.press("Escape");
await expect(shareDialog).toHaveCount(0);
await page.getByTestId("channel-general").click();
await page.evaluate(async ({ html, text }) => {
await navigator.clipboard.write([
new ClipboardItem({
"text/html": new Blob([html ?? ""], { type: "text/html" }),
"text/plain": new Blob([text ?? ""], { type: "text/plain" }),
}),
]);
}, copiedAgent ?? {});
await page
.getByTestId("message-composer")
.locator("[contenteditable='true']")
.click();
await page.keyboard.press("ControlOrMeta+V");
const composerAgentCard = page.getByTestId("composer-agent-snapshot-card");
await expect(composerAgentCard).toBeVisible();
await expect(composerAgentCard).toContainText("Animation Auditor");
const composerRemoveButton = page.getByTestId(
"composer-agent-snapshot-remove",
);
await expect(composerRemoveButton).toHaveCSS("border-top-width", "0px");
await composerRemoveButton.focus();
await page.keyboard.press("Tab");
await page.keyboard.press("Shift+Tab");
await expect(composerRemoveButton).toBeFocused();
expect(
await composerRemoveButton.evaluate(
(button) => getComputedStyle(button).boxShadow,
),
).not.toContain("1px");
await expect(composerRemoveButton).not.toHaveCSS(
"background-color",
"rgba(0, 0, 0, 0)",
);
await expect(page.getByTestId("send-message")).toBeEnabled();
await page.getByTestId("send-message").click();
const pastedAgentCard = page.getByTestId("agent-snapshot-card").last();
await expect(pastedAgentCard).toBeVisible();
await expect(pastedAgentCard).toContainText("Animation Auditor");
await page.getByTestId("open-agents-view").click();
await actionsButton.click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(shareDialog).toBeVisible();
const recipientSearch = page.getByTestId("persona-share-recipient-search");
const recipientField = page.getByTestId("persona-share-recipient-field");
await waitForAnimations(page);
const emptyRecipientFieldWidth =
(await recipientField.boundingBox())?.width ?? 0;
await recipientSearch.click();
const recipientPopover = page.getByTestId("persona-share-recipient-popover");
await expect(recipientPopover).toBeVisible();
await page.evaluate(() => {
const content = document.querySelector(
'[data-testid="persona-share-recipient-popover"]',
);
if (!content) throw new Error("Recipient popover is not mounted");
const stateChanges: string[] = [];
const observer = new MutationObserver(() => {
stateChanges.push(content.getAttribute("data-state") ?? "unmounted");
});
observer.observe(content, {
attributeFilter: ["data-state"],
attributes: true,
});
(
window as Window & {
__BUZZ_RECIPIENT_POPOVER_OBSERVER__?: MutationObserver;
__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[];
}
).__BUZZ_RECIPIENT_POPOVER_OBSERVER__ = observer;
(
window as Window & {
__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[];
}
).__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__ = stateChanges;
});
await recipientSearch.click();
await waitForAnimations(page);
const recipientPopoverStateChanges = await page.evaluate(() => {
const trackedWindow = window as Window & {
__BUZZ_RECIPIENT_POPOVER_OBSERVER__?: MutationObserver;
__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[];
};
trackedWindow.__BUZZ_RECIPIENT_POPOVER_OBSERVER__?.disconnect();
return trackedWindow.__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__ ?? [];
});
expect(recipientPopoverStateChanges).not.toContain("closed");
const recipientList = page.getByTestId("persona-share-recipient-results");
await expect(recipientList).toBeVisible();
await expect
.poll(() =>
recipientList.evaluate(
(element) => element.scrollHeight - element.clientHeight,
),
)
.toBeGreaterThan(0);
const recipientListBox = await recipientList.boundingBox();
expect(recipientListBox).not.toBeNull();
if (!recipientListBox) return;
await page.mouse.move(
recipientListBox.x + recipientListBox.width / 2,
recipientListBox.y + recipientListBox.height / 2,
);
await page.mouse.wheel(0, 300);
await expect
.poll(() => recipientList.evaluate((element) => element.scrollTop))
.toBeGreaterThan(0);
await recipientSearch.fill("charlie");
await page
.getByTestId(
`persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`,
)
.click();
const charlieChip = page.getByTestId(
`persona-share-recipient-chip-${TEST_IDENTITIES.charlie.pubkey}`,
);
await expect(charlieChip).toBeVisible();
await expect(
page.getByRole("button", { name: "Verify charlie public key" }),
).toHaveCount(0);
await expect(recipientSearch).toHaveValue("");
await expect(recipientSearch).toHaveAttribute("placeholder", "");
await expect(
page
.getByTestId("persona-share-recipient-field")
.locator("svg.lucide-search"),
).toHaveCount(0);
await expect(
page
.getByTestId("persona-share-recipient-field")
.getByTestId("persona-share-recipient-access"),
).toHaveCount(0);
await expect(page.getByTestId("persona-share-send")).toBeVisible();
await recipientSearch.fill("bob");
await page
.getByTestId(`persona-share-recipient-option-${TEST_IDENTITIES.bob.pubkey}`)
.click();
const bobChip = page.getByTestId(
`persona-share-recipient-chip-${TEST_IDENTITIES.bob.pubkey}`,
);
await expect(bobChip).toBeVisible();
await expect(bobChip).not.toHaveClass(/buzz-poof-trigger/);
await waitForAnimations(page);
const bobChipBox = await bobChip.boundingBox();
expect(bobChipBox).not.toBeNull();
if (!bobChipBox) return;
const removePointer = {
x: bobChipBox.x + bobChipBox.width - 3,
y: bobChipBox.y + bobChipBox.height / 2,
};
await page.mouse.move(removePointer.x, removePointer.y);
await page.mouse.down();
await expect(page.locator(".buzz-poof-burst")).toHaveCount(0);
await page.mouse.up();
await expect(bobChip).toHaveCount(0);
await waitForAnimations(page);
const selectedRecipientFieldWidth =
(await recipientField.boundingBox())?.width ?? 0;
const charlieChipBox = await charlieChip.boundingBox();
expect(charlieChipBox).not.toBeNull();
if (!charlieChipBox) return;
await page.mouse.move(
charlieChipBox.x + charlieChipBox.width / 2,
charlieChipBox.y + charlieChipBox.height / 2,
);
await page.mouse.down();
const lastChipRemovalSamples = await page.evaluate(async () => {
const samples: Array<{ elapsed: number; fieldWidth: number }> = [];
const start = performance.now();
await new Promise<void>((resolve) => {
const sample = (now: number) => {
const field = document.querySelector<HTMLElement>(
'[data-testid="persona-share-recipient-field"]',
);
samples.push({
elapsed: now - start,
fieldWidth: field?.getBoundingClientRect().width ?? 0,
});
if (now - start >= 240) {
resolve();
return;
}
requestAnimationFrame(sample);
};
requestAnimationFrame(sample);
});
return samples;
});
await page.mouse.up();
await expect(charlieChip).toHaveCount(0);
const firstExpandedSample = lastChipRemovalSamples.find(
({ fieldWidth }) => fieldWidth > selectedRecipientFieldWidth + 1,
);
expect(firstExpandedSample?.elapsed).toBeLessThan(100);
expect(
Math.abs(
(lastChipRemovalSamples.at(-1)?.fieldWidth ?? 0) -
emptyRecipientFieldWidth,
),
).toBeLessThanOrEqual(1);
await recipientSearch.fill("bob");
await page
.getByTestId(`persona-share-recipient-option-${TEST_IDENTITIES.bob.pubkey}`)
.click();
await page.getByTestId("persona-share-send").click();
await expect(
page.getByText("Sent a copy of Animation Auditor"),
).toBeVisible();
const sentAgentMessages = await page.evaluate(() =>
(
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: { content?: string };
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? []
).filter((entry) => entry.command === "send_channel_message"),
);
expect(sentAgentMessages.at(-1)?.payload.content).toBe(
`\n[Animation Auditor](${sharedAgentUrl})`,
);
await expect(shareDialog).toHaveCount(0);
});
test("custom personas can be shared to the relay catalog", async ({ page }) => {
const personaId = "custom:catalog-analyst";
await installMockBridge(page, {
globalAgentConfig: {
env_vars: { ANTHROPIC_API_KEY: "sk-ant-test" },
provider: "anthropic",
model: "claude-opus-4-5",
},
personas: [
{
id: personaId,
displayName: "Catalog Analyst",
respondTo: "allowlist",
respondToAllowlist: [TEST_IDENTITIES.alice.pubkey],
systemPrompt: `## Design System And Styling
- For design-system changes, check the local guidance in \`DESIGN.md\`, \`docs/color-token-mapping.md\`, \`src/shared/ui/AGENTS.md\`, and \`src/features/design-system/AGENTS.md\` before judging the implementation.
- Check every changed visual surface in both light and dark mode. Missing dark-mode support is a review issue, not visual polish.
- Review the selected changes and explain whether \`git diff --cached --name-only --some-extremely-long-inline-option-that-must-wrap\` stays inside the catalog detail column.
\`\`\`text
This deliberately long fenced-code example must not establish the minimum width of the full custom-agent instruction document or force earlier prose outside the catalog detail pane.
\`\`\`
| Before | After | Why |
| --- | --- | --- |
| \`transition: all 300ms\` | \`transition: transform 200ms ease-out\` | Specify exact properties so a wide instruction table stays independently scrollable without expanding the full catalog detail pane. |
| \`transform: scale(0)\` | \`transform: scale(0.95); opacity: 0\` | Preserve physicality while keeping the shared agent instructions inside their container. |`,
},
],
});
await gotoApp(page);
await page.evaluate(() => {
document.documentElement.style.fontSize = "24px";
});
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
await expect(
page.getByTestId(`persona-catalog-list-item-${personaId}`),
).toHaveCount(0);
await page.keyboard.press("Escape");
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Share" }).click();
const catalogAccess = page.getByTestId("persona-share-catalog-access");
const shareDialog = page.getByTestId("persona-share-dialog");
const shareMainCard = shareDialog.getByTestId("persona-share-main-card");
const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link");
const catalogSection = shareDialog.getByTestId("persona-share-catalog");
await expect(shareMainCard.getByTestId("persona-share-catalog")).toHaveCount(
0,
);
await expect(catalogSection).toContainText("Share to catalog");
await expect(catalogSection).toContainText(
"Anyone in this community can find and use a copy.",
);
await expect(catalogSection).toContainText(
"Your agent instruction is shared as plaintext. Memories and secrets arent included.",
);
const [copyLinkButtonBox, catalogSectionBox, shareMainCardBox] =
await Promise.all([
copyLinkButton.boundingBox(),
catalogSection.boundingBox(),
shareMainCard.boundingBox(),
]);
// Catalog is its own card below the sharing controls.
expect(
(copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0),
).toBeLessThanOrEqual(
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0),
);
expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual(
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0),
);
await expect(catalogAccess).not.toBeChecked();
await catalogAccess.click();
await expect(catalogAccess).toBeChecked();
const storedPersonas = await invokeTauri<
Array<{ id: string; shared: boolean }>
>(page, "list_personas");
expect(
storedPersonas.find((persona) => persona.id === personaId)?.shared,
).toBe(true);
await page
.getByTestId("persona-share-dialog")
.getByRole("button", { name: "Close" })
.click();
await openPersonaCatalog(page);
await expect(
page.getByTestId(`persona-catalog-list-item-${personaId}`),
).toContainText("Catalog Analyst");
await selectCatalogPersona(page, personaId);
const catalogDialog = page.getByTestId("persona-catalog-dialog");
const catalogDetailPane = page.getByTestId("persona-catalog-detail-pane");
await expect(catalogDetailPane).toContainText("Design System And Styling");
await expect(catalogDialog).toBeVisible();
await expect(catalogDetailPane).toBeVisible();
await waitForAnimations(page);
const [catalogDialogRight, catalogDetailPaneRight] = await Promise.all([
catalogDialog.evaluate((element) => element.getBoundingClientRect().right),
catalogDetailPane.evaluate(
(element) => element.getBoundingClientRect().right,
),
]);
expect(catalogDetailPaneRight).toBeLessThanOrEqual(catalogDialogRight);
expect(
await catalogDetailPane.evaluate(
(element) => element.scrollWidth - element.clientWidth,
),
).toBeLessThanOrEqual(1);
const catalogInstruction = catalogDetailPane.locator(".message-markdown");
expect(
await catalogInstruction.evaluate(
(element) => element.scrollWidth - element.clientWidth,
),
).toBeLessThanOrEqual(1);
await page.keyboard.press("Escape");
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Edit" }).click();
const editDialog = page.getByTestId("persona-dialog");
const catalogPublishNotice = editDialog.getByTestId(
"persona-dialog-catalog-publish-notice",
);
await expect(catalogPublishNotice).toHaveCount(0);
await expect(
editDialog.getByRole("button", { name: "Save and publish" }),
).toHaveCount(0);
await expect(
editDialog.getByRole("button", { name: "Save changes" }),
).toBeVisible();
await editDialog
.getByLabel("Agent instructions")
.fill("Review the latest catalog changes.");
await expect(catalogPublishNotice).toHaveText(
"This agent is in the community catalog. Your changes will be published when you save.",
);
await expect(
editDialog.getByRole("button", { name: "Save changes" }),
).toHaveCount(0);
await editDialog.getByRole("button", { name: "Save and publish" }).click();
await expect(editDialog).toHaveCount(0);
// The promise in the button label is only kept by the command that awaits the
// relay; a plain `update_persona` merely enqueues a head best-effort.
await expect
.poll(() => countCommandInvocations(page, "update_persona_and_publish"))
.toBe(1);
expect(await countCommandInvocations(page, "update_persona")).toBe(0);
await expect(
page.getByText(
"Updated Catalog Analyst and published it to the community catalog.",
),
).toBeVisible();
await openPersonaCatalog(page);
await selectCatalogPersona(page, personaId);
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Review the latest catalog changes.",
);
await page.keyboard.press("Escape");
await page.getByLabel("Open actions for Catalog Analyst").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await expect(catalogAccess).toBeChecked();
await catalogAccess.click();
await expect(catalogAccess).not.toBeChecked();
await page
.getByTestId("persona-share-dialog")
.getByRole("button", { name: "Close" })
.click();
await openPersonaCatalog(page);
await expect(
page.getByTestId(`persona-catalog-list-item-${personaId}`),
).toHaveCount(0);
});
test("a queued catalog share is not presented as relay-published", async ({
page,
}) => {
const personaId = "custom:queued-catalog-agent";
await installMockBridge(page, {
personas: [
{
id: personaId,
displayName: "Queued Catalog Agent",
systemPrompt: "Wait for relay acceptance.",
},
],
personaSharePublicationStatuses: ["queued"],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Queued Catalog Agent").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await page.getByTestId("persona-share-catalog-access").click();
await expect(
page.getByText(
"Sharing Queued Catalog Agent is queued. It will appear after the relay accepts the update.",
),
).toBeVisible();
await page
.getByTestId("persona-share-dialog")
.getByRole("button", { name: "Close" })
.click();
await openPersonaCatalog(page);
await expect(
page.getByTestId(`persona-catalog-list-item-${personaId}`),
).toHaveCount(0);
});
test("a foreign reader does not receive an unshared kind 30175 persona", async ({
page,
}) => {
const personaId = "private-reviewer";
const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`;
await installMockBridge(page, {
personaCatalogEvents: [
createCatalogEvent({
ownerPubkey: TEST_IDENTITIES.alice.pubkey,
sourcePersonaId: personaId,
displayName: "Alices Private Reviewer",
systemPrompt: "This instruction must remain private.",
shared: false,
}),
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
await expect(
page.getByTestId(`persona-catalog-list-item-${remoteCatalogId}`),
).toHaveCount(0);
await expect(
page.getByText("No shared agents", { exact: true }),
).toBeVisible();
});
test("a catalog entry keeps the owner's emoji avatar", async ({ page }) => {
const personaId = "emoji-reviewer";
const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`;
// Emoji avatars persist as inline percent-encoded SVG rather than a hosted
// URL, so build the value with the same producer the editor uses — a
// hand-rolled data URL would pass even if the real shape stopped matching.
const avatarUrl = emojiAvatarDataUrl("🐝", "#FFCC00");
await installMockBridge(page, {
personaCatalogEvents: [
createCatalogEvent({
avatarUrl,
ownerPubkey: TEST_IDENTITIES.alice.pubkey,
sourcePersonaId: personaId,
displayName: "Alices Reviewer",
systemPrompt: "Review changes for the whole community.",
}),
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
// An `<img>` carrying the avatar — not the initials fallback — in both the
// list row and the detail header is what proves the projection kept it.
const remoteEntry = page.getByTestId(
`persona-catalog-list-item-${remoteCatalogId}`,
);
await expect(remoteEntry.locator("img")).toHaveAttribute("src", avatarUrl);
await remoteEntry.click();
await expect(
page.getByTestId("persona-catalog-detail-pane").locator("img").first(),
).toHaveAttribute("src", avatarUrl);
});
test("a community member can discover and add another member's catalog agent", async ({
page,
}) => {
const personaId = "shared-reviewer";
const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`;
await installMockBridge(page, {
personaCatalogEvents: [
createCatalogEvent({
ownerPubkey: TEST_IDENTITIES.alice.pubkey,
sourcePersonaId: personaId,
displayName: "Alices Reviewer",
systemPrompt: "Review changes for the whole community.",
}),
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
const remoteEntry = page.getByTestId(
`persona-catalog-list-item-${remoteCatalogId}`,
);
await expect(remoteEntry).toContainText("Alices Reviewer");
await remoteEntry.click();
// The detail pane resolves the publisher's display name; 'Community member'
// is only the fallback for an unresolvable pubkey.
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Added by alice",
);
await page
.getByRole("button", {
name: "Add Alices Reviewer from Agent Catalog",
})
.click();
await expect
.poll(() => countCommandInvocations(page, "create_persona"))
.toBe(1);
const imported = await invokeTauri<
Array<{
display_name: string;
system_prompt: string;
shared: boolean;
catalog_source: { owner_pubkey: string; persona_id: string } | null;
}>
>(page, "list_personas");
expect(
imported.find((persona) => persona.display_name === "Alices Reviewer"),
).toMatchObject({
system_prompt: "Review changes for the whole community.",
shared: false,
// Provenance is what lets the catalog recognise the copy on the next open.
catalog_source: {
owner_pubkey: TEST_IDENTITIES.alice.pubkey,
persona_id: personaId,
},
});
// Reopening must offer the entry as already added rather than minting a
// second copy — the copy has a fresh local id, so only the stored
// coordinate can link it back to Alice's publication.
await page.keyboard.press("Escape");
await openPersonaCatalog(page);
// The entry now projects onto the local copy, so its list-item testid is the
// local persona id rather than the catalog coordinate.
await expect(
page.getByTestId(`persona-catalog-list-item-${remoteCatalogId}`),
).toHaveCount(0);
await page
.locator('[data-testid^="persona-catalog-list-item-"]')
.filter({ hasText: "Alices Reviewer" })
.click();
const addedTarget = page.getByRole("button", {
name: "Alices Reviewer is already in My Agents",
});
await expect(addedTarget).toBeDisabled();
await expect(addedTarget).toHaveText("Added to My Agents");
expect(await countCommandInvocations(page, "create_persona")).toBe(1);
});
test("catalog detail shows Community member when the publisher profile cannot be resolved", async ({
page,
}) => {
// A pubkey that is not in the mock profile registry — profile resolution
// will fail and the detail pane must fall back gracefully.
const unknownPubkey =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const personaId = "unresolvable-reviewer";
await installMockBridge(page, {
personaCatalogEvents: [
createCatalogEvent({
ownerPubkey: unknownPubkey,
sourcePersonaId: personaId,
displayName: "Mystery Agent",
systemPrompt: "Published by someone whose profile cannot be fetched.",
}),
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await openPersonaCatalog(page);
await page
.getByTestId(
`persona-catalog-list-item-catalog:${unknownPubkey}:${personaId}`,
)
.click();
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
"Added by Community member",
);
});
test("one share level selector drives both the link and send paths", async ({
page,
}) => {
await page.emulateMedia({ reducedMotion: "no-preference" });
const linkedAgentPubkey = TEST_IDENTITIES.alice.pubkey;
const profileAvatarUrl = "https://mock.relay/media/profile-only-avatar.png";
const profileAvatarBytes = Uint8Array.from(
atob(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
),
(character) => character.charCodeAt(0),
);
await page.route(profileAvatarUrl, async (route) => {
await route.fulfill({
body: Buffer.from(profileAvatarBytes),
contentType: "image/png",
status: 200,
});
});
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
await installMockBridge(page, {
personas: [
{
id: "custom:animation-auditor",
displayName: "Animation Auditor",
systemPrompt: "You audit animations.",
},
],
managedAgents: [
{
pubkey: linkedAgentPubkey,
name: "Animation Auditor",
personaId: "custom:animation-auditor",
status: "running",
},
],
searchProfiles: [
{
pubkey: linkedAgentPubkey,
displayName: "Animation Auditor",
avatarUrl: profileAvatarUrl,
isAgent: true,
},
{
pubkey: TEST_IDENTITIES.charlie.pubkey,
displayName: "Charlie",
},
],
uploadDescriptors: [
{
url: `https://mock.relay/media/${"c".repeat(64)}.png`,
sha256: "c".repeat(64),
size: 1234,
type: "image/png",
uploaded: Math.floor(Date.now() / 1000),
filename: "animation-auditor.agent.png",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Animation Auditor").click();
await page.getByRole("menuitem", { name: "Share" }).click();
const shareDialog = page.getByTestId("persona-share-dialog");
const shareMainCard = shareDialog.getByTestId("persona-share-main-card");
const initialShareCardHeight = await shareMainCard.evaluate(
(element) => element.getBoundingClientRect().height,
);
const shareLevel = shareDialog.getByLabel("What to include", {
exact: true,
});
const catalogAccess = shareDialog.getByLabel("Share to catalog");
const recipientField = page.getByTestId("persona-share-recipient-field");
const emptyRecipientFieldBox = await recipientField.boundingBox();
await expect(shareDialog.getByTestId("persona-share-send")).toHaveCount(0);
await expect(shareLevel).toHaveText("Agent only");
expect((await shareLevel.boundingBox())?.width).toBeLessThan(140);
expect(await shareLevel.evaluate((element) => element.tagName)).toBe(
"BUTTON",
);
await expect(shareLevel).toHaveCSS("text-decoration-line", "none");
await expect(shareLevel).toHaveCSS("padding-left", "8px");
await expect(shareLevel).toHaveCSS("padding-right", "8px");
await expect(catalogAccess).not.toBeChecked();
await expect(catalogAccess).toHaveCSS("cursor", "default");
await expect(
shareDialog.getByTestId("persona-share-link-settings"),
).toContainText("Share settings");
await expect(
shareDialog.getByText("Share settings", { exact: true }),
).toHaveClass(/text-xs.*text-secondary-foreground\/75/);
const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link");
const recipientFieldBox = await recipientField.boundingBox();
const [shareLevelBox, copyLinkButtonBox, catalogAccessBox] =
await Promise.all([
shareLevel.boundingBox(),
copyLinkButton.boundingBox(),
catalogAccess.boundingBox(),
]);
// Reading order: who → what's included → copy link → catalog.
expect(shareLevelBox?.y ?? 0).toBeGreaterThanOrEqual(
(recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0),
);
expect(copyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual(
(shareLevelBox?.y ?? 0) + (shareLevelBox?.height ?? 0),
);
expect(catalogAccessBox?.y ?? 0).toBeGreaterThanOrEqual(
(copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0),
);
// The memory choice is stated once, governing both delivery actions —
// neither the recipients row nor the link row carries its own copy.
await expect(
shareDialog.getByTestId("persona-share-recipient-access"),
).toHaveCount(0);
await expect(
shareDialog.getByTestId("persona-share-link-access"),
).toHaveCount(0);
await expect(shareLevel).toHaveCount(1);
await expect(
shareDialog.getByTestId("persona-share-memory-warning"),
).toHaveCount(0);
await shareLevel.click();
await expect(page.getByRole("menuitemradio")).toHaveText([
"Agent only",
"Agent + core memory",
"Agent + all memories",
]);
await page
.getByRole("menuitemradio", { name: "Agent + core memory" })
.click();
await expect(shareLevel).toHaveText("Agent + core memory");
await waitForAnimations(page);
const expandedShareCardHeight = await shareMainCard.evaluate(
(element) => element.getBoundingClientRect().height,
);
const inlineMemoryWarning = shareDialog.getByTestId(
"persona-share-memory-warning",
);
// No recipient is selected yet: the warning tracks the chosen contents, not
// whichever delivery button might be pressed.
await expect(shareDialog.getByTestId("persona-share-send")).toHaveCount(0);
await expect(inlineMemoryWarning).toBeVisible();
await expect(inlineMemoryWarning).toContainText(
"Memory is stored as plaintext in the snapshot.",
);
await expect(inlineMemoryWarning).toContainText(
"Only share it with people you trust.",
);
expect(expandedShareCardHeight).toBeGreaterThan(initialShareCardHeight);
await page.getByTestId("persona-share-copy-link").click();
const memoryConfirmation = page.getByTestId(
"persona-share-memory-confirmation",
);
await expect(memoryConfirmation).toBeVisible();
await expect(
memoryConfirmation.getByRole("heading", { name: "Share memories?" }),
).toBeVisible();
await expect(memoryConfirmation).toContainText("plaintext core memory");
await expect(memoryConfirmation).toContainText(
"Anyone with the link can view it.",
);
await expect(memoryConfirmation).toContainText(
"Only share with people you trust.",
);
const encodeLevelsBeforeLinkConfirmation = await page.evaluate(() =>
(
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: { memoryLevel?: string };
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? []
)
.filter((entry) => entry.command === "encode_agent_snapshot_for_send")
.map((entry) => entry.payload.memoryLevel),
);
expect(encodeLevelsBeforeLinkConfirmation).toEqual([]);
await memoryConfirmation.getByTestId("persona-share-memory-confirm").click();
await expect(page.getByTestId("persona-share-copy-link")).toContainText(
"Copied",
);
await shareLevel.click();
await page
.getByRole("menuitemradio", { name: "Agent only", exact: true })
.click();
await expect(shareLevel).toHaveText("Agent only");
await expect(inlineMemoryWarning).toHaveCount(0);
const recipientSearch = page.getByTestId("persona-share-recipient-search");
await recipientSearch.fill("charlie");
await page
.getByTestId(
`persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`,
)
.click();
const recipientInputRegion = recipientField.getByTestId(
"persona-share-recipient-input-region",
);
await expect(recipientField).toHaveCSS("column-gap", "12px");
await expect(recipientInputRegion).toHaveCSS("flex-wrap", "wrap");
const sendButton = shareDialog.getByTestId("persona-share-send");
await expect(sendButton).toBeVisible();
await waitForAnimations(page);
const resizedRecipientFieldBox = await recipientField.boundingBox();
expect(resizedRecipientFieldBox?.width).toBeLessThan(
emptyRecipientFieldBox?.width ?? 0,
);
await expect
.poll(async () => {
const [sendButtonBox, currentCopyLinkButtonBox] = await Promise.all([
sendButton.boundingBox(),
copyLinkButton.boundingBox(),
]);
return Math.abs(
(sendButtonBox?.x ?? 0) +
(sendButtonBox?.width ?? 0) -
((currentCopyLinkButtonBox?.x ?? 0) +
(currentCopyLinkButtonBox?.width ?? 0)),
);
})
.toBeLessThanOrEqual(1);
// Same single selector now drives the send path; picking a level here is
// what the send confirmation must report.
await shareLevel.click();
await page
.getByRole("menuitemradio", { name: "Agent + all memories" })
.click();
await expect(shareLevel).toHaveText("Agent + all memories");
await expect(inlineMemoryWarning).toBeVisible();
await waitForAnimations(page);
expect(
await shareLevel
.locator("span")
.evaluate((element) => element.scrollWidth <= element.clientWidth),
).toBe(true);
await page.getByTestId("persona-share-send").click();
await expect(memoryConfirmation).toBeVisible();
await expect(memoryConfirmation).toContainText("plaintext all memories");
await expect(memoryConfirmation).toContainText(
"Charlie—and anyone with the file link—can view it.",
);
const encodeLevelsBeforeSendConfirmation = await page.evaluate(() =>
(
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: { memoryLevel?: string };
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? []
)
.filter((entry) => entry.command === "encode_agent_snapshot_for_send")
.map((entry) => entry.payload.memoryLevel),
);
expect(encodeLevelsBeforeSendConfirmation).not.toContain("none");
expect(encodeLevelsBeforeSendConfirmation).toContain("core");
expect(encodeLevelsBeforeSendConfirmation).not.toContain("everything");
await memoryConfirmation.getByTestId("persona-share-memory-confirm").click();
await expect(
page.getByText("Sent a copy of Animation Auditor"),
).toBeVisible();
const encodePayloads = await page.evaluate(() =>
(
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: unknown;
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? []
)
.filter((entry) => entry.command === "encode_agent_snapshot_for_send")
.map((entry) => entry.payload),
);
expect(
encodePayloads.filter(
(payload) => (payload as { memoryLevel?: string }).memoryLevel === "none",
),
).toHaveLength(0);
expect(encodePayloads).toEqual(
expect.arrayContaining([
expect.objectContaining({
memoryLevel: "core",
memorySourcePubkey: linkedAgentPubkey,
avatarPngDataUrl: `data:image/png;base64,${Buffer.from(
profileAvatarBytes,
).toString("base64")}`,
}),
expect.objectContaining({
memoryLevel: "everything",
memorySourcePubkey: linkedAgentPubkey,
}),
]),
);
});
test("people sharing excludes the moderation recipient", async ({ page }) => {
await openSafetyShareDialog(page, {
relaySelf: TEST_IDENTITIES.charlie.pubkey,
relaySelfDelayMs: 800,
});
const search = page.getByTestId("persona-share-recipient-search");
await expect(search).toBeEnabled({ timeout: 5_000 });
await search.fill("charlie");
await expect(
page.getByTestId(
`persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`,
),
).toHaveCount(0);
await expect(page.getByText("No people found.")).toBeVisible();
const commands = await readAgentShareCommands(page);
expect(
commands.filter((entry) =>
[
"open_dm",
"encode_agent_snapshot_for_send",
"upload_media_bytes",
"send_channel_message",
].includes(entry.command),
),
).toEqual([]);
});
test("people sharing blocks a timeout before encoding or upload", async ({
page,
}) => {
await openSafetyShareDialog(page);
await selectCharlieRecipient(page);
await page.evaluate(() => {
(
window as Window & {
__BUZZ_E2E_ACTIVATE_TIMEOUT__?: (expiresAtMs: number) => void;
}
).__BUZZ_E2E_ACTIVATE_TIMEOUT__?.(Date.now() + 60_000);
});
await page.getByTestId("persona-share-send").click();
await expect(page.getByText("Couldnt send agent. Try again.")).toBeVisible();
const commands = await readAgentShareCommands(page);
expect(
commands.filter((entry) =>
[
"encode_agent_snapshot_for_send",
"upload_media_bytes",
"send_channel_message",
].includes(entry.command),
),
).toEqual([]);
});
test("people sharing rechecks destination eligibility after encoding", async ({
page,
}) => {
await openSafetyShareDialog(page, { encodeDelayMs: 800 });
await selectCharlieRecipient(page);
await page.getByTestId("persona-share-send").click();
await expect
.poll(async () => {
const commands = await readAgentShareCommands(page);
return commands.filter(
(entry) => entry.command === "encode_agent_snapshot_for_send",
).length;
})
.toBe(1);
await page.evaluate(() => {
const testWindow = window as Window & {
__BUZZ_E2E_MUTATE_CHANNEL__?: (options: {
channelId: string;
channelType: "forum";
}) => void;
__BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise<void>;
};
testWindow.__BUZZ_E2E_MUTATE_CHANNEL__?.({
channelId: "d1ec7000-d000-4000-8000-000000000001",
channelType: "forum",
});
return testWindow.__BUZZ_E2E_INVALIDATE_CHANNELS__?.();
});
await expect(page.getByText("Couldnt send agent. Try again.")).toBeVisible({
timeout: 5_000,
});
const commands = await readAgentShareCommands(page);
expect(
commands.filter(
(entry) => entry.command === "encode_agent_snapshot_for_send",
),
).toHaveLength(1);
expect(
commands.filter((entry) => entry.command === "upload_media_bytes"),
).toHaveLength(0);
expect(
commands.filter((entry) => entry.command === "send_channel_message"),
).toHaveLength(0);
});
test("people sharing guards the full action against duplicate sends", async ({
page,
}) => {
await openSafetyShareDialog(page, { encodeDelayMs: 500 });
await selectCharlieRecipient(page);
await page.getByTestId("persona-share-send").evaluate((button) => {
if (!(button instanceof HTMLButtonElement)) return;
button.click();
button.click();
});
await expect(page.getByText("Sent a copy of Safety Auditor")).toBeVisible({
timeout: 5_000,
});
await expect(page.getByText("Couldnt send agent. Try again.")).toHaveCount(
0,
);
const commands = await readAgentShareCommands(page);
for (const command of [
"open_dm",
"encode_agent_snapshot_for_send",
"upload_media_bytes",
"send_channel_message",
]) {
expect(commands.filter((entry) => entry.command === command)).toHaveLength(
1,
);
}
});
test("people sharing stays mounted while a send is pending", async ({
page,
}) => {
await openSafetyShareDialog(page, { encodeDelayMs: 800 });
await selectCharlieRecipient(page);
await page.getByTestId("persona-share-send").click();
await page.keyboard.press("Escape");
await expect(page.getByTestId("persona-share-dialog")).toBeVisible();
await expect(page.getByText("Sent a copy of Safety Auditor")).toBeVisible({
timeout: 5_000,
});
});
test("export from share aligns selections and animates memory details", async ({
page,
}) => {
await page.emulateMedia({ reducedMotion: "no-preference" });
await installMockBridge(page, {
personas: [
{
id: "custom:animation-auditor",
displayName: "Animation Auditor",
systemPrompt: "You audit animations.",
},
],
managedAgents: [
{
pubkey: TEST_IDENTITIES.alice.pubkey,
name: "Animation Auditor",
personaId: "custom:animation-auditor",
status: "running",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Animation Auditor").click();
await page.getByRole("menuitem", { name: "Share" }).click();
await page.getByTestId("persona-share-export").click();
const exportDialog = page.getByTestId("agent-snapshot-export-dialog");
const memoryTrigger = exportDialog.getByLabel("Memories");
await expect(memoryTrigger).toHaveText("Agent only");
expect((await memoryTrigger.boundingBox())?.width).toBeLessThan(112);
await expect(memoryTrigger).toHaveCSS("text-align", "right");
await expect(memoryTrigger.locator("svg.lucide-chevron-down")).toBeVisible();
const initialHeight = await exportDialog.evaluate(
(element) => element.getBoundingClientRect().height,
);
await memoryTrigger.click();
await page
.getByRole("menuitemradio", { name: "Agent + core memory" })
.click();
const heightSamples = await exportDialog.evaluate(async (element) => {
const samples: number[] = [];
const start = performance.now();
await new Promise<void>((resolve) => {
const sample = (now: number) => {
samples.push(element.getBoundingClientRect().height);
if (now - start >= 280) {
resolve();
return;
}
requestAnimationFrame(sample);
};
requestAnimationFrame(sample);
});
return samples;
});
await expect(
exportDialog.getByTestId("agent-snapshot-memory-warning"),
).toBeVisible();
expect(heightSamples.at(-1)).toBeGreaterThan(initialHeight);
expect(
new Set(heightSamples.map((height) => Math.round(height))).size,
).toBeGreaterThan(2);
});
test("team-managed personas do not expose editable actions", async ({
page,
}) => {
await installMockBridge(page, {
personas: [
{
id: "team:analyst",
displayName: "Team Analyst",
sourceTeam: "team-research-002",
systemPrompt: "You are Team Analyst.",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await page.getByLabel("Open actions for Team Analyst").click();
await expect(page.getByRole("menuitem")).toHaveText([
"Duplicate",
"Share",
"Managed by team",
]);
await expect(page.getByRole("menuitem", { name: "Edit" })).toHaveCount(0);
await expect(page.getByRole("menuitem", { name: "Delete" })).toHaveCount(0);
});
test("inactive built-ins cannot be used to create teams", async ({ page }) => {
await gotoApp(page);
const error = await invokeTauriExpectError(page, "create_team", {
input: {
name: "Honeys",
personaIds: ["builtin:honey"],
},
});
expect(error).toBe("Honey is not in My Agents.");
});
test("built-in removal failures show up from My Agents", async ({ page }) => {
await installMockBridge(page, {
activePersonaIds: ["builtin:honey"],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await invokeTauri(page, "create_team", {
input: {
name: "Honeys",
personaIds: ["builtin:honey"],
},
});
await page.getByLabel("Open actions for Honey").click();
await page.getByRole("menuitem", { name: "Delete" }).click();
await expect(
page
.locator("[data-sonner-toast]")
.filter({ hasText: "Honey is still referenced by a team." }),
).toBeVisible();
});
test("personas referenced by teams cannot be deleted", async ({ page }) => {
await gotoApp(page);
const persona = await invokeTauri<{ id: string }>(page, "create_persona", {
input: {
displayName: "Analyst",
systemPrompt: "You are Analyst.",
},
});
await invokeTauri(page, "create_team", {
input: {
name: "Analysts",
personaIds: [persona.id],
},
});
const error = await invokeTauriExpectError(page, "delete_persona", {
id: persona.id,
});
expect(error).toBe(
"Analyst is still referenced by a team. Remove it from those teams first.",
);
});
test("start pill morphs into the running dot without remounting the avatar", async ({
page,
}) => {
const personaId = "custom:motion-auditor";
const pubkey = "ab".repeat(32);
const activeDotSize = 18;
await page.emulateMedia({ reducedMotion: "no-preference" });
await installMockBridge(page, {
personas: [
{
avatarUrl: emojiAvatarDataUrl("✨", "#7657FF"),
displayName: "Motion Auditor",
id: personaId,
systemPrompt: "You audit motion continuity.",
},
],
managedAgents: [
{
name: "Motion Auditor",
personaId,
pubkey,
status: "stopped",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
const card = page.getByTestId(`persona-agent-row-${personaId}`);
const startButton = page.getByTestId(`agent-runtime-start-${pubkey}`);
const badge = startButton.locator("xpath=../..");
const initialAvatar = await card
.getByAltText("Motion Auditor avatar")
.elementHandle();
expect(initialAvatar).not.toBeNull();
const samplesPromise = badge.evaluate(async (element) => {
const samples: Array<{
backgroundColor: string;
height: number;
width: number;
}> = [];
const startedAt = performance.now();
while (performance.now() - startedAt < 440) {
const bounds = element.getBoundingClientRect();
samples.push({
backgroundColor: getComputedStyle(element).backgroundColor,
height: bounds.height,
width: bounds.width,
});
await new Promise<void>((resolve) =>
requestAnimationFrame(() => resolve()),
);
}
return samples;
});
await page.waitForTimeout(32);
await startButton.click();
await expect(
page.getByTestId(`agent-runtime-active-${pubkey}`),
).toBeVisible();
const samples = await samplesPromise;
const finalAvatar = await card
.getByAltText("Motion Auditor avatar")
.elementHandle();
expect(samples[0]?.width).toBeCloseTo(56, 0);
expect(samples[0]?.height).toBeCloseTo(36, 0);
expect(
samples.some(
(sample) =>
sample.width > activeDotSize &&
sample.width < 56 &&
sample.height > activeDotSize &&
sample.height < 36,
),
).toBe(true);
expect(samples.at(-1)?.width).toBeCloseTo(activeDotSize, 0);
expect(samples.at(-1)?.height).toBeCloseTo(activeDotSize, 0);
expect(samples.at(-1)?.backgroundColor).not.toBe(samples[0]?.backgroundColor);
await expect(
page.getByTestId(`agent-runtime-active-${pubkey}`).locator("xpath=../.."),
).toHaveClass(/bg-emerald-500/);
expect(
await initialAvatar?.evaluate(
(before, after) => before === after,
finalAvatar,
),
).toBe(true);
});
test("duplicate instances move from the agents gallery into the agent profile", async ({
page,
}) => {
const personaId = "custom:duplicate-auditor";
const primaryPubkey = TEST_IDENTITIES.alice.pubkey;
const additionalPubkey = TEST_IDENTITIES.charlie.pubkey;
await installMockBridge(page, {
personas: [
{
id: personaId,
displayName: "Duplicate Auditor",
systemPrompt: "You audit duplicate instances.",
},
],
managedAgents: [
{
pubkey: primaryPubkey,
name: "Duplicate Auditor",
personaId,
status: "running",
},
{
pubkey: additionalPubkey,
name: "Duplicate Auditor",
personaId,
status: "stopped",
},
],
});
await gotoApp(page);
await page.getByTestId("open-agents-view").click();
await expect(page.getByText("Additional running agents")).toHaveCount(0);
await expect(
page.getByTestId(`managed-agent-${additionalPubkey}`),
).toHaveCount(0);
await page.getByTestId(`persona-agent-row-${personaId}`).click();
await page.getByTestId("user-profile-instances").click();
await page.getByTestId(`user-profile-instance-${additionalPubkey}`).click();
await expect(page.getByTestId("user-profile-panel")).toBeVisible();
await page.getByTestId("user-profile-settings-menu-trigger").click();
await expect(
page.getByTestId(`user-profile-agent-delete-${additionalPubkey}`),
).toBeVisible();
});