mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
## Summary Test-side fixes for ten desktop E2E flake clusters and six long-tail rows from the 21–28 Oct 2026 CI audit. Each spec now waits for the state it assumes (a rendered row, a mounted listener, a finished animation, a held boot read), not a fixed delay or a one-shot probe. Two mock-bridge seams support this. The PR changes test code and mock-bridge seams only, apart from one product constant extracted with its value unchanged. It is one of seven PRs split out of the `flakey-tests` branch (siblings below). ### Related issue None found. - #8622, #8244 and #8386 touch some of these spec files, but not the tests fixed here. - §7 mode A (`navigation:472`) is left to open PR #6852, which already sets the width and measures in one polled evaluate. - Draft #8279 queues cold-launch nostr-bind links in the product. Its mock returns null, so the §12 listener seam is still needed. ### Mock-bridge seams - `__BUZZ_E2E_EVENT_LISTENER_COUNT__(event)` counts live **app** listeners per Tauri event. It wraps the mocked `plugin:event|listen`/`unlisten`. The spec-facing `__TAURI_INTERNALS__.listen` passes a marker target that the wrapper skips, so a spec's own subscription can't satisfy the wait. Tauri 2.11.1's mock ignores targets, so delivery is unchanged. - `deferFirstChannelsRead` arms the existing channels-read hold at boot. `__BUZZ_E2E_RELEASE_CHANNELS_READ__` releases it. - `CREATE_PROJECT_REFOCUS_DELAY_MS` moves to `createProjectFormFocus.ts` with its value unchanged (50 ms). Both `CreateProjectFormContent` and `project-commit-detail.spec` import it. ### Flaky tests fixed | Test | Failing runs | Cause | Change | |---|---|---|---| | §2 `overscroll-boundary.spec.ts:34` (and its sibling) | 39601763602, 40367495044, 40583830861, 40597484657, 40373404267 | The `message-timeline` test id first lands on the loading skeleton, which doesn't overflow, so the scroll lock rightly cancels the wheel. | Wait for a rendered row, and content taller than the viewport, before dispatching wheels. | | §4 `project-conversation-load-failure.spec.ts:73` | 40367495044 (all 3 attempts), 40608383847, 40583830861; PRs 39541263507, 40509460168 | The seeded root and reply were dated `now`. The panel's live REQ (`since = floor(now)`) still replayed the reply, so the error card never appeared. | Date both events a minute in the past, and correct the comment. | | §6 `workflows.spec.ts` `createWorkflow` cluster (:222, :365, :297, :525, :471, :406, :614, :997) | 14 of 50 main jobs; :222 ×4, e.g. 40353619362 | `AnimatePresence mode="wait"` keeps the trigger body mounted through its exit, so "Message text" could fill the trigger's field (:222 and the callers). For :297, `isVisible()` read "closed" one frame before the dialog opened its own channel list. | Wait for the step body's "Step details" control before filling. Create dialogs wait for the list; only the duplicate dialog clicks it. | | §12 `nostr-bind.spec.ts` (:156, :198, :228, :247, :260, :335, :417, :447) | 8 main jobs, each test once | The spec emitted `deep-link-nostr-bind` before the app subscribed, and the mock drops events that have no listener. | Wait for `__BUZZ_E2E_EVENT_LISTENER_COUNT__` to report the app listener before emitting. | | §15 `profile.spec.ts:1128` (integration) | 7 of 50 main jobs | The 320 ms motion-sampling window started before the click. | Start the window when `activeTab` reads "runtime". | | §17 `agent-availability.spec.ts:602`, both shutdown-failure variants (integration) | 6 jobs (4 disconnected-online, 2 failed-online) | The "shutdown refused" toast sits over the Delete row and pauses while the pointer is on it, so the retry click is intercepted. | Assert the toast, then retry with focus + Enter. The toaster has no close button. | | §19 `virtualization.spec.ts:247` "08 — cascading older pages…" | 4 of 50 main jobs; PR 40605736093 (all 3 attempts) | The 120 ms and 400 ms windows were timed from before the wheel bursts, and CDP can deliver wheels late. | Count the wheels the page receives. Each window opens at the first wheel and closes only after all have landed, then waits for still frames. **A second, product-side mode remains** (see below). | | §20 `message-copy-link.spec.ts:160` | 4 of 50 main jobs (5 failed attempts), e.g. 40595483966 | The huddle row reused `"d".repeat(64)`, which is a seeded ordinary `#general` message. | Use ids that no seed uses, and emit only after `waitForMockChannelHeadReady`. | | §21 `navigation.spec.ts:687` | 4 of 50 main jobs | The pending chip's `boundingBox()` was read once, and can be null while the row mounts. | Poll until the chip has a width. | | §23 `sidebar-snapshot.spec.ts:358`, `:388`, `:438` | PR 40427822003 (all 3 attempts); never on main | The 500 ms loading-state budget counted app boot, so a slow runner finished the channels read first. | Hold the first channels read with `deferFirstChannelsRead`, assert the loading state, then release it. | | `project-commit-detail.spec.ts:259` | 1–2 CI failures (audit long tail) | A 50 ms refocus timer can land mid-fill and type the description into the name field. | Once the name field is focused, wait out a page timer of `CREATE_PROJECT_REFOCUS_DELAY_MS`; equal-delay timers fire in order. | | `project-pr-review.spec.ts:3520` | 1–2 CI failures | The same timer pattern in `CreateProjectWorkItemDialog`. | Wait for `toBeFocused()` on the title, which only that timer can satisfy. | | `thread-load-failure.spec.ts:79` | 1–2 CI failures | `openWelcomeThread` branched on a one-shot `count()` and fell back to a hover-only Reply click. | Wait for the summary row, then click it. | | `virtualization.spec.ts:201` | 1–2 CI failures | The drop point sat 2 px above the target's bottom edge, after two fixed frames. | Drop at the centre, after dnd-kit's live region names the target. | | `channel-composer-overflow.spec.ts:217` | 1–2 CI failures | Same-second emits sort by id, and the clearance was checked once. | `createdAt: base + i` and `expect.poll`. | | `scroll-history.spec.ts:1289` | 1–2 CI failures | A fixed 250 ms wait before idle sampling. | Poll for coverage first. | Local before/after counts are from the original `flakey-tests` commits (e286a6ae0, 816c7c35e, 67185df6a). Each used a forcing that made `main`'s copy fail with CI's signature: | Test | Before | After | |---|---|---| | §2 | 3/15 | 15/15 | | §4 | 0/10 | 10/10 | | §6 :222 | 0/3 | 3/3 | | §6 :297 | 0/3 | 3/3 | | §12 | 0/3 | 3/3 | | §15 | 0/3 | 3/3 | | §17 | 1/4 | 4/4 | | §19 late-wheel modes | 0/3 | 3/3 and 2/3 | | §23 | 2/6 | 6/6 | | project-commit-detail | 0/3 | 3/3 | | project-pr-review | 0/3 | 3/3 | §20, §21, `thread-load-failure:79`, `virtualization:201`, `channel-composer-overflow:217` and `scroll-history:1289` were not reproduced; they get the fix that matches their CI signature. Not fixed (follow-up): `virtualization:247` also has a product-side mode. In one frame after a prepend, `scrollHeight` can drop 458–639 px while `scrollTop` moves only 50–114 px, so the anchor row jumps 382–457 px. That is lost anchor compensation, and this test can still fail on it. ### Verification This branch was pushed without local re-verification, by the user's choice: local test runs were overloading the machine and failing for that reason. **Hosted CI is the check.** It was pushed with `git push --no-verify`, because the pre-push hook runs typecheck and the unit suites. #8613's flake-check lane will also repeat the changed specs. - Every file except `virtualization.spec.ts` is byte-identical to its final version on `flakey-tests` (67185df6a). That file carries only this PR's hunks; see "Merge order". - Checked for this PR: `git diff --check`, biome on the partial spec (no changes, the same as on `main`), and a conflict-free merge with all six siblings onto `main`. ### Readiness Test and mock-bridge change; the one product edit is a constant extraction with its value unchanged. AGENTS.md's readiness steps (agent review, then human confirmation) are still outstanding, so `buzz-review-completed` is not set. ### Merge order No required order. `desktop/tests/e2e/virtualization.spec.ts` is also edited by **flakey-tests-desktop-races**, which adds one test after line 929; this PR's hunks are all above line 551. They don't touch, so the two merge cleanly in either order, as checked with `git merge-tree`. ### Sibling PRs Split from `flakey-tests` (one PR per area): #8676 (PostgreSQL lease + lane), #8677 (relay E2E helper + Windows shell), #8678 (mobile), #8679 (Tauri apt cache + Windows Rust cap), #8680 (desktop product races), #8682 (desktop E2E composer + row actions). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Jitter <2b2e6415e748c35180847a37aadae06a11e30dddc76b784e7fd9354c4eb42e7a@buzz.block.builderlab.xyz>
756 lines
25 KiB
TypeScript
756 lines
25 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
|
|
const LOCAL = "d".repeat(64);
|
|
|
|
test("saved deployment with offline presence is not shown as online", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: LOCAL,
|
|
name: "Offline deployment",
|
|
status: "deployed",
|
|
backend: { type: "provider", id: "fixture", config: {} },
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/#/agents");
|
|
const dot = page.getByTestId(`agent-runtime-active-${LOCAL}`);
|
|
await expect(dot).toHaveAttribute(
|
|
"aria-label",
|
|
"Offline deployment: Offline",
|
|
);
|
|
await expect(dot.locator("xpath=../..")).not.toHaveClass(/bg-emerald-500/);
|
|
await page
|
|
.getByRole("button", { name: "Offline deployment agent profile" })
|
|
.click();
|
|
await expect(page.getByTestId("user-profile-presence-badge")).toHaveAttribute(
|
|
"aria-label",
|
|
"Offline",
|
|
);
|
|
// Preserve the existing request-only lifecycle control; no inferred redeploy.
|
|
await expect(
|
|
page.getByTestId("user-profile-agent-primary-action"),
|
|
).toHaveAttribute("aria-label", "Shutdown");
|
|
await waitForAnimations(page);
|
|
await page
|
|
.getByTestId("user-profile-panel")
|
|
.screenshot({ path: testInfo.outputPath("offline-deployment.png") });
|
|
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
|
channelName: "agents",
|
|
kind: 20001,
|
|
}),
|
|
),
|
|
)
|
|
.toBe(true);
|
|
for (const status of ["online", "away", "offline"] as const) {
|
|
await page.evaluate(
|
|
({ pubkey, status }) => {
|
|
const emit = window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__;
|
|
if (!emit) throw new Error("Mock presence emitter is unavailable.");
|
|
emit({ pubkey, status });
|
|
},
|
|
{ pubkey: LOCAL, status },
|
|
);
|
|
await expect(
|
|
page.getByTestId("user-profile-presence-badge"),
|
|
).toHaveAttribute("aria-label", status[0].toUpperCase() + status.slice(1));
|
|
await expect(dot).toHaveAttribute(
|
|
"aria-label",
|
|
`Offline deployment: ${status[0].toUpperCase() + status.slice(1)}`,
|
|
);
|
|
await expect(
|
|
page.getByTestId("user-profile-agent-primary-action"),
|
|
).toHaveAttribute("aria-label", "Shutdown");
|
|
if (status === "online") {
|
|
await waitForAnimations(page);
|
|
await page
|
|
.getByTestId("user-profile-panel")
|
|
.screenshot({ path: testInfo.outputPath("online-deployment.png") });
|
|
await page.getByTestId("user-profile-agent-primary-action").click();
|
|
await expect(
|
|
page.locator("[data-sonner-toast]").filter({
|
|
hasText:
|
|
"Shutdown requested. This does not confirm the agent has stopped.",
|
|
}),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByTestId("user-profile-presence-badge"),
|
|
).toHaveAttribute("aria-label", "Online");
|
|
await expect(
|
|
page.getByTestId("user-profile-agent-primary-action"),
|
|
).toHaveAttribute("aria-label", "Shutdown");
|
|
expect(
|
|
await page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMANDS__ ?? []).filter((command) =>
|
|
["start_managed_agent", "stop_managed_agent"].includes(command),
|
|
),
|
|
),
|
|
).toEqual([]);
|
|
}
|
|
}
|
|
await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?.("disconnected"),
|
|
);
|
|
await expect(page.getByTestId("user-profile-presence-badge")).toHaveCount(0);
|
|
await expect(dot).toHaveAttribute(
|
|
"aria-label",
|
|
"Offline deployment: Availability unknown",
|
|
);
|
|
});
|
|
|
|
test("missing snapshot is offline but failed reads cannot reuse cached online", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: LOCAL,
|
|
name: "Snapshot agent",
|
|
status: "running",
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/#/agents");
|
|
const dot = page.getByTestId(`agent-runtime-active-${LOCAL}`);
|
|
await expect(dot).toHaveAttribute("aria-label", "Snapshot agent: Offline");
|
|
await page
|
|
.getByRole("button", { name: "Snapshot agent agent profile" })
|
|
.click();
|
|
const badge = page.getByTestId("user-profile-presence-badge");
|
|
await expect(badge).toHaveAttribute("aria-label", "Offline");
|
|
|
|
// Override the IPC response, not rendered state or query data. Both card and
|
|
// profile must consume the same real query success/error boundary.
|
|
await page.evaluate(() => {
|
|
const w = window as typeof window & {
|
|
__AVAILABILITY_RESPONSE__?: "missing" | "online" | "error";
|
|
__TAURI_INTERNALS__: {
|
|
invoke: (
|
|
command: string,
|
|
payload: unknown,
|
|
options: unknown,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
const original = w.__TAURI_INTERNALS__.invoke.bind(w.__TAURI_INTERNALS__);
|
|
w.__TAURI_INTERNALS__.invoke = async (command, payload, options) => {
|
|
if (command === "get_presence") {
|
|
if (w.__AVAILABILITY_RESPONSE__ === "error")
|
|
throw "relay unreachable: request timed out"; // Native Result::Err IPC shape
|
|
if (w.__AVAILABILITY_RESPONSE__ === "missing") return {};
|
|
if (w.__AVAILABILITY_RESPONSE__ === "online") {
|
|
return Object.fromEntries(
|
|
(payload as { pubkeys: string[] }).pubkeys.map((key) => [
|
|
key,
|
|
"online",
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
return original(command, payload, options);
|
|
};
|
|
});
|
|
for (const response of ["online", "missing", "online", "error"] as const) {
|
|
await page.evaluate(async (response) => {
|
|
const w = window as typeof window & {
|
|
__AVAILABILITY_RESPONSE__?: typeof response;
|
|
__BUZZ_E2E_QUERY_CLIENT__?: {
|
|
invalidateQueries: (filter: { queryKey: string[] }) => Promise<void>;
|
|
};
|
|
};
|
|
w.__AVAILABILITY_RESPONSE__ = response;
|
|
if (!w.__BUZZ_E2E_QUERY_CLIENT__)
|
|
throw new Error("Query client unavailable");
|
|
await w.__BUZZ_E2E_QUERY_CLIENT__.invalidateQueries({
|
|
queryKey: ["presence"],
|
|
});
|
|
}, response);
|
|
const label =
|
|
response === "error"
|
|
? "Availability unknown"
|
|
: response === "missing"
|
|
? "Offline"
|
|
: "Online";
|
|
await expect(dot).toHaveAttribute("aria-label", `Snapshot agent: ${label}`);
|
|
if (response === "error") await expect(badge).toHaveCount(0);
|
|
else await expect(badge).toHaveAttribute("aria-label", label);
|
|
await expect(
|
|
page.getByTestId("user-profile-agent-primary-action"),
|
|
).toHaveAttribute("aria-label", "Stop");
|
|
}
|
|
});
|
|
|
|
test("stopped local with authored presence has a dot, not an invokable Start", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: LOCAL,
|
|
name: "Present local",
|
|
status: "stopped",
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/#/agents");
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
|
channelName: "agents",
|
|
kind: 20001,
|
|
}),
|
|
),
|
|
)
|
|
.toBe(true);
|
|
await page.evaluate(
|
|
(pubkey) =>
|
|
window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__?.({ pubkey, status: "online" }),
|
|
LOCAL,
|
|
);
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-active-${LOCAL}`),
|
|
).toHaveAttribute("aria-label", "Present local: Online");
|
|
await expect(page.getByTestId(`agent-runtime-start-${LOCAL}`)).toHaveCount(0);
|
|
await page
|
|
.getByRole("button", { name: "Present local agent profile" })
|
|
.press("Enter");
|
|
const action = page.getByTestId("user-profile-agent-primary-action");
|
|
await expect(action).toHaveAttribute("aria-label", "Start agent");
|
|
await expect(action).toBeDisabled();
|
|
await action.evaluate((button: HTMLButtonElement) => button.click());
|
|
await action.press("Enter");
|
|
await action.press("Space");
|
|
expect(
|
|
await page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMANDS__ ?? []).filter((command) =>
|
|
["start_managed_agent", "stop_managed_agent"].includes(command),
|
|
),
|
|
),
|
|
).toEqual([]);
|
|
await expect(page.getByTestId("user-profile-agent-restart")).toHaveCount(0);
|
|
const badge = page.getByTestId("user-profile-presence-badge");
|
|
await expect(badge).toHaveAttribute("aria-label", "Online");
|
|
await waitForAnimations(page);
|
|
await page
|
|
.getByTestId(`managed-agent-${LOCAL}`)
|
|
.screenshot({ path: testInfo.outputPath("stopped-online-card.png") });
|
|
await page
|
|
.getByTestId("user-profile-panel")
|
|
.screenshot({ path: testInfo.outputPath("stopped-online-profile.png") });
|
|
for (const status of ["away", "offline"] as const) {
|
|
await page.evaluate(
|
|
({ pubkey, status }) =>
|
|
window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__?.({ pubkey, status }),
|
|
{ pubkey: LOCAL, status },
|
|
);
|
|
await expect(badge).toHaveAttribute(
|
|
"aria-label",
|
|
status === "away" ? "Away" : "Offline",
|
|
);
|
|
if (status === "away") {
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-active-${LOCAL}`),
|
|
).toHaveAttribute("aria-label", "Present local: Away");
|
|
await expect(action).toBeDisabled();
|
|
} else {
|
|
await expect(action).toBeEnabled();
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-start-${LOCAL}`),
|
|
).toBeVisible();
|
|
}
|
|
}
|
|
// Keyboard activation is restored for the ordinary stopped/offline control.
|
|
// Runtime startup alone must still not manufacture authored availability.
|
|
await action.press("Enter");
|
|
await expect(action).toHaveAttribute("aria-label", "Stop");
|
|
await expect(badge).toHaveAttribute("aria-label", "Offline");
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-active-${LOCAL}`),
|
|
).toHaveAttribute("aria-label", "Present local: Offline");
|
|
});
|
|
|
|
test("member menu cannot start a present stopped local runtime", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: LOCAL,
|
|
name: "Present member",
|
|
status: "stopped",
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-agents").click();
|
|
await page.getByTestId("channel-members-trigger").click();
|
|
const row = page.getByTestId(`sidebar-member-${LOCAL}`);
|
|
await expect(row).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
|
channelName: "agents",
|
|
kind: 20001,
|
|
}),
|
|
),
|
|
)
|
|
.toBe(true);
|
|
await page.evaluate(
|
|
(pubkey) =>
|
|
window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__?.({ pubkey, status: "away" }),
|
|
LOCAL,
|
|
);
|
|
await row.hover();
|
|
const menu = page.getByTestId(`sidebar-member-menu-${LOCAL}`);
|
|
await menu.focus();
|
|
await menu.press("Enter");
|
|
const action = page.getByTestId(`sidebar-agent-action-${LOCAL}`);
|
|
await expect(action).toContainText("Start");
|
|
await expect(action).toHaveAttribute("aria-disabled", "true");
|
|
await action.press("Enter");
|
|
await action.evaluate((node: HTMLElement) => node.click());
|
|
expect(
|
|
await page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMANDS__ ?? []).filter((command) =>
|
|
/^(start|stop)_managed_agent/.test(command),
|
|
),
|
|
),
|
|
).toEqual([]);
|
|
await page.evaluate(
|
|
(pubkey) =>
|
|
window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__?.({ pubkey, status: "offline" }),
|
|
LOCAL,
|
|
);
|
|
await expect(action).not.toHaveAttribute("aria-disabled", "true");
|
|
await action.click();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
(window.__BUZZ_E2E_COMMANDS__ ?? []).filter(
|
|
(command) => command === "start_managed_agent_runtime",
|
|
).length,
|
|
),
|
|
)
|
|
.toBe(1);
|
|
});
|
|
|
|
for (const surface of ["agents", "members"] as const) {
|
|
test(`${surface}: three rows and their actions share one presence request`, async ({
|
|
page,
|
|
}) => {
|
|
const keys = [LOCAL, "e".repeat(64), "f".repeat(64)];
|
|
await installMockBridge(page, {
|
|
managedAgents: keys.map((pubkey, index) => ({
|
|
pubkey,
|
|
name: `Shared snapshot ${index}`,
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
})),
|
|
});
|
|
await page.goto(surface === "agents" ? "/#/agents" : "/");
|
|
if (surface === "members") {
|
|
await page.getByTestId("channel-agents").click();
|
|
await page.getByTestId("channel-members-trigger").click();
|
|
}
|
|
for (const pubkey of keys) {
|
|
await expect(
|
|
page.getByTestId(
|
|
surface === "agents"
|
|
? `managed-agent-${pubkey}`
|
|
: `sidebar-member-${pubkey}`,
|
|
),
|
|
).toBeVisible();
|
|
}
|
|
// Count requests at the actual IPC boundary. Refresh every active presence
|
|
// observer so a hidden per-row/action query cannot escape the assertion.
|
|
const calls = await page.evaluate(async (keys) => {
|
|
const w = window as typeof window & {
|
|
__TAURI_INTERNALS__: {
|
|
invoke: (
|
|
command: string,
|
|
payload: unknown,
|
|
options: unknown,
|
|
) => Promise<unknown>;
|
|
};
|
|
__BUZZ_E2E_QUERY_CLIENT__: {
|
|
invalidateQueries: (filter: { queryKey: string[] }) => Promise<void>;
|
|
};
|
|
};
|
|
const calls: string[][] = [];
|
|
const original = w.__TAURI_INTERNALS__.invoke;
|
|
w.__TAURI_INTERNALS__.invoke = (command, payload, options) => {
|
|
if (command === "get_presence") {
|
|
const pubkeys = (payload as { pubkeys: string[] }).pubkeys;
|
|
if (pubkeys.some((key) => keys.includes(key))) calls.push(pubkeys);
|
|
}
|
|
return original(command, payload, options);
|
|
};
|
|
try {
|
|
await w.__BUZZ_E2E_QUERY_CLIENT__.invalidateQueries({
|
|
queryKey: ["presence"],
|
|
});
|
|
} finally {
|
|
w.__TAURI_INTERNALS__.invoke = original;
|
|
}
|
|
return calls;
|
|
}, keys);
|
|
expect(calls).toHaveLength(1);
|
|
expect(calls[0]).toEqual(expect.arrayContaining(keys));
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
|
channelName: "agents",
|
|
kind: 20001,
|
|
}),
|
|
),
|
|
)
|
|
.toBe(true);
|
|
await page.evaluate(
|
|
(pubkey) =>
|
|
window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__?.({ pubkey, status: "away" }),
|
|
LOCAL,
|
|
);
|
|
if (surface === "agents") {
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-active-${LOCAL}`),
|
|
).toHaveAttribute("aria-label", "Shared snapshot 0: Away");
|
|
await expect(
|
|
page.getByTestId(`agent-runtime-active-${keys[1]}`),
|
|
).toHaveAttribute("aria-label", "Shared snapshot 1: Offline");
|
|
} else {
|
|
await expect(
|
|
page.getByTestId(`sidebar-member-presence-${LOCAL}`).locator("span"),
|
|
).toHaveClass(/bg-amber-500/);
|
|
await expect(
|
|
page.getByTestId(`sidebar-member-presence-${keys[1]}`).locator("span"),
|
|
).toHaveClass(/bg-muted-foreground/);
|
|
}
|
|
});
|
|
}
|
|
|
|
test("hover profile preserves unknown availability and announces only established status", async ({
|
|
page,
|
|
}) => {
|
|
const pubkey =
|
|
"554cef57437abac34522ac2c9f0490d685b72c80478cf9f7ed6f9570ee8624ea";
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey,
|
|
name: "Charlie",
|
|
status: "running",
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-agents").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("agents");
|
|
|
|
// Hold the actual single-key IPC read pending before opening the lazy hover
|
|
// body. Do not mock the availability hook, avatar or rendered query state.
|
|
await page.evaluate((pubkey) => {
|
|
const w = window as typeof window & {
|
|
__POPOVER_RESPONSE__?:
|
|
| "pending"
|
|
| "missing"
|
|
| "offline"
|
|
| "online"
|
|
| "error";
|
|
__POPOVER_RELEASE__?: () => void;
|
|
__TAURI_INTERNALS__: {
|
|
invoke: (
|
|
command: string,
|
|
payload: unknown,
|
|
options: unknown,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
w.__POPOVER_RESPONSE__ = "pending";
|
|
const original = w.__TAURI_INTERNALS__.invoke.bind(w.__TAURI_INTERNALS__);
|
|
w.__TAURI_INTERNALS__.invoke = async (command, payload, options) => {
|
|
const keys = (payload as { pubkeys?: string[] } | undefined)?.pubkeys;
|
|
if (
|
|
command === "get_presence" &&
|
|
keys?.length === 1 &&
|
|
keys[0] === pubkey
|
|
) {
|
|
if (w.__POPOVER_RESPONSE__ === "pending") {
|
|
await new Promise<void>((resolve) => {
|
|
w.__POPOVER_RELEASE__ = resolve;
|
|
});
|
|
}
|
|
if (w.__POPOVER_RESPONSE__ === "error") {
|
|
throw "relay unreachable: request timed out"; // Native Result::Err IPC shape
|
|
}
|
|
if (w.__POPOVER_RESPONSE__ === "missing") return {};
|
|
return { [pubkey]: w.__POPOVER_RESPONSE__ };
|
|
}
|
|
return original(command, payload, options);
|
|
};
|
|
}, pubkey);
|
|
|
|
await page
|
|
.getByTestId("message-row")
|
|
.filter({ hasText: "Indexing the channel catalog now." })
|
|
.getByRole("button")
|
|
.first()
|
|
.hover();
|
|
const popover = page.getByTestId("user-profile-popover");
|
|
const badge = popover.getByTestId("user-profile-popover-presence-badge");
|
|
await expect(popover).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
typeof (window as typeof window & { __POPOVER_RELEASE__?: unknown })
|
|
.__POPOVER_RELEASE__,
|
|
),
|
|
)
|
|
.toBe("function");
|
|
|
|
async function expectUnknown() {
|
|
await expect(badge).toHaveCount(0);
|
|
await expect(
|
|
popover.getByRole("img", { name: /^(Offline|Online|Away)$/ }),
|
|
).toHaveCount(0);
|
|
await expect(
|
|
popover
|
|
.locator(".sr-only")
|
|
.filter({ hasText: /^(Offline|Online|Away)$/ }),
|
|
).toHaveCount(0);
|
|
}
|
|
async function expectStatus(label: string) {
|
|
await expect(badge).toBeVisible();
|
|
await expect(badge).toHaveAttribute("role", "img");
|
|
await expect(badge).toHaveAttribute("aria-label", label);
|
|
await expect(badge.locator(".sr-only")).toHaveText(label);
|
|
}
|
|
await expectUnknown();
|
|
await page.evaluate(() => {
|
|
const w = window as typeof window & {
|
|
__POPOVER_RESPONSE__?: string;
|
|
__POPOVER_RELEASE__?: () => void;
|
|
};
|
|
w.__POPOVER_RESPONSE__ = "missing";
|
|
w.__POPOVER_RELEASE__?.();
|
|
});
|
|
await expectStatus("Offline");
|
|
|
|
// Successful missing is distinct from explicit Offline, failure (including
|
|
// stale Online), and a disconnect after recovery. Keep the same popover open.
|
|
for (const response of [
|
|
"offline",
|
|
"online",
|
|
"error",
|
|
"missing",
|
|
"online",
|
|
] as const) {
|
|
await page.evaluate(
|
|
async ({ response, pubkey }) => {
|
|
const w = window as typeof window & {
|
|
__POPOVER_RESPONSE__?: typeof response;
|
|
__BUZZ_E2E_QUERY_CLIENT__?: {
|
|
invalidateQueries: (filter: {
|
|
queryKey: string[];
|
|
exact: boolean;
|
|
}) => Promise<void>;
|
|
};
|
|
};
|
|
w.__POPOVER_RESPONSE__ = response;
|
|
if (!w.__BUZZ_E2E_QUERY_CLIENT__)
|
|
throw new Error("Query client unavailable");
|
|
await w.__BUZZ_E2E_QUERY_CLIENT__.invalidateQueries({
|
|
queryKey: ["presence", pubkey],
|
|
exact: true,
|
|
});
|
|
},
|
|
{ response, pubkey },
|
|
);
|
|
if (response === "error") await expectUnknown();
|
|
else await expectStatus(response === "online" ? "Online" : "Offline");
|
|
}
|
|
await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?.("disconnected"),
|
|
);
|
|
await expectUnknown();
|
|
await expect(popover).toBeVisible();
|
|
});
|
|
|
|
for (const scenario of [
|
|
"failed-online",
|
|
"disconnected-online",
|
|
"failed-offline",
|
|
"offline",
|
|
] as const) {
|
|
test(`profile deletion respects ${scenario} and preserves the record on shutdown failure`, async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: LOCAL,
|
|
name: "Deletion fixture",
|
|
status: "deployed",
|
|
backend: { type: "provider", id: "fixture", config: {} },
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/#/agents");
|
|
await expect(
|
|
page.getByRole("button", { name: "Deletion fixture agent profile" }),
|
|
).toBeVisible();
|
|
await page.evaluate(
|
|
async ({ pubkey, warm }) => {
|
|
const w = window as typeof window & {
|
|
__DELETE_FAIL_PRESENCE__?: boolean;
|
|
__DELETE_FAIL_SHUTDOWN__?: boolean;
|
|
__DELETE_EFFECTS__?: string[];
|
|
__TAURI_INTERNALS__: {
|
|
invoke: (
|
|
command: string,
|
|
payload: unknown,
|
|
options: unknown,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
const original = w.__TAURI_INTERNALS__.invoke.bind(
|
|
w.__TAURI_INTERNALS__,
|
|
);
|
|
w.__DELETE_EFFECTS__ = [];
|
|
w.__DELETE_FAIL_SHUTDOWN__ = true;
|
|
w.__TAURI_INTERNALS__.invoke = async (command, payload, options) => {
|
|
if (command === "list_managed_agents") {
|
|
const rows = (await original(command, payload, options)) as Array<{
|
|
pubkey: string;
|
|
backend_agent_id: string | null;
|
|
}>;
|
|
return rows.map((row) =>
|
|
row.pubkey === pubkey
|
|
? { ...row, backend_agent_id: "fixture-receipt" }
|
|
: row,
|
|
);
|
|
}
|
|
if (command === "get_presence") {
|
|
if (w.__DELETE_FAIL_PRESENCE__)
|
|
throw "relay unreachable: request timed out";
|
|
return { [pubkey]: warm };
|
|
}
|
|
if (
|
|
command === "send_channel_message" &&
|
|
(payload as { content?: string })?.content === "!shutdown"
|
|
) {
|
|
w.__DELETE_EFFECTS__?.push("shutdown");
|
|
if (w.__DELETE_FAIL_SHUTDOWN__) throw "shutdown refused";
|
|
}
|
|
if (command === "delete_managed_agent")
|
|
w.__DELETE_EFFECTS__?.push("delete");
|
|
if (command === "remove_channel_member")
|
|
w.__DELETE_EFFECTS__?.push("remove-member");
|
|
return original(command, payload, options);
|
|
};
|
|
await window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
|
|
queryKey: ["managed-agents"],
|
|
});
|
|
await window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
|
|
queryKey: ["presence", pubkey],
|
|
exact: true,
|
|
});
|
|
},
|
|
{
|
|
pubkey: LOCAL,
|
|
warm: scenario.endsWith("offline") ? "offline" : "online",
|
|
},
|
|
);
|
|
await page
|
|
.getByRole("button", { name: "Deletion fixture agent profile" })
|
|
.click();
|
|
await expect(
|
|
page.getByTestId("user-profile-presence-badge"),
|
|
).toHaveAttribute(
|
|
"aria-label",
|
|
scenario.endsWith("offline") ? "Offline" : "Online",
|
|
);
|
|
if (scenario.startsWith("failed")) {
|
|
await page.evaluate(async (pubkey) => {
|
|
(
|
|
window as typeof window & { __DELETE_FAIL_PRESENCE__?: boolean }
|
|
).__DELETE_FAIL_PRESENCE__ = true;
|
|
await window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
|
|
queryKey: ["presence", pubkey],
|
|
exact: true,
|
|
});
|
|
}, LOCAL);
|
|
} else if (scenario.startsWith("disconnected")) {
|
|
await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?.("disconnected"),
|
|
);
|
|
}
|
|
if (scenario !== "offline")
|
|
await expect(page.getByTestId("user-profile-presence-badge")).toHaveCount(
|
|
0,
|
|
);
|
|
await page.getByTestId("user-profile-delete-agent-row").click();
|
|
const dialog = page.getByTestId("agent-delete-confirm-dialog");
|
|
await expect(dialog).toContainText("not its remote deployment");
|
|
await expect(dialog).toContainText("A failed request cancels deletion");
|
|
await expect(dialog).not.toContainText("This agent is offline");
|
|
await page.getByTestId("agent-delete-confirm-action").click();
|
|
const effects = () =>
|
|
page.evaluate(
|
|
() =>
|
|
(window as typeof window & { __DELETE_EFFECTS__?: string[] })
|
|
.__DELETE_EFFECTS__ ?? [],
|
|
);
|
|
if (scenario === "offline") {
|
|
await expect.poll(effects).toEqual(["delete", "remove-member"]);
|
|
await expect(page.getByTestId("user-profile-panel")).toHaveCount(0);
|
|
} else {
|
|
await expect.poll(effects).toEqual(["shutdown"]);
|
|
await expect(page.getByTestId("user-profile-panel")).toBeVisible();
|
|
await expect(page.locator("[data-sonner-toast]")).toContainText(
|
|
"shutdown refused",
|
|
);
|
|
// Retry the same user action without healing presence. A successful
|
|
// request permits the explicitly confirmed local deletion, not a claim
|
|
// of remote termination. No membership write precedes it.
|
|
await page.evaluate(() => {
|
|
(
|
|
window as typeof window & { __DELETE_FAIL_SHUTDOWN__?: boolean }
|
|
).__DELETE_FAIL_SHUTDOWN__ = false;
|
|
});
|
|
// The failure toast can cover the action rows, and a pointer over it
|
|
// pauses its dismissal. The toaster has no close button, so retry with
|
|
// the keyboard, which the toast cannot intercept.
|
|
const deleteRow = page.getByTestId("user-profile-delete-agent-row");
|
|
await expect(deleteRow).toBeEnabled();
|
|
await deleteRow.focus();
|
|
await deleteRow.press("Enter");
|
|
const confirmDelete = page.getByTestId("agent-delete-confirm-action");
|
|
await confirmDelete.focus();
|
|
await confirmDelete.press("Enter");
|
|
await expect
|
|
.poll(effects)
|
|
.toEqual(["shutdown", "shutdown", "delete", "remove-member"]);
|
|
await expect(page.getByTestId("user-profile-panel")).toHaveCount(0);
|
|
}
|
|
});
|
|
}
|