mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
test(desktop): publish explicit Welcome teammate presence
Keep the onboarding fixture independent of lifecycle bookkeeping. Prove started Welcome agents remain offline until scenario-authored presence, then retain the original kickoff and guidance assertions. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
@@ -8,6 +8,11 @@ import {
|
||||
TEST_IDENTITIES,
|
||||
} from "../helpers/bridge";
|
||||
import { expectEmojiMartStylesInstalled } from "../helpers/css";
|
||||
import {
|
||||
invokeMockCommand,
|
||||
publishWelcomeTeamPresence,
|
||||
waitForWelcomeTeam,
|
||||
} from "../helpers/welcomeTeam";
|
||||
import { installFakeCamera } from "../helpers/fakeCamera";
|
||||
import {
|
||||
E2E_IDENTITY_OVERRIDE_STORAGE_KEY,
|
||||
@@ -519,39 +524,6 @@ async function getMockChannels(page: Page) {
|
||||
});
|
||||
}
|
||||
|
||||
async function invokeMockCommand<T>(
|
||||
page: Page,
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) {
|
||||
return page.evaluate(
|
||||
async ({ command, payload }) => {
|
||||
const bridgeWindow = window as Window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
__TAURI_INTERNALS__?: {
|
||||
invoke?: (
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
const invoke =
|
||||
bridgeWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ??
|
||||
bridgeWindow.__TAURI_INTERNALS__?.invoke;
|
||||
|
||||
if (!invoke) {
|
||||
throw new Error("Mock invoke bridge is unavailable.");
|
||||
}
|
||||
|
||||
return (await invoke(command, payload)) as T;
|
||||
},
|
||||
{ command, payload },
|
||||
);
|
||||
}
|
||||
|
||||
async function seedCurrentAvatar(page: Page, avatarUrl: string) {
|
||||
await page.waitForFunction(() => {
|
||||
const bridgeWindow = window as Window & {
|
||||
@@ -3025,6 +2997,7 @@ test("avatar step accepts an avatar URL before completing onboarding", async ({
|
||||
|
||||
await page.getByTestId("onboarding-next").click();
|
||||
await expect(page.getByTestId("onboarding-gate")).toHaveCount(0);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
await expectWelcomeView(page);
|
||||
});
|
||||
|
||||
@@ -3060,6 +3033,7 @@ test("failed avatar saves can continue without saving the avatar", async ({
|
||||
await page.getByTestId("onboarding-next-without-saving").click();
|
||||
|
||||
await expect(page.getByTestId("onboarding-gate")).toHaveCount(0);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
await expectWelcomeView(page);
|
||||
});
|
||||
|
||||
@@ -3158,6 +3132,7 @@ test("first-run onboarding keeps the shell hidden and lands on private Welcome a
|
||||
await page.getByTestId("onboarding-display-name").fill("Alice");
|
||||
await completeProfileOnboarding(page);
|
||||
await expect(page.getByTestId("onboarding-gate")).toHaveCount(0);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
await expectWelcomeView(page);
|
||||
await expectStarterChannels(page);
|
||||
await expectWelcomeGuideIntro(page);
|
||||
@@ -3215,6 +3190,22 @@ test("first-run onboarding posts the live Fizz kickoff", async ({ page }) => {
|
||||
await completeProfileOnboarding(page);
|
||||
|
||||
await expectPrivateWelcomeLanding(page);
|
||||
// Runtime start alone cannot satisfy the kickoff's relay-presence wait.
|
||||
const team = await waitForWelcomeTeam(page);
|
||||
const presence = await invokeMockCommand<Record<string, string>>(
|
||||
page,
|
||||
"get_presence",
|
||||
{ pubkeys: team.map((agent) => agent.pubkey) },
|
||||
);
|
||||
expect(team.map((agent) => presence[agent.pubkey])).toEqual([
|
||||
"offline",
|
||||
"offline",
|
||||
"offline",
|
||||
]);
|
||||
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
||||
"Hi Morty QA, I'm Fizz. Welcome to Buzz.",
|
||||
);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
// Greeted by the name typed above — the @mention pill also files the opener
|
||||
// into the new user's Inbox mentions feed.
|
||||
await expect(page.getByTestId("message-timeline")).toContainText(
|
||||
@@ -3241,6 +3232,7 @@ test("first-run onboarding lands before Welcome team bootstrap completes", async
|
||||
|
||||
await expectPrivateWelcomeLanding(page);
|
||||
await expect(page.getByTestId("app-loading-gate")).toHaveCount(0);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
await expect(page.getByTestId("message-timeline")).toContainText(
|
||||
"Hi Morty QA, I'm Fizz. Welcome to Buzz.",
|
||||
);
|
||||
@@ -3315,6 +3307,7 @@ test("finishing onboarding creates starter channels and focuses welcome-everyone
|
||||
await page.getByTestId("onboarding-display-name").fill("Morty QA");
|
||||
await completeProfileOnboarding(page);
|
||||
|
||||
await publishWelcomeTeamPresence(page);
|
||||
await expectWelcomeView(page);
|
||||
await expect(page.getByTestId("channel-general")).toBeVisible();
|
||||
await expectStarterChannels(page);
|
||||
@@ -3331,6 +3324,7 @@ test("welcome-everywhere banner: X dismiss removes the guidance surface", async
|
||||
|
||||
await page.getByTestId("onboarding-display-name").fill("Morty QA");
|
||||
await completeProfileOnboarding(page);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
|
||||
const banner = page.getByTestId("welcome-composer-guide-banner");
|
||||
const guidanceLayer = page.getByTestId("welcome-composer-guidance-layer");
|
||||
@@ -3357,6 +3351,7 @@ test("welcome-everywhere banner: dismiss persists after channel re-entry", async
|
||||
|
||||
await page.getByTestId("onboarding-display-name").fill("Morty QA");
|
||||
await completeProfileOnboarding(page);
|
||||
await publishWelcomeTeamPresence(page);
|
||||
|
||||
const banner = page.getByTestId("welcome-composer-guide-banner");
|
||||
|
||||
|
||||
87
desktop/tests/helpers/welcomeTeam.ts
Normal file
87
desktop/tests/helpers/welcomeTeam.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
/** Invoke the mock command boundary without seeding product query caches. */
|
||||
export async function invokeMockCommand<T>(
|
||||
page: Page,
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) {
|
||||
return page.evaluate(
|
||||
async ({ command, payload }) => {
|
||||
const bridgeWindow = window as Window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
__TAURI_INTERNALS__?: {
|
||||
invoke?: (
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
const invoke =
|
||||
bridgeWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ??
|
||||
bridgeWindow.__TAURI_INTERNALS__?.invoke;
|
||||
|
||||
if (!invoke) {
|
||||
throw new Error("Mock invoke bridge is unavailable.");
|
||||
}
|
||||
|
||||
return (await invoke(command, payload)) as T;
|
||||
},
|
||||
{ command, payload },
|
||||
);
|
||||
}
|
||||
|
||||
type WelcomeAgent = {
|
||||
pubkey: string;
|
||||
persona_id: string;
|
||||
status: string;
|
||||
};
|
||||
// Pollen retains the builtin:bumble persona identity.
|
||||
const WELCOME_PERSONAS = ["builtin:bumble", "builtin:fizz", "builtin:honey"];
|
||||
|
||||
/** Wait for bootstrap identities so a scenario can author their relay events. */
|
||||
export async function waitForWelcomeTeam(page: Page): Promise<WelcomeAgent[]> {
|
||||
let team: WelcomeAgent[] = [];
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const agents = await invokeMockCommand<WelcomeAgent[]>(
|
||||
page,
|
||||
"list_managed_agents",
|
||||
);
|
||||
const { channels } = await invokeMockCommand<{
|
||||
channels: Array<{ id: string; name: string }>;
|
||||
}>(page, "get_channels");
|
||||
const welcome = channels.find((channel) => channel.name === "Welcome");
|
||||
if (!welcome) return [];
|
||||
const { members } = await invokeMockCommand<{
|
||||
members: Array<{ pubkey: string }>;
|
||||
}>(page, "get_channel_members", { channelId: welcome.id });
|
||||
// Select the started Welcome members, not inactive seeded siblings.
|
||||
// This only identifies the event authors; Start does not publish presence.
|
||||
team = agents.filter(
|
||||
(agent) =>
|
||||
agent.status === "running" &&
|
||||
members.some((member) => member.pubkey === agent.pubkey) &&
|
||||
WELCOME_PERSONAS.includes(agent.persona_id),
|
||||
);
|
||||
return team.map((agent) => `${agent.persona_id}:${agent.status}`).sort();
|
||||
})
|
||||
.toEqual(WELCOME_PERSONAS.map((persona) => `${persona}:running`));
|
||||
return team;
|
||||
}
|
||||
|
||||
/** Author healthy Welcome-team presence explicitly, independently of Start. */
|
||||
export async function publishWelcomeTeamPresence(page: Page) {
|
||||
const team = await waitForWelcomeTeam(page);
|
||||
await page.evaluate(
|
||||
(pubkeys) => {
|
||||
const emit = window.__BUZZ_E2E_EMIT_MOCK_PRESENCE__;
|
||||
if (!emit) throw new Error("Mock presence emitter is unavailable.");
|
||||
for (const pubkey of pubkeys) emit({ pubkey, status: "online" });
|
||||
},
|
||||
team.map((agent) => agent.pubkey),
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,10 @@ is not permission to deploy a duplicate body. Local Stop/Start routing is unchan
|
||||
runtime-only negative control, then authored Online presence.
|
||||
- `desktop/tests/e2e/profile.spec.ts`: created-agent initial Online snapshot and
|
||||
live Offline/Online transitions alongside independent Stop/Start controls.
|
||||
- `desktop/tests/e2e/onboarding.spec.ts`: Welcome kickoff stays pending after
|
||||
runtime start alone, then greets the owner and addresses Honey/Pollen only
|
||||
after the scenario publishes their explicit presence. Healthy-team scenarios
|
||||
use `tests/helpers/welcomeTeam.ts`, without changing the product readiness wait.
|
||||
|
||||
Mock create/start/stop only model runtime bookkeeping. Scenarios that require
|
||||
availability use `__BUZZ_E2E_EMIT_MOCK_PRESENCE__` to seed the snapshot and emit
|
||||
|
||||
Reference in New Issue
Block a user