tfrere's picture
tfrere HF Staff
test(backend): add Playwright E2E suite + deps for editor basics, chat persistence and publish
6a34a48
Raw
History Blame Contribute Delete
2.86 kB
/**
* E2E test fixtures: spins up a real server on a random port with
* mocked /api/chat and /api/embed-chat endpoints (no LLM calls).
*/
import { test as base, type Page } from "@playwright/test";
import { mkdtempSync, rmSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import type { AddressInfo } from "net";
import { createApp, resetSaveTimers } from "../src/create-app.js";
import { setDataDir } from "../src/utils.js";
const __filename = fileURLToPath(import.meta.url);
const BACKEND_DIR = join(dirname(__filename), "..");
const STREAM_HEADERS = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
"X-Vercel-AI-UI-Message-Stream": "v1",
};
function buildFakeStream(text: string): string {
const textId = `txt-${Date.now()}`;
const events = [
{ type: "start" },
{ type: "text-start", id: textId },
{ type: "text-delta", id: textId, delta: text },
{ type: "text-end", id: textId },
{ type: "finish-step" },
{ type: "finish" },
];
const lines = events.map((e) => `data: ${JSON.stringify(e)}\n`).join("\n");
return lines + "\ndata: [DONE]\n\n";
}
interface TestFixtures {
serverUrl: string;
appPage: Page;
}
export const test = base.extend<TestFixtures>({
serverUrl: [
async ({}, use) => {
// Keep tmpDir under backend/ so relative staticDir path (../../frontend/dist) resolves
const tmpDir = mkdtempSync(join(BACKEND_DIR, ".e2e-data-"));
setDataDir(tmpDir);
const { app, httpServer, hocuspocus } = createApp();
await new Promise<void>((resolve) => httpServer.listen(0, resolve));
const port = (httpServer.address() as AddressInfo).port;
const url = `http://localhost:${port}`;
await use(url);
resetSaveTimers();
try { await hocuspocus.destroy(); } catch {}
try { httpServer.close(); } catch {}
setDataDir(undefined);
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
},
{ scope: "test" },
],
appPage: async ({ page, serverUrl }, use) => {
// Intercept chat API calls at the browser level
await page.route("**/api/chat", async (route) => {
await route.fulfill({
status: 200,
headers: STREAM_HEADERS,
body: buildFakeStream("Hello! I'm the AI assistant."),
});
});
await page.route("**/api/embed-chat", async (route) => {
await route.fulfill({
status: 200,
headers: STREAM_HEADERS,
body: buildFakeStream("Chart created successfully."),
});
});
await page.goto(serverUrl);
// Wait for editor to be ready (toolbar visible)
await page.waitForSelector("[aria-label='Undo']", { timeout: 15_000 });
await use(page);
},
});
export { expect } from "@playwright/test";