Files
PaperClipAI/tests/e2e/playwright.config.ts
T
Dotta e3eed3a3ae Keep browser startup explicitly opt-in (#12435)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The CLI can save a configuration and start the local server in one
command.
> - The onboarding path set a browser-open environment variable without
an explicit user request.
> - Headless test servers use the same onboarding path.
> - Each server restart could therefore open a system browser.
> - This pull request removes the implicit browser-open request and
fixes test servers to disable it explicitly.
> - The benefit is predictable foreground and test startup without
unsolicited browser windows.

## Linked Issues or Issue Description

This is stack 11 of 11. It depends on stack 10.

**What happened?**

`paperclipai onboard --yes --run` set `PAPERCLIP_OPEN_ON_LISTEN=true`.
Headless server users, including browser test runners, opened the system
browser on each server restart.

**Expected behavior**

Server startup must not open a browser unless the caller explicitly sets
`PAPERCLIP_OPEN_ON_LISTEN=true`.

**Steps to reproduce**

1. Run `paperclipai onboard --yes --run` from a clean source checkout.
2. Wait for the server to listen.
3. Observe that the default system browser opens.

**Paperclip version or commit**

Reproduced on `dbf052577` plus the dependent stack.

**Deployment mode**

Local dev from source.

## What Changed

- Stop onboarding from setting `PAPERCLIP_OPEN_ON_LISTEN=true` for
foreground startup.
- Set `PAPERCLIP_OPEN_ON_LISTEN=false` in E2E and issue-detail
performance test servers as defense in depth.
- Preserve the existing explicit environment opt-in in the server.

## Verification

- `pnpm exec vitest run cli/src/__tests__/onboard.test.ts` — 10 tests
passed.
- `pnpm --filter paperclipai typecheck` — passed.
- `pnpm -r typecheck` — passed on the stacked head.
- `pnpm build` — passed on the stacked head.
- Playwright was not run locally by request.

## Risks

- Low risk. The only behavior change removes an unsolicited side effect.
- A caller that wants browser startup can still set
`PAPERCLIP_OPEN_ON_LISTEN=true` explicitly.
- No database or migration change exists in this layer.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, GPT-5. The exact deployment suffix and context window
are not exposed. The model used reasoning, repository tools, code
execution, Git, and GitHub API access.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-29 12:08:35 -05:00

89 lines
3.7 KiB
TypeScript

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;
// 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: ["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",
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" }]],
});