evOo-local/apps/web/e2e/peer-messages.spec.ts
Claude f3e438444a
Put voice in the composer's send slot until there is something to send
An empty draft shows the mic where the send arrow sits; it opens the
voice conversation as the separate Voice button did. As soon as the draft
holds text, a chip or an attachment, the slot turns back into Send. This
frees the width the extra Voice pill took on small screens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcoERsCTbvjqDzE8qC82Wo
2026-10-05 08:52:06 +00:00

121 lines
4.8 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { activeBotId, captureScreenshot, completeOnboarding, rpc, signup } from "./helpers";
test("shows peer chips in transcript and opens view-only peer chat", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `peer-msg-${stamp}@rakazo.test`, "password12", "Peer Msg");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
const chiefId = activeBotId(page);
await rpc(page, "bots/create", {
name: "Researcher",
title: "",
description: "",
instructions: "",
notifyOnFinish: true,
});
await page.reload();
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
const composer = page.getByRole("combobox", { name: "Message" });
await composer.fill("message the bot named Researcher saying peer-exchange-alpha");
await composer.press("Enter");
await expect(page.getByText("messaging that bot now.").first()).toBeVisible({
timeout: 60_000,
});
await expect
.poll(
async () => {
const history = await rpc<{
messages: Array<{ blocks: Array<{ kind: string; text?: string }> }>;
}>(page, "threads/messages", { botId: chiefId, includePeerRuns: true });
const peerTexts = history.messages.flatMap((message) =>
message.blocks
.filter(
(block) => block.kind === "bot_message_sent" || block.kind === "bot_message_received",
)
.map((block) => block.text ?? ""),
);
return peerTexts.some((text) => text.includes("peer-exchange-alpha"));
},
{ timeout: 60_000 },
)
.toBe(true);
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible({ timeout: 60_000 });
const transcript = page.getByTestId("transcript");
const chip = transcript
.getByTestId("peer-receipt-chip")
.filter({ hasText: "Researcher" })
.first();
await expect(chip).toBeVisible({ timeout: 30_000 });
await expect(chip.getByText(/Messaged|Message from/)).toBeVisible();
await expect(chip).toHaveAccessibleName(/Messaged Researcher|Message from Researcher/);
await expect(chip.locator(".rakazo-bot-avatar")).toBeVisible();
await expect(chip).not.toContainText("{peer}");
// The user bubble and the delegated task card both show the task text.
// The receipt chip stays a status marker and does not repeat the body.
await expect(chip).not.toContainText("peer-exchange-alpha");
await expect(transcript.getByText("peer-exchange-alpha")).toHaveCount(2);
const assertChipLeftAligned = async () => {
const boxes = await chip.evaluate((node) => {
const transcript = node.closest("[data-testid='transcript']");
if (!transcript) return null;
const chipBox = node.getBoundingClientRect();
const transcriptBox = transcript.getBoundingClientRect();
return {
inset: chipBox.left - transcriptBox.left,
chipWidth: chipBox.width,
transcriptWidth: transcriptBox.width,
};
});
expect(boxes).not.toBeNull();
// The reading column is centered in the transcript. The chip sits on that
// column's left edge, so a midpoint chip fails the upper bound.
expect(boxes!.inset).toBeGreaterThanOrEqual(8);
expect(boxes!.inset).toBeLessThan(boxes!.transcriptWidth / 3);
expect(boxes!.chipWidth).toBeLessThan(boxes!.transcriptWidth);
};
await assertChipLeftAligned();
await expect(composer).toBeVisible();
await captureScreenshot(page, testInfo, "peer-chip-desktop");
await page.setViewportSize({ width: 390, height: 844 });
await chip.scrollIntoViewIfNeeded();
await expect(chip).toBeVisible();
await expect(composer).toBeVisible();
await assertChipLeftAligned();
await captureScreenshot(page, testInfo, "peer-chip-mobile");
await chip.focus();
await expect(chip).toBeFocused();
await chip.press("Enter");
const view = page.getByTestId("peer-conversation-view");
await expect(view).toBeVisible();
await expect(view.getByRole("heading", { name: /Chief · Researcher/ })).toBeVisible();
await expect(view.getByText("This chat is view-only")).toBeVisible();
await expect(view.getByRole("button", { name: "Close" })).toHaveCount(1);
await expect(view.getByText("peer-exchange-alpha").first()).toBeVisible({
timeout: 30_000,
});
await expect(view.getByRole("textbox")).toHaveCount(0);
await expect(view.getByText("Loading")).toHaveCount(0);
const peerTranscript = view.getByTestId("peer-conversation-transcript");
await peerTranscript.evaluate((element) => {
element.scrollTop = element.scrollHeight;
});
await expect
.poll(() =>
peerTranscript.evaluate(
(element) => element.scrollTop + element.clientHeight >= element.scrollHeight - 1,
),
)
.toBe(true);
await captureScreenshot(page, testInfo, "peer-view-only");
});