diff --git a/desktop/package.json b/desktop/package.json index e810d2bc28..3e9e176d88 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -20,6 +20,7 @@ "test:e2e": "pnpm build:e2e && playwright test", "test:e2e:smoke": "pnpm build:e2e && playwright test --project=smoke", "test:e2e:integration": "pnpm build:e2e && playwright test --project=integration", + "test:e2e:bestie": "VITE_BUZZ_BESTIE=1 pnpm build:e2e && playwright test --config=playwright.bestie.config.ts", "test:e2e:release-smoke": "pnpm build:e2e && playwright test --config=playwright.release-smoke.config.ts", "test:e2e:report": "playwright show-report", "tauri:build": "tauri build" diff --git a/desktop/playwright.bestie.config.ts b/desktop/playwright.bestie.config.ts new file mode 100644 index 0000000000..e543e3e1cc --- /dev/null +++ b/desktop/playwright.bestie.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from "@playwright/test"; + +const webPort = process.env.BUZZ_BESTIE_E2E_WEB_PORT ?? "4174"; +const webUrl = `http://127.0.0.1:${webPort}`; + +export default defineConfig({ + testDir: "./tests/e2e", + testMatch: "**/bestie-sidebar.spec.ts", + timeout: 30_000, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: [["list"]], + use: { + ...devices["Desktop Chrome"], + baseURL: webUrl, + screenshot: "only-on-failure", + trace: "on-first-retry", + }, + webServer: { + command: `python3 -m http.server ${webPort} -d dist`, + cwd: ".", + reuseExistingServer: false, + url: webUrl, + }, +}); diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index 50c9a80f6b..19f4aa59dc 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -552,7 +552,9 @@ export function AppSidebar({ data-testid="sidebar-scroll-content" > Promise; onSelectAgents: () => void; onSelectHome: () => void; onSelectProjects: () => void; @@ -89,7 +95,9 @@ export function AppSidebarPinnedHeader({ } export function AppSidebarPrimaryMenu({ + bestieRelayUrl, homeBadgeCount, + onOpenDm, onSelectAgents, onSelectHome, onSelectProjects, @@ -167,6 +175,12 @@ export function AppSidebarPrimaryMenu({ Agents + + + ); } + +function BestieSidebarMenuItem({ + onOpenDm, + relayUrl, +}: { + onOpenDm: (input: { pubkeys: string[] }) => Promise; + relayUrl?: string | null; +}) { + const managedAgentsQuery = useManagedAgentsQuery(); + const bestieAgent = React.useMemo( + () => pickBestieAgent(managedAgentsQuery.data ?? [], relayUrl), + [managedAgentsQuery.data, relayUrl], + ); + + if (!bestieAgent) return null; + + return ( + + void onOpenDm({ pubkeys: [bestieAgent.pubkey] })} + tooltip={`Message ${bestieAgent.name}`} + type="button" + > + + Bestie + + + ); +} diff --git a/desktop/tests/e2e/bestie-sidebar.spec.ts b/desktop/tests/e2e/bestie-sidebar.spec.ts new file mode 100644 index 0000000000..dc15ebb599 --- /dev/null +++ b/desktop/tests/e2e/bestie-sidebar.spec.ts @@ -0,0 +1,54 @@ +import { expect, test } from "@playwright/test"; + +import { installMockBridge } from "../helpers/bridge"; +import { FEATURE_OVERRIDES_STORAGE_KEY } from "../helpers/features"; + +const BESTIE_PUBKEY = + "be571e0000000000000000000000000000000000000000000000000000000000"; + +const bestie = { + avatarUrl: null, + name: "Bestie", + personaId: "builtin:bestie", + pubkey: BESTIE_PUBKEY, + status: "running" as const, +}; + +test("the enabled Bestie experiment adds a direct-message entry below Agents", async ({ + page, +}) => { + await installMockBridge(page, { managedAgents: [bestie] }); + await page.goto("/"); + + const agentsEntry = page.getByTestId("open-agents-view"); + const bestieEntry = page.getByTestId("open-bestie-dm"); + await expect(bestieEntry).toBeVisible(); + await expect(bestieEntry).toContainText("Bestie"); + + const [agentsBox, bestieBox] = await Promise.all([ + agentsEntry.boundingBox(), + bestieEntry.boundingBox(), + ]); + expect(agentsBox).not.toBeNull(); + expect(bestieBox).not.toBeNull(); + expect(bestieBox?.y).toBeGreaterThan(agentsBox?.y ?? 0); + + await bestieEntry.click(); + await expect(page.getByTestId("chat-title")).toHaveText("Bestie"); +}); + +test("the disabled Bestie experiment does not mount the sidebar entry", async ({ + page, +}) => { + await installMockBridge(page, { managedAgents: [bestie] }); + await page.addInitScript((key) => { + const overrides = JSON.parse( + window.localStorage.getItem(key) ?? "{}", + ) as Record; + overrides.bestie = false; + window.localStorage.setItem(key, JSON.stringify(overrides)); + }, FEATURE_OVERRIDES_STORAGE_KEY); + await page.goto("/"); + + await expect(page.getByTestId("open-bestie-dm")).toHaveCount(0); +});