mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
test(desktop): add leadership E2E seed hook + screenshot spec
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
co-authored by
Will Pfleger
parent
e8dc2ebf0f
commit
b76b1813ef
@@ -34,6 +34,7 @@ export default defineConfig({
|
||||
"**/team-management-screenshots.spec.ts",
|
||||
"**/active-turn-screenshots.spec.ts",
|
||||
"**/active-turn-resilience-screenshots.spec.ts",
|
||||
"**/leadership-screenshots.spec.ts",
|
||||
"**/profile-active-turn-screenshots.spec.ts",
|
||||
"**/file-attachment.spec.ts",
|
||||
"**/video-attachment.spec.ts",
|
||||
|
||||
@@ -328,6 +328,31 @@ export function useManagedAgentObserverBridge(
|
||||
}, [hasActiveAgent]);
|
||||
}
|
||||
|
||||
// Test-only: inject synthetic `leadership_status` frames through the real
|
||||
// ingest path (`appendAgentEvent`), so the cached-map rebuild the consumer
|
||||
// reads is exercised — not a fake. Registers the agent as known so the row
|
||||
// renders. Production ingest (`handleRelayObserverEvent`) is untouched.
|
||||
export function seedLeadershipForTest(
|
||||
agentPubkey: string,
|
||||
instances: readonly { instanceId: string; isLeader: boolean }[],
|
||||
) {
|
||||
knownAgentPubkeys.add(normalizePubkey(agentPubkey));
|
||||
let seq = Date.now();
|
||||
for (const { instanceId, isLeader } of instances) {
|
||||
seq += 1;
|
||||
appendAgentEvent(agentPubkey, {
|
||||
seq,
|
||||
timestamp: new Date().toISOString(),
|
||||
kind: LEADERSHIP_EVENT_KIND,
|
||||
agentIndex: null,
|
||||
channelId: null,
|
||||
sessionId: null,
|
||||
turnId: null,
|
||||
payload: { instanceId, isLeader },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function resetAgentObserverStore() {
|
||||
generation += 1;
|
||||
const unsubscribe = unsubscribeRelay;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { relayClient } from "@/shared/api/relayClient";
|
||||
import type { ConnectionState } from "@/shared/api/relayClientShared";
|
||||
import type { RelayEvent } from "@/shared/api/types";
|
||||
import { syncAgentTurnsFromEvents } from "@/features/agents/activeAgentTurnsStore";
|
||||
import { seedLeadershipForTest } from "@/features/agents/observerRelayStore";
|
||||
import {
|
||||
CUSTOM_EMOJI_SET_D_TAG,
|
||||
KIND_EMOJI_SET,
|
||||
@@ -653,6 +654,10 @@ declare global {
|
||||
channelId: string;
|
||||
turnId: string;
|
||||
}) => void;
|
||||
__BUZZ_E2E_SEED_LEADERSHIP__?: (input: {
|
||||
agentPubkey: string;
|
||||
instances: { instanceId: string; isLeader: boolean }[];
|
||||
}) => void;
|
||||
__BUZZ_E2E_EMIT_MOCK_READ_STATE__?: (input: {
|
||||
clientId: string;
|
||||
contexts: Record<string, number>;
|
||||
@@ -6339,6 +6344,9 @@ export function maybeInstallE2eTauriMocks() {
|
||||
},
|
||||
]);
|
||||
};
|
||||
window.__BUZZ_E2E_SEED_LEADERSHIP__ = ({ agentPubkey, instances }) => {
|
||||
seedLeadershipForTest(agentPubkey, instances);
|
||||
};
|
||||
const meshNodeStatus = (
|
||||
state: "off" | "running",
|
||||
mode: "serve" | "client" | null,
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
const SHOTS = "test-results/leadership";
|
||||
|
||||
// Mock agent pubkeys (distinct from the relay agents seeded by default).
|
||||
const AGENT_PAUL = "aa".repeat(32);
|
||||
const AGENT_DUNCAN = "bb".repeat(32);
|
||||
|
||||
type LeadershipInstance = { instanceId: string; isLeader: boolean };
|
||||
|
||||
async function waitForBridge(page: import("@playwright/test").Page) {
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
typeof (window as Window & { __BUZZ_E2E_SEED_LEADERSHIP__?: unknown })
|
||||
.__BUZZ_E2E_SEED_LEADERSHIP__ === "function",
|
||||
null,
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
}
|
||||
|
||||
async function openAgentsView(page: import("@playwright/test").Page) {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await waitForBridge(page);
|
||||
await page.getByTestId("open-agents-view").click();
|
||||
await expect(page.getByTestId("unified-agents-groups")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
}
|
||||
|
||||
// The freshest-leader rule selects max(lastSeen), tie-broken by seq. The seed
|
||||
// hook seeds in array order with a monotonic seq, so list the intended leader
|
||||
// LAST to make selection deterministic even when timestamps collide at ms.
|
||||
async function seedLeadership(
|
||||
page: import("@playwright/test").Page,
|
||||
agentPubkey: string,
|
||||
instances: LeadershipInstance[],
|
||||
) {
|
||||
await page.evaluate(
|
||||
({ pubkey, frames }) => {
|
||||
const win = window as Window & {
|
||||
__BUZZ_E2E_SEED_LEADERSHIP__?: (input: {
|
||||
agentPubkey: string;
|
||||
instances: { instanceId: string; isLeader: boolean }[];
|
||||
}) => void;
|
||||
};
|
||||
win.__BUZZ_E2E_SEED_LEADERSHIP__?.({
|
||||
agentPubkey: pubkey,
|
||||
instances: frames,
|
||||
});
|
||||
},
|
||||
{ pubkey: agentPubkey, frames: instances },
|
||||
);
|
||||
}
|
||||
|
||||
const MANAGED_AGENTS = [
|
||||
{
|
||||
pubkey: AGENT_PAUL,
|
||||
name: "Paul",
|
||||
status: "running" as const,
|
||||
channelNames: ["general", "engineering"],
|
||||
},
|
||||
{
|
||||
pubkey: AGENT_DUNCAN,
|
||||
name: "Duncan",
|
||||
status: "running" as const,
|
||||
channelNames: ["general", "design"],
|
||||
},
|
||||
];
|
||||
|
||||
async function openLeadershipSubmenu(
|
||||
page: import("@playwright/test").Page,
|
||||
agentPubkey: string,
|
||||
) {
|
||||
await page.getByTestId(`managed-agent-actions-${agentPubkey}`).click();
|
||||
const submenuTrigger = page.getByRole("menuitem", { name: "Leadership" });
|
||||
await expect(submenuTrigger).toBeVisible();
|
||||
await submenuTrigger.hover();
|
||||
// Settle the submenu open animation before capture.
|
||||
await submenuTrigger.evaluate((el) =>
|
||||
Promise.all(
|
||||
el
|
||||
.closest("[data-state]")
|
||||
?.getAnimations()
|
||||
.map((a) => a.finished) ?? [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test.describe("leadership UI screenshots", () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
|
||||
test("01 — single instance shows Leader badge, no submenu", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
||||
await openAgentsView(page);
|
||||
|
||||
await seedLeadership(page, AGENT_PAUL, [
|
||||
{ instanceId: "4821-1718600000000000", isLeader: true },
|
||||
]);
|
||||
|
||||
const row = page.getByTestId(`managed-agent-${AGENT_PAUL}`);
|
||||
await expect(row).toContainText("Leader", { timeout: 5_000 });
|
||||
|
||||
await page.getByTestId(`managed-agent-actions-${AGENT_PAUL}`).click();
|
||||
await expect(
|
||||
page.getByRole("menuitem", { name: "Leadership" }),
|
||||
).toHaveCount(0);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByTestId("unified-agents-groups").screenshot({
|
||||
path: `${SHOTS}/01-single-instance-leader.png`,
|
||||
});
|
||||
});
|
||||
|
||||
test("02 — multi-instance badge reflects the freshest leader", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
||||
await openAgentsView(page);
|
||||
|
||||
// Leader seeded last → highest seq → wins the freshest-leader tie-break.
|
||||
await seedLeadership(page, AGENT_PAUL, [
|
||||
{ instanceId: "4821-1718600000000000", isLeader: false },
|
||||
{ instanceId: "5190-1718600100000000", isLeader: false },
|
||||
{ instanceId: "6033-1718600200000000", isLeader: true },
|
||||
]);
|
||||
|
||||
const row = page.getByTestId(`managed-agent-${AGENT_PAUL}`);
|
||||
await expect(row).toContainText("Leader", { timeout: 5_000 });
|
||||
|
||||
await page.getByTestId("unified-agents-groups").screenshot({
|
||||
path: `${SHOTS}/02-multi-instance-badge.png`,
|
||||
});
|
||||
});
|
||||
|
||||
test("03 — leadership submenu lists each instance", async ({ page }) => {
|
||||
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
||||
await openAgentsView(page);
|
||||
|
||||
await seedLeadership(page, AGENT_PAUL, [
|
||||
{ instanceId: "4821-1718600000000000", isLeader: false },
|
||||
{ instanceId: "5190-1718600100000000", isLeader: false },
|
||||
{ instanceId: "6033-1718600200000000", isLeader: true },
|
||||
]);
|
||||
|
||||
const row = page.getByTestId(`managed-agent-${AGENT_PAUL}`);
|
||||
await expect(row).toContainText("Leader", { timeout: 5_000 });
|
||||
|
||||
await openLeadershipSubmenu(page, AGENT_PAUL);
|
||||
await expect(page.getByRole("menuitem", { name: /Leader/ })).toBeVisible();
|
||||
|
||||
await page.screenshot({
|
||||
path: `${SHOTS}/03-leadership-submenu.png`,
|
||||
clip: { x: 0, y: 0, width: 1280, height: 720 },
|
||||
});
|
||||
});
|
||||
|
||||
test("04 — Make leader action on a non-leader instance", async ({ page }) => {
|
||||
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
||||
await openAgentsView(page);
|
||||
|
||||
await seedLeadership(page, AGENT_PAUL, [
|
||||
{ instanceId: "4821-1718600000000000", isLeader: false },
|
||||
{ instanceId: "5190-1718600100000000", isLeader: false },
|
||||
{ instanceId: "6033-1718600200000000", isLeader: true },
|
||||
]);
|
||||
|
||||
const row = page.getByTestId(`managed-agent-${AGENT_PAUL}`);
|
||||
await expect(row).toContainText("Leader", { timeout: 5_000 });
|
||||
|
||||
await openLeadershipSubmenu(page, AGENT_PAUL);
|
||||
|
||||
// A non-leader instance's item is the enabled cooperative-steal entry point.
|
||||
const makeLeaderItem = page
|
||||
.getByRole("menuitem")
|
||||
.filter({ hasText: "4821" });
|
||||
await expect(makeLeaderItem).toBeVisible();
|
||||
await makeLeaderItem.hover();
|
||||
|
||||
await page.screenshot({
|
||||
path: `${SHOTS}/04-make-leader-action.png`,
|
||||
clip: { x: 0, y: 0, width: 1280, height: 720 },
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user