Files
buzz/desktop/tests/e2e/thread-reply-anchor-roleplay.spec.ts
2026-06-27 06:27:51 -07:00

352 lines
10 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 { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge";
const SHOTS = "test-results/thread-reply-anchor-roleplay";
const SELF_PUBKEY = "deadbeef".repeat(8);
const CHANNEL = "general";
type MockMessageEvent = {
id: string;
created_at: number;
pubkey: string;
};
async function waitForMockLiveSubscription(
page: import("@playwright/test").Page,
channelName: string,
) {
await expect
.poll(async () => {
return page.evaluate(
({ ch }) =>
(
window as Window & {
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
channelName: string;
}) => boolean;
}
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: ch }) ??
false,
{ ch: channelName },
);
})
.toBe(true);
}
async function emitMockMessage(
page: import("@playwright/test").Page,
channelName: string,
content: string,
options?: {
parentEventId?: string | null;
pubkey?: string;
createdAt?: number;
mentionPubkeys?: string[];
},
): Promise<MockMessageEvent> {
const event = await page.evaluate(
({ ch, msg, parentEventId, pubkey, ts, mentionPubkeys }) => {
return (
window as Window & {
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
channelName: string;
content: string;
parentEventId?: string | null;
pubkey?: string;
createdAt?: number;
mentionPubkeys?: string[];
}) => { id: string; created_at: number; pubkey: string };
}
).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
channelName: ch,
content: msg,
parentEventId,
pubkey,
createdAt: ts,
mentionPubkeys,
});
},
{
ch: channelName,
msg: content,
parentEventId: options?.parentEventId ?? null,
pubkey: options?.pubkey ?? SELF_PUBKEY,
ts: options?.createdAt,
mentionPubkeys: options?.mentionPubkeys,
},
);
if (!event) {
throw new Error("Mock message emitter is not installed");
}
return event;
}
async function setupRoleplayChannel(page: import("@playwright/test").Page) {
await installMockBridge(page, {
relayAgents: [
{
pubkey: TEST_IDENTITIES.alice.pubkey,
name: "Pinky",
respondTo: "anyone",
channelNames: [CHANNEL],
status: "online",
},
{
pubkey: TEST_IDENTITIES.charlie.pubkey,
name: "Brain",
respondTo: "anyone",
channelNames: [CHANNEL],
status: "online",
},
],
searchProfiles: [
{
pubkey: TEST_IDENTITIES.alice.pubkey,
displayName: "Pinky",
isAgent: true,
},
{
pubkey: TEST_IDENTITIES.charlie.pubkey,
displayName: "Brain",
isAgent: true,
},
{
pubkey: TEST_IDENTITIES.bob.pubkey,
displayName: "Wes",
isAgent: false,
},
{
pubkey: SELF_PUBKEY,
displayName: "Nora",
isAgent: false,
},
],
});
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText(CHANNEL);
await waitForMockLiveSubscription(page, CHANNEL);
}
async function openThread(page: import("@playwright/test").Page) {
const summary = page.getByTestId("message-thread-summary").first();
await expect(summary).toBeVisible();
await summary.click();
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
}
async function expandReply(
page: import("@playwright/test").Page,
replyId: string,
) {
const replies = page
.getByTestId("message-thread-replies")
.getByTestId("message-row");
const before = await replies.count();
await page.locator(`[data-thread-head-id="${replyId}"]`).click();
await expect.poll(() => replies.count()).toBeGreaterThan(before);
}
async function screenshotThreadPanel(
page: import("@playwright/test").Page,
path: string,
) {
const panel = page.getByTestId("message-thread-panel");
await expect(panel).toBeVisible();
await page.mouse.move(360, 24);
await page.waitForTimeout(100);
await panel.screenshot({ path });
}
test.describe("thread reply anchor A/B roleplay screenshots", () => {
test("01-baseline-human-reply-nests-agent-at-depth-2", async ({ page }) => {
await setupRoleplayChannel(page);
const now = Math.floor(Date.now() / 1000);
const root = await emitMockMessage(
page,
CHANNEL,
"Wes: @Pinky please review the checkout copy.",
{
pubkey: TEST_IDENTITIES.bob.pubkey,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now,
},
);
const humanReply = await emitMockMessage(
page,
CHANNEL,
"Nora: adding context — this is only about the receipt screen.",
{
parentEventId: root.id,
pubkey: SELF_PUBKEY,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now + 1,
},
);
// Baseline queue.rs anchored the agent response to the triggering human
// reply, producing depth 2 under Nora's message.
await emitMockMessage(
page,
CHANNEL,
"Pinky: Got it — I’ll check the receipt copy only. Narf!",
{
parentEventId: humanReply.id,
pubkey: TEST_IDENTITIES.alice.pubkey,
mentionPubkeys: [TEST_IDENTITIES.bob.pubkey, SELF_PUBKEY],
createdAt: now + 2,
},
);
await openThread(page);
await expandReply(page, humanReply.id);
await expect(page.getByText("Nora: adding context")).toBeVisible();
await expect(page.getByText("Pinky: Got it")).toBeVisible();
await expect(
page.getByTestId("message-thread-replies").getByTestId("message-row"),
).toHaveCount(2);
await expect(page.getByTestId("thread-collapse-rail")).toHaveCount(1);
await screenshotThreadPanel(page, `${SHOTS}/01-baseline-depth-2.png`);
});
test("02-patched-human-reply-flattens-agent-at-root", async ({ page }) => {
await setupRoleplayChannel(page);
const now = Math.floor(Date.now() / 1000);
const root = await emitMockMessage(
page,
CHANNEL,
"Wes: @Pinky please review the checkout copy.",
{
pubkey: TEST_IDENTITIES.bob.pubkey,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now,
},
);
await emitMockMessage(
page,
CHANNEL,
"Nora: adding context — this is only about the receipt screen.",
{
parentEventId: root.id,
pubkey: SELF_PUBKEY,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now + 1,
},
);
// Patched queue.rs anchors the agent response to the thread root, keeping
// both human and agent replies as flat layer-1 siblings.
await emitMockMessage(
page,
CHANNEL,
"Pinky: Got it — I’ll check the receipt copy only. Narf!",
{
parentEventId: root.id,
pubkey: TEST_IDENTITIES.alice.pubkey,
mentionPubkeys: [TEST_IDENTITIES.bob.pubkey, SELF_PUBKEY],
createdAt: now + 2,
},
);
await openThread(page);
await expect(page.getByText("Nora: adding context")).toBeVisible();
await expect(page.getByText("Pinky: Got it")).toBeVisible();
await expect(
page.getByTestId("message-thread-replies").getByTestId("message-row"),
).toHaveCount(2);
await expect(page.getByTestId("thread-collapse-rail")).toHaveCount(0);
await screenshotThreadPanel(page, `${SHOTS}/02-patched-flat-l1.png`);
});
test("03-patched-top-level-human-starts-thread-at-human-root", async ({
page,
}) => {
await setupRoleplayChannel(page);
const now = Math.floor(Date.now() / 1000);
const humanRoot = await emitMockMessage(
page,
CHANNEL,
"Wes: @Pinky start the inventory audit.",
{
pubkey: TEST_IDENTITIES.bob.pubkey,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now,
},
);
await emitMockMessage(
page,
CHANNEL,
"Pinky: Starting the audit and I’ll report back here. Poit!",
{
parentEventId: humanRoot.id,
pubkey: TEST_IDENTITIES.alice.pubkey,
mentionPubkeys: [TEST_IDENTITIES.bob.pubkey],
createdAt: now + 1,
},
);
await openThread(page);
await expect(page.getByText("Pinky: Starting the audit")).toBeVisible();
await expect(
page.getByTestId("message-thread-replies").getByTestId("message-row"),
).toHaveCount(1);
await screenshotThreadPanel(page, `${SHOTS}/03-top-level-human-root.png`);
});
test("04-agent-only-branch-keeps-deeper-nesting", async ({ page }) => {
await setupRoleplayChannel(page);
const now = Math.floor(Date.now() / 1000);
const root = await emitMockMessage(
page,
CHANNEL,
"Pinky: @Brain I found a failing visual case.",
{
pubkey: TEST_IDENTITIES.alice.pubkey,
mentionPubkeys: [TEST_IDENTITIES.charlie.pubkey],
createdAt: now,
},
);
const brainReply = await emitMockMessage(
page,
CHANNEL,
"Brain: Check the anchor emitted by queue.rs.",
{
parentEventId: root.id,
pubkey: TEST_IDENTITIES.charlie.pubkey,
mentionPubkeys: [TEST_IDENTITIES.alice.pubkey],
createdAt: now + 1,
},
);
await emitMockMessage(
page,
CHANNEL,
"Pinky: Good catch — agent-only branches can stay nested. Zort!",
{
parentEventId: brainReply.id,
pubkey: TEST_IDENTITIES.alice.pubkey,
mentionPubkeys: [TEST_IDENTITIES.charlie.pubkey],
createdAt: now + 2,
},
);
await openThread(page);
await expandReply(page, brainReply.id);
await expect(page.getByText("Brain: Check the anchor")).toBeVisible();
await expect(page.getByText("Pinky: Good catch")).toBeVisible();
await expect(
page.getByTestId("message-thread-replies").getByTestId("message-row"),
).toHaveCount(2);
await expect(page.getByTestId("thread-collapse-rail")).toHaveCount(1);
await screenshotThreadPanel(page, `${SHOTS}/04-agent-only-nested.png`);
});
});