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"); });