feat(lightpanda): add @e2e-dev/lightpanda, a browser provider for the Lightpanda headless browser

Draft
OK
11d ago
+1031-8

What and why

  • New package @e2e-dev/lightpanda: lightpanda() is a BrowserProvider for the web engine that starts one lightpanda serve per worker slot on a free loopback port and stops its process group when the run ends. No vendor SDK; the binary is the dependency.
  • Lightpanda is an open-source headless browser without Chromium: instant start, a fraction of Chrome's memory, and CDP, so the engine attaches to it the way it attaches to Kernel.
  • Binary discovery: binary, LIGHTPANDA_PATH, then lightpanda on PATH, ~/.lightpanda/lightpanda, ~/.local/bin/lightpanda (the places agent-browser looks). None found fails the lease before any test, naming the install page.
  • A server already running (the Docker image in CI) is a web({ connect }) target, so the provider has one lease shape and no attach mode.
  • Worker scope only: Lightpanda exposes no default context identity, so a per-attempt lease fails with CDP recovery failed: the default context identity is unavailable. The option is not offered.
  • Docs page docs/integrations/lightpanda.mdx with setup, options, and a table of what Lightpanda cannot do yet (sessions, dialogs, downloads, iframes, drags, resize, scrolling) and why, each verified against the nightly. Testbed gets e2e.lightpanda.config.ts and pnpm test:lightpanda to re-check that table as Lightpanda ships fixes.
import { lightpanda } from '@e2e-dev/lightpanda';

export default {
  targets: [{ name: 'lightpanda', engine: web({ browser: lightpanda({ loadResources: ['iframe'] }) }), app: { url } }],
} satisfies E2EConfig;
sequenceDiagram
  participant R as runner (prepare)
  participant P as lightpanda()
  participant S as lightpanda serve
  participant W as worker
  R->>P: acquire(slot n)
  P->>S: spawn --host 127.0.0.1 --port free, detached
  P->>S: poll /json/version (10 s cap)
  S-->>P: Lightpanda-Version
  P-->>R: lease { id: ws://127.0.0.1:port/ }
  W->>S: connectOverCDP, one context per attempt
  R->>P: release(lease)
  P->>S: SIGTERM group, SIGKILL after 5 s

Benchmark

Testbed, the six files that pass on both engines copied four times (96 tests), M4 Pro, mean of two runs. Baseline is web() Chromium on this branch; the candidate is lightpanda().

WorkersChromium wallLightpanda wallChromium peak RSSLightpanda peak RSS
131.6 s21.8 s358 MB124 MB
49.8 s6.6 s1.30 GB364 MB
86.7 s5.0 s2.52 GB587 MB
146.1 s4.1 s4.12 GB954 MB

Per-test time grows 33 percent from 1 to 14 workers on Lightpanda and 77 percent on Chromium; Chromium stops gaining past 8 workers on 14 cores.

Verified

Ran it locally: yes

  • testbed, LIGHTPANDA_PATH=... pnpm test:lightpanda --workers 2: two servers leased on distinct ports, logged as lightpanda 1.1.0-nightly.10207 at ws://127.0.0.1:51165/, released at the end, pgrep lightpanda empty after.
  • Full testbed suite on the provider: 53 passed, 18 failed, 1 skipped; every failure is in the docs table and reproduced with a bare Playwright connectOverCDP probe, so none is engine-side.
  • scope: 'attempt' tried live and fails as described, which is why it is absent.
  • Empty PATH and HOME: the run fails before any test with lightpanda is not on PATH, in ~/.lightpanda, or in ~/.local/bin; install it (https://lightpanda.io/docs/open-source/installation) or set LIGHTPANDA_PATH.
  • Docs page rendered in mint dev, sidebar icon and tables in place.

Checklist

  • Changeset added (pnpm changeset) for any change to a published package, or the change is testbed, docs, or CI only.
  • Breaking change: title carries !, the changeset body names what breaks and what replaces it, and the deprecation warning landed a window earlier (stability policy in CONTRIBUTING.md).
  • Docs updated in the same PR: the docs/**/*.mdx page for the behavior, and skills/e2e/ if the skill describes it.
  • Contract change: emitted .d.ts reviewed, tests/types/sdk-types.ts updated; wire change edits the schema and both fixtures.
  • Engine contract change (e2e/engine): changesets for e2e, @e2e-dev/web, and @e2e-dev/mobile.