Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
7.9 kB · 110 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111// Synthetic-only rendered acceptance test. No SDK, live OAuth, or private history.import assert from "node:assert/strict";import fs from "node:fs/promises";import http from "node:http";import os from "node:os";import path from "node:path";import { randomUUID } from "node:crypto";import { chromium } from "playwright-core";import { createCoChatHandler } from "../src/web/co-chat-http.js";import type { CoChat } from "../src/web/co-chat.js";import type { InspectorOAuthAuth } from "../src/web/oauth-auth.js";import type { ChatRow, ChatSend, WebConversation } from "../src/web/co-chat-types.js";
assert(process.env.CHROME_BIN, "Set CHROME_BIN to an existing Chromium executable; this test never installs a browser.");const root = await fs.mkdtemp(path.join(os.tmpdir(), "co-chat-browser-"));const conversations: WebConversation[] = [];const rows = new Map<string, ChatRow[]>();let sendCount = 0; let stopCount = 0;const fake = { list: () => conversations, create: async () => { const now = new Date().toISOString(); const conversation: WebConversation = { id: randomUUID(), title: "New conversation", createdAt: now, updatedAt: now }; conversations.unshift(conversation); rows.set(conversation.id, []); return conversation; }, rename: async (id: string, title: string) => { const item = conversations.find((entry) => entry.id === id)!; item.title = title; return item; }, snapshot: async (id: string) => ({ conversation: conversations.find((entry) => entry.id === id), rows: rows.get(id), hasMore: false, nextBefore: null }), send: async (id: string, input: ChatSend) => { sendCount++; const conversation = conversations.find((entry) => entry.id === id)!; const now = new Date().toISOString(); conversation.turn = { requestId: input.requestId, status: "running", createdAt: now, updatedAt: now, runIds: [] }; rows.get(id)!.push({ kind: "user", key: input.requestId, otid: input.requestId, text: input.text }, { kind: "tool_call", key: "tool", name: "SyntheticTool", outcome: "pending" }, { kind: "assistant", key: "answer", text: "A synthetic response.\n\n```js\nconst answer = 42;\n```\n\n<script>window.injected = true</script>\n\n" }); return conversation.turn; }, stop: async (id: string) => { stopCount++; const conversation = conversations.find((entry) => entry.id === id)!; conversation.turn!.status = "interrupted"; return conversation; },} as unknown as CoChat;const oauth = { authenticate: async (cookie?: string) => cookie === "synthetic-owner=yes" ? { csrfToken: "synthetic-csrf" } : undefined } as unknown as InspectorOAuthAuth;let handler: ReturnType<typeof createCoChatHandler>;const server = http.createServer((request, response) => { void handler(request, response, new URL(request.url!, "http://fixture.invalid")); });await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));const address = server.address(); assert(address && typeof address !== "string");const origin = `http://127.0.0.1:${address.port}`;handler = createCoChatHandler({ chat: fake, origin, javascript: await fs.readFile("dist/co-chat/app.js", "utf8"), stylesheet: await fs.readFile("dist/co-chat/app.css", "utf8") }, oauth);const browser = await chromium.launch({ executablePath: process.env.CHROME_BIN, headless: true });try { for (const [width, colorScheme] of [[1440, "light"], [390, "light"], [390, "dark"]] as const) { const context = await browser.newContext({ viewport: { width, height: 900 }, colorScheme, permissions: ["clipboard-read", "clipboard-write"] }); const page = await context.newPage(); const errors: string[] = []; const external: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await context.route("**/*", (route) => { if (!route.request().url().startsWith(origin)) { external.push(route.request().url()); return route.abort(); } return route.continue(); }); await context.addCookies([{ name: "synthetic-owner", value: "yes", url: origin }]); await page.route("**/chat/api/conversations/*/send", async (route) => { const metadata = await page.evaluate(() => JSON.parse(sessionStorage.getItem("co-web-pending-send-v1")!)); assert.deepEqual(Object.keys(metadata).sort(), ["id", "requestId"]); assert.equal(metadata.requestId, route.request().postDataJSON().requestId); await route.continue(); }); await page.goto(origin + "/chat/"); assert.equal(await page.getByText("What’s on your mind?", {exact:true}).count(), 0); assert.equal(await page.getByText("A place to think together", {exact:true}).count(), 0); assert.equal(await page.evaluate(() => getComputedStyle(document.body).backgroundColor), colorScheme === "dark" ? "rgb(10, 10, 10)" : "rgb(255, 255, 255)"); await page.getByRole("button", { name: "New conversation", exact: true }).click(); const composer = page.getByRole("textbox", { name: "Message Co", exact: true }); await composer.waitFor(); const beforeOversize = sendCount; await composer.fill("界".repeat(11_000)); await page.getByRole("button", { name: "Send", exact: true }).click(); await page.getByText("Message and selected context exceed 32 KB. Shorten them before sending.", { exact: false }).waitFor(); assert.equal(sendCount, beforeOversize, "UTF-8 size is checked before admission"); await composer.fill("Synthetic first line"); await composer.press("Shift+Enter"); await composer.press("x"); assert((await composer.inputValue()).includes("\n")); const previous = sendCount; await composer.dispatchEvent("keydown", { key: "Enter", code: "Enter", isComposing: true }); assert.equal(sendCount, previous, "IME Enter must not send"); await page.getByRole("button", { name: "Send", exact: true }).click(); await page.getByText("A synthetic response.", { exact: true }).waitFor(); assert.equal(sendCount, previous + 1); assert.equal(await page.locator("details.tool[open]").count(), 0); await page.getByRole("button", { name: "Copy code", exact: true }).click(); assert.equal(await page.evaluate(() => navigator.clipboard.readText()), "const answer = 42;\n"); await page.getByRole("button", { name: "Stop", exact: true }).click(); await page.getByText("Stopped", { exact: true }).waitFor(); assert(stopCount > 0); await page.reload(); await page.getByText("A synthetic response.", { exact: true }).waitFor(); assert.equal(sendCount, previous + 1, "Reload must not resend"); assert.equal(await page.locator(".message.user").count(), 1); assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth), false); assert.equal(await page.evaluate(() => (window as unknown as { injected?: boolean }).injected), undefined); assert.deepEqual(external, [], "No remote images or other destinations"); assert.deepEqual(errors, []); if (width === 390) { await page.getByRole("button", { name: "Show conversations" }).click(); assert(await page.getByRole("button", { name: "+ New chat", exact: true }).isVisible()); await page.getByRole("button", { name: "Close conversations" }).click({ position: { x: 380, y: 30 } }); await page.waitForFunction(() => document.querySelector(".sidebar")!.getBoundingClientRect().right <= 1); } await page.screenshot({ path: path.join(root, `chat-${width}-${colorScheme}.png`), fullPage: true }); await context.close(); } console.log(JSON.stringify({ syntheticOnly: true, widths: [1440, 390], checks: ["composer", "IME", "streamed text", "collapsed tools", "copy code", "Stop", "reload", "no duplicate send", "no overflow", "no HTML execution", "no external requests"], screenshots: root }));} finally { await browser.close(); await new Promise<void>((resolve) => { server.closeAllConnections(); server.close(() => resolve()); });}