test: unload chat client before restarting its server

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-21 14:35:51 -05:00
1 parent 639cb48562
commit 1fe2fe2751
5 files changed
+44 -2

No files matched your search

+3
View File
@@ -176,6 +176,9 @@ fails the case instead of silently testing another phase.
Restart continuity requires the agent to recall a phrase after the server
restarts. The final prompt does not reveal that phrase. A generic successful
reply after restart cannot pass this check.
The browser leaves the old development client before the server stops, then
opens the canonical chat route and waits for the composer. This avoids racing
Vite's automatic reconnect navigation against the test's explicit navigation.
The blocker query requests a JSON status snapshot. It must name the current
recorded blocker and report zero active runs independently of the task's blocked
+31
View File
@@ -0,0 +1,31 @@
import { createServer } from "node:http";
import { expect, test } from "@playwright/test";
import { restartChatServer } from "./chat-restart.js";
test("unloads the old browser client before server restart and opens the new document", async ({ page }) => {
let generation = 1;
const server = createServer((_req, res) => {
res.writeHead(200, { "content-type": "text/html" });
res.end(`<input data-testid="composer" value="server generation ${generation}"><script>setInterval(() => fetch('/probe').catch(() => {}), 50)</script>`);
});
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
if (!address || typeof address === "string") throw new Error("Missing listener");
const url = `http://127.0.0.1:${address.port}`;
try {
await page.goto(url);
await expect(page.getByTestId("composer")).toHaveValue("server generation 1");
await restartChatServer(page, async () => {
expect(page.url(), "The old client must be gone before it can auto-reload").toBe("about:blank");
server.closeAllConnections();
await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
generation += 1;
await new Promise<void>(resolve => server.listen(address.port, "127.0.0.1", resolve));
});
await page.goto(url, { waitUntil: "commit" });
await expect(page.getByTestId("composer")).toHaveValue("server generation 2");
} finally {
server.closeAllConnections();
await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
}
});
+7
View File
@@ -0,0 +1,7 @@
import type { Page } from "@playwright/test";
/** Restart the test server without letting the old Vite client race navigation. */
export async function restartChatServer(page: Page, restart: () => Promise<void>) {
await page.goto("about:blank", { waitUntil: "commit", timeout: 30_000 });
await restart();
}
@@ -3,7 +3,7 @@ import { defineConfig } from "@playwright/test";
/** Browser-only harness regressions: no Paperclip instance or provider credentials. */
export default defineConfig({
testDir: ".",
testMatch: ["screenshot-readiness.spec.ts", "lost-send.spec.ts"],
testMatch: ["screenshot-readiness.spec.ts", "lost-send.spec.ts", "chat-restart.spec.ts"],
workers: 1,
retries: 0,
timeout: 10_000,
+2 -1
View File
@@ -4,6 +4,7 @@ import { createTaskThroughUi, submitTaskReply } from "./user-actions.js";
import { runFirstTaskFlow, setupFirstTaskFixtures } from "./first-task-flow.js";
import { runChatFlow } from "./chat-flow.js";
import { restartChatServer } from "./chat-restart.js";
import { createHash, randomBytes } from "node:crypto";
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import path from "node:path";
@@ -832,7 +833,7 @@ for (const execution of executions) {
} else if (execution.task.flow === "agent_chat") {
const chat = await runChatFlow({
page, api, fixtures, execution, nonce, workspacePath,
restart: () => restartIsolatedPaperclipServer({ api, requestId: `chat-${nonce}`, deadlineAt: startedAtMs + deadlineMs }),
restart: () => restartChatServer(page, () => restartIsolatedPaperclipServer({ api, requestId: `chat-${nonce}`, deadlineAt: startedAtMs + deadlineMs })),
observe: (chatIssue, chatRuns) => { issue = chatIssue; selectedRuns = chatRuns; },
capture: captureScreenshot,
evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets),