Files
buzz/desktop/tests/e2e/agent-control-regressions.spec.ts
Will Pfleger 7a9a5233d9 🤖 fix(desktop): harden smoke E2E tests against Bestie overlay and toast timing (#7270)
## What

Two remaining E2E hardening fixes from the Desktop Smoke flake pattern
introduced by `ac5a18697` (Bestie — added `VITE_BUZZ_BESTIE=1` to
`.env.e2e` and mounted `BestieGlobalOverlay` globally).

The toast/DM-retry fix landed independently in main via #7127
(`input.hover()` + bounded `toHaveCount` wait); that hunk is dropped
from this PR.

## Fixes

### `agent-control-regressions.spec.ts:240` — Stop does not accept an
unconfirmed or foreign-channel result

**Cause:** Playwright 1.60.0's `page.clock.install()` fakes all timers
including `requestAnimationFrame`. The test called it before opening the
settings menu. With RAF frozen, the `DropdownMenuContent`'s `zoom-in-95
duration-150` CSS enter-animation never advances — Playwright's
stability check observes a continuously-changing bounding box until the
30s test timeout.

**Fix:** Re-sequence so the menu is opened on real time first. After
`openAgentActivity`, open the trigger, assert visibility/enabled, call
`waitForAnimations(page)` to settle the enter-animation (real
`setTimeout`, no fake clock installed yet), then install the clock. The
`fastForward(8_001)` correlation timeout still works because it's
scheduled after the clock is active. Pointer actionability preserved —
normal `stop.click()` (no `force`) fails with pointer-interception under
a covering surface.

### `message-feedback-snapshots.spec.ts:97` — profile hover uses the
channel hover surface

**Cause:** `channel.hover()` triggers a CSS `transition-colors`
animation. With the Bestie `LayoutGroup` mounted, `evaluate()` captures
a mid-transition background value that never matches the profile card's
settled token.

**Fix:** `waitForAnimations(page)` after `channel.hover()` and before
reading `channelHoverColor`.

## Evidence

- Target specs pass: stop-turn 8/8, profile-hover passes.
- Full `agent-control-regressions.spec.ts` (7 tests) green.
- `just desktop-typecheck` clean at pushed head `0fbfe2a9f`.
- No production code changed.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-09-03 08:59:49 -04:00

431 lines
14 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
import { waitForAnimations } from "../helpers/animations";
const AGENT_PUBKEY = TEST_IDENTITIES.charlie.pubkey;
const RESTORED_UNSCOPED_AGENT_PUBKEY = TEST_IDENTITIES.outsider.pubkey;
const CHANNEL_AGENTS = "94a444a4-c0a3-5966-ab05-530c6ddc2301";
const CHANNEL_GENERAL = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
const CHANNEL_FOREIGN = "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9";
type ControlRequest = {
agentPubkey: string;
payload: {
type: "cancel_turn" | "switch_model";
channelId?: string;
requestId?: string;
modelId?: string;
};
};
type E2eControlWindow = Window & {
__BUZZ_E2E_RUN_MODEL_SWITCH__?: (input: {
agentPubkey: string;
channelIds: string[];
modelId: string;
requestId: string;
timeoutMs?: number;
}) => Promise<string>;
__BUZZ_E2E_MOUNT_AGENT_SESSION_PANEL__?: (input: {
agentPubkey: string;
channelId: string;
canInterruptTurn?: boolean;
}) => Promise<void>;
};
async function waitForActiveTurnSeed(page: Page) {
await page.waitForFunction(
() => typeof window.__BUZZ_E2E_SEED_ACTIVE_TURNS__ === "function",
null,
{ timeout: 10_000 },
);
}
async function seedActiveTurn(page: Page, channelId: string) {
await page.evaluate(
({ agentPubkey, channelId }) => {
window.__BUZZ_E2E_SEED_ACTIVE_TURNS__?.({
agentPubkey,
channelId,
turnId: `e2e-stop-${channelId}`,
});
},
{ agentPubkey: AGENT_PUBKEY, channelId },
);
}
async function openAgentActivity(
page: Page,
activityChannelId: string,
seedChannels: string[] = [activityChannelId],
): Promise<ReturnType<Page["getByTestId"]>> {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("channel-agents").click();
await expect(page.getByTestId("chat-title")).toHaveText("agents");
await waitForActiveTurnSeed(page);
for (const channelId of seedChannels) {
await seedActiveTurn(page, channelId);
}
const messageRow = page
.getByTestId("message-row")
.filter({ hasText: "Indexing the channel catalog now." });
await expect(messageRow.first()).toBeVisible({ timeout: 8_000 });
await messageRow.first().getByRole("button").first().click();
const profile = page.getByTestId("user-profile-panel");
await expect(profile).toBeVisible({ timeout: 10_000 });
if (activityChannelId !== CHANNEL_AGENTS) {
const dot = page.getByRole("tab", {
name: "Show #general activity",
});
await expect(dot).toBeVisible({ timeout: 5_000 });
await dot.click();
}
const activity = page.getByRole("button", {
name: /Open full activity\./,
});
await expect(activity).toBeVisible({ timeout: 5_000 });
await activity.click();
const panel = page.getByTestId("agent-session-thread-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
await expect(
page.getByTestId("agent-session-settings-menu-trigger"),
).toBeVisible();
return panel;
}
async function readControlRequests(page: Page): Promise<ControlRequest[]> {
return page.evaluate(
() => (window.__BUZZ_E2E_OBSERVER_CONTROLS__ ?? []) as ControlRequest[],
);
}
async function clickStop(page: Page) {
await page.getByTestId("agent-session-settings-menu-trigger").click();
const stop = page.getByTestId("agent-session-stop-turn");
await expect(stop).toBeVisible();
await expect(stop).toBeEnabled();
await stop.click();
}
test.describe("agent control browser regressions", () => {
test.use({ viewport: { width: 1280, height: 720 } });
test("Stop uses the channelId-only activity scope and carries a requestId", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
name: "Charlie",
personaId: "control-persona",
pubkey: AGENT_PUBKEY,
status: "running",
channelNames: ["agents"],
},
],
observerControlResults: [{ type: "cancel_turn", status: "sent" }],
});
// The visible route normalizes a requested #general activity scope back
// to the active #agents channel. This covers route normalization and
// effective active-channel targeting, not the channelId-only component
// props contract (covered by the direct harness regression below).
const panel = await openAgentActivity(page, CHANNEL_GENERAL, [
CHANNEL_AGENTS,
CHANNEL_GENERAL,
]);
// ChannelPane intentionally resolves a requested scope that differs from
// the visible route back to the active channel. Stop must target that
// effective channel scope, never the stale profile selection.
await expect(page.getByTestId("agent-session-scope-label")).toHaveText(
"Activity · #agents",
);
await clickStop(page);
await expect
.poll(() => readControlRequests(page))
.toEqual(
expect.arrayContaining([
expect.objectContaining({
agentPubkey: AGENT_PUBKEY,
payload: expect.objectContaining({
type: "cancel_turn",
channelId: CHANNEL_AGENTS,
requestId: expect.any(String),
}),
}),
]),
);
const request = (await readControlRequests(page)).find(
(entry) => entry.payload.type === "cancel_turn",
);
expect(request?.payload.requestId).toBeTruthy();
await expect(page.getByText(/Stop signal sent to Charlie/)).toBeVisible();
await expect(panel).toBeVisible();
});
test("Stop publishes from a channelId-only panel with no Channel object", async ({
page,
}) => {
await installMockBridge(page, {
observerControlResults: [{ type: "cancel_turn", status: "sent" }],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.waitForFunction(
() =>
typeof (window as E2eControlWindow)
.__BUZZ_E2E_MOUNT_AGENT_SESSION_PANEL__ === "function",
);
await waitForActiveTurnSeed(page);
await seedActiveTurn(page, CHANNEL_AGENTS);
await page.evaluate(
({ agentPubkey, channelId }) =>
(window as E2eControlWindow).__BUZZ_E2E_MOUNT_AGENT_SESSION_PANEL__?.({
agentPubkey,
channelId,
}),
{ agentPubkey: AGENT_PUBKEY, channelId: CHANNEL_AGENTS },
);
const panel = page.getByTestId("agent-session-thread-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
await page.getByTestId("agent-session-settings-menu-trigger").click();
const stop = page.getByTestId("agent-session-stop-turn");
await expect(stop).toBeEnabled();
await stop.click();
await expect
.poll(() => readControlRequests(page))
.toEqual(
expect.arrayContaining([
expect.objectContaining({
agentPubkey: AGENT_PUBKEY,
payload: expect.objectContaining({
type: "cancel_turn",
channelId: CHANNEL_AGENTS,
requestId: expect.any(String),
}),
}),
]),
);
await expect(page.getByText(/Stop signal sent to charlie/)).toBeVisible();
});
test("Stop reports ambiguous_target without claiming success", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
name: "Charlie",
personaId: "control-persona",
pubkey: AGENT_PUBKEY,
status: "running",
channelNames: ["agents"],
},
],
observerControlResults: [
{ type: "cancel_turn", status: "ambiguous_target" },
],
});
await openAgentActivity(page, CHANNEL_AGENTS);
await clickStop(page);
await expect(page.getByText(/multiple agent sessions/)).toBeVisible();
await expect(page.getByText(/Stop signal sent to Charlie/)).toHaveCount(0);
});
test("Stop does not accept an unconfirmed or foreign-channel result", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
name: "Charlie",
personaId: "control-persona",
pubkey: AGENT_PUBKEY,
status: "running",
channelNames: ["agents"],
},
],
observerControlResults: [
{
type: "cancel_turn",
status: "sent",
channelId: CHANNEL_FOREIGN,
},
],
});
await openAgentActivity(page, CHANNEL_AGENTS);
// Open the settings menu on real time so the DropdownMenuContent's 150ms
// CSS enter-animation (zoom-in-95) can complete before the fake clock is
// installed. Once the clock is active, every setTimeout — including those
// used by the correlation timeout the test exercises — is fake-controlled.
await page.getByTestId("agent-session-settings-menu-trigger").click();
const stop = page.getByTestId("agent-session-stop-turn");
await expect(stop).toBeVisible();
await expect(stop).toBeEnabled();
// Settle the enter-animation before installing the fake clock. Real
// setTimeout here; waitForAnimations works normally with no fake clock.
await waitForAnimations(page);
// Install the fake clock NOW — after the menu is open and stable. Any
// setTimeout scheduled from this point forward (e.g. the 8-second
// correlation timeout) will be fake-clock-controlled.
await page.clock.install({ time: new Date("2026-08-30T17:00:00.000Z") });
await stop.click();
await expect
.poll(() => readControlRequests(page))
.toEqual(
expect.arrayContaining([
expect.objectContaining({
payload: expect.objectContaining({
channelId: CHANNEL_AGENTS,
requestId: expect.any(String),
}),
}),
]),
);
// The configured result is emitted with CHANNEL_FOREIGN. The correlator
// must ignore it, then report the bounded timeout honestly.
await page.clock.fastForward(8_001);
await expect(page.getByText(/hasn't confirmed it/)).toBeVisible();
await expect(page.getByText(/Stop signal sent to Charlie/)).toHaveCount(0);
});
test("model switch sends requestId and reports ambiguous_target", async ({
page,
}) => {
const requestId = "e2e-model-request";
await installMockBridge(page, {
managedAgents: [
{
name: "Charlie",
pubkey: AGENT_PUBKEY,
status: "running",
channelNames: ["agents"],
},
],
observerControlResults: [
{
type: "switch_model",
status: "ambiguous_target",
},
],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.waitForFunction(
() => typeof window.__BUZZ_E2E_RUN_MODEL_SWITCH__ === "function",
);
const result = await page.evaluate(
({ agentPubkey, channelId, modelId, requestId }) =>
(window as E2eControlWindow).__BUZZ_E2E_RUN_MODEL_SWITCH__?.({
agentPubkey,
channelIds: [channelId],
modelId,
requestId,
}),
{
agentPubkey: AGENT_PUBKEY,
channelId: CHANNEL_AGENTS,
modelId: "test-model",
requestId,
},
);
expect(result).toBe("ambiguous");
await expect
.poll(() => readControlRequests(page))
.toEqual(
expect.arrayContaining([
expect.objectContaining({
agentPubkey: AGENT_PUBKEY,
payload: expect.objectContaining({
type: "switch_model",
channelId: CHANNEL_AGENTS,
modelId: "test-model",
requestId,
}),
}),
]),
);
});
test("model switch reports pending when no correlated terminal arrives", async ({
page,
}) => {
const requestId = "e2e-pending-request";
await installMockBridge(page, {
observerControlResults: [
{
type: "switch_model",
status: "sent",
channelId: CHANNEL_FOREIGN,
},
],
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.waitForFunction(
() => typeof window.__BUZZ_E2E_RUN_MODEL_SWITCH__ === "function",
);
const resultPromise = page.evaluate(
({ agentPubkey, channelId, modelId, requestId }) =>
(window as E2eControlWindow).__BUZZ_E2E_RUN_MODEL_SWITCH__?.({
agentPubkey,
channelIds: [channelId],
modelId,
requestId,
timeoutMs: 50,
}),
{
agentPubkey: AGENT_PUBKEY,
channelId: CHANNEL_AGENTS,
modelId: "test-model",
requestId,
},
);
expect(await resultPromise).toBe("pending");
});
test("Stop is disabled for an unscoped restored activity URL", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
name: "Outsider",
pubkey: RESTORED_UNSCOPED_AGENT_PUBKEY,
status: "running",
channelNames: ["random"],
},
],
});
await page.goto(
`/#/channels/${CHANNEL_AGENTS}?agentSession=${RESTORED_UNSCOPED_AGENT_PUBKEY}`,
{ waitUntil: "domcontentloaded" },
);
await waitForActiveTurnSeed(page);
// Seed real work for an agent that is not in the visible channel activity
// list. The restored URL therefore mounts an unscoped panel (no
// agentSessionChannel and no matching activity-list channel), so the
// disabled state proves the missing scope guard rather than inactivity.
await seedActiveTurn(page, CHANNEL_AGENTS);
const panel = page.getByTestId("agent-session-thread-panel");
await expect(panel).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("agent-session-scope-label")).toHaveText(
"Activity · All channels",
);
await page.getByTestId("agent-session-settings-menu-trigger").click();
await expect(page.getByTestId("agent-session-stop-turn")).toBeDisabled();
await expect(page.getByTestId("agent-session-stop-turn")).toHaveAttribute(
"aria-disabled",
"true",
);
});
});