import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { defineConfig } from "@playwright/test"; // Use a dedicated port so e2e tests always start their own server in local_trusted mode, // even when the dev server is running on :3100 in authenticated mode. const PORT = Number(process.env.PAPERCLIP_E2E_PORT ?? 3199); const BASE_URL = `http://127.0.0.1:${PORT}`; const PAPERCLIP_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-e2e-home-")); const PAPERCLIP_INSTANCE_ID = "playwright-e2e"; const PAPERCLIP_CONFIG = path.join(PAPERCLIP_HOME, "instances", PAPERCLIP_INSTANCE_ID, "config.json"); const PAPERCLIP_AGENT_JWT_SECRET = process.env.PAPERCLIP_AGENT_JWT_SECRET ?? "playwright-e2e-agent-jwt-secret"; const PAPERCLIP_DECISION_SIGNING_SECRET = process.env.PAPERCLIP_DECISION_SIGNING_SECRET ?? "playwright-e2e-decision-signing-secret"; const PAPERCLIP_TOOL_ACTION_SIGNING_SECRET = process.env.PAPERCLIP_TOOL_ACTION_SIGNING_SECRET ?? "playwright-e2e-tool-action-signing-secret"; const PLAYWRIGHT_CHANNEL = process.env.PAPERCLIP_PLAYWRIGHT_CHANNEL; process.env.PAPERCLIP_HOME = PAPERCLIP_HOME; process.env.PAPERCLIP_CONFIG = PAPERCLIP_CONFIG; // Worker processes reload this config; retain the main process's server path // for specs that seed historical database state in the throwaway instance. process.env.PAPERCLIP_E2E_SERVER_CONFIG ??= PAPERCLIP_CONFIG; // Specs that mint agent JWTs in-process (via createLocalAgentJwt) must derive // the same per-instance signing key as the webServer, or verification fails // with a 401 instead of authenticating as the agent. process.env.PAPERCLIP_INSTANCE_ID = PAPERCLIP_INSTANCE_ID; process.env.PAPERCLIP_AGENT_JWT_SECRET = PAPERCLIP_AGENT_JWT_SECRET; process.env.PAPERCLIP_DECISION_SIGNING_SECRET = PAPERCLIP_DECISION_SIGNING_SECRET; process.env.PAPERCLIP_TOOL_ACTION_SIGNING_SECRET = PAPERCLIP_TOOL_ACTION_SIGNING_SECRET; export default defineConfig({ testDir: ".", testMatch: "**/*.spec.ts", // These suites target dedicated multi-user configurations/ports and are // intentionally not part of the default local_trusted e2e run. testIgnore: ["in-feed-native/**", "multi-user.spec.ts", "multi-user-authenticated.spec.ts"], timeout: 60_000, retries: 0, // All specs share one throwaway server, and several toggle instance-level // state (the `enableConferenceRoomChat` experimental flag) that changes // which UI variant renders. Run files serially so a flag flip in one spec // can't change the wizard/thread under another spec mid-flight. workers: 1, use: { baseURL: BASE_URL, headless: true, screenshot: "only-on-failure", trace: "on-first-retry", }, projects: [ { name: "chromium", use: { browserName: "chromium", ...(PLAYWRIGHT_CHANNEL ? { channel: PLAYWRIGHT_CHANNEL } : {}), }, }, ], // The webServer directive bootstraps a throwaway instance and then starts it. // `onboard --yes --run` works in a non-interactive temp PAPERCLIP_HOME. webServer: { command: `pnpm paperclipai onboard --yes --run`, url: `${BASE_URL}/api/health`, // Always boot a dedicated throwaway instance for e2e so browser tests // never attach to the developer's active Paperclip home/server. reuseExistingServer: false, timeout: 120_000, stdout: "pipe", stderr: "pipe", env: { ...process.env, NODE_ENV: "test", NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ""} --import=${path.resolve(import.meta.dirname, "fixtures/agent-chat-github.mjs")}`, PORT: String(PORT), PAPERCLIP_OPEN_ON_LISTEN: "false", PAPERCLIP_API_URL: BASE_URL, PAPERCLIP_HOME, PAPERCLIP_INSTANCE_ID, PAPERCLIP_CONFIG, PAPERCLIP_AGENT_JWT_SECRET, PAPERCLIP_DECISION_SIGNING_SECRET, PAPERCLIP_TOOL_ACTION_SIGNING_SECRET, PAPERCLIP_BIND: "loopback", PAPERCLIP_DEPLOYMENT_MODE: "local_trusted", PAPERCLIP_DEPLOYMENT_EXPOSURE: "private", }, }, outputDir: "./test-results", reporter: [["list"], ["html", { open: "never", outputFolder: "./playwright-report" }]], });