Skip to content

feat: add CDPServer to drive lightpanda with Playwright/Puppeteer - #1

Merged
arrufat merged 1 commit into
mainfrom
cdp-server
Sep 2, 2026
Merged

feat: add CDPServer to drive lightpanda with Playwright/Puppeteer#1
arrufat merged 1 commit into
mainfrom
cdp-server

Conversation

@arrufat

@arrufat arrufat commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

What

Adds CDPServer / AsyncCDPServer, which spawn lightpanda serve (the browser's native Chrome DevTools Protocol server) on a free localhost port and expose ws_endpoint / http_endpoint for any CDP client:

from lightpanda import CDPServer
from playwright.sync_api import sync_playwright

with CDPServer() as server, sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(server.ws_endpoint)
    page = browser.new_context().new_page()
    page.goto("https://example.com")

The same endpoint works for Node Puppeteer (puppeteer.connect({ browserWSEndpoint })), Node Playwright (chromium.connectOverCDP), chromedp, etc. No Chromium is involved and the package still has zero runtime dependencies.

It is a separate process from Browser: the binary refuses mcp --port together with --cdp-port, and MCP sessions and CDP browser contexts are unrelated anyway.

How

  • client.py: the spawn loop moves out of Client.__init__ into a shared _spawn(binary, mode, ...) helper. _reserve_port(port=0) serves both the free-port path (retried on a bind collision, as before) and a caller-pinned port=, which reports "port N is already in use" instead of an opaque startup failure.
  • cdp.py: CDPServer (sync, context manager, __del__ cleanup, Linux PDEATHSIG via the shared spawn path) and AsyncCDPServer (lazy start() over asyncio.to_thread, mirroring AsyncBrowser's shape).
  • Endpoints use the 127.0.0.1 literal on purpose: the server only upgrades on path /, rejects any Origin header, and only accepts an IP-literal or localhost Host.

Tests

  • tests/test_cdp.py (stdlib only): endpoints and /json/version, pinned port, port in use, serve flag passthrough, close(), async lazy start. The PDEATHSIG test in test_browser.py is now parametrized over Browser and CDPServer.
  • tests/test_cdp_playwright.py: real connect_over_cdp sessions through both the ws and http endpoints. playwright is added to the dev dependency group and the CI wheel-test venv only; connect_over_cdp needs the pip package, not a browser download, so there is no playwright install step. The tests skip when it is absent.
  • Also verified by hand with Node puppeteer-core from the demo repo against a CDPServer endpoint.

Docs

README gets a "Drive it with Playwright or Puppeteer" section; the package docstring points at CDPServer. The docs site's Python reference will need a section for the new classes once this ships.

Add `CDPServer` / `AsyncCDPServer`, which spawn `lightpanda serve` (the
browser's native Chrome DevTools Protocol server) on a free localhost port
and expose `ws_endpoint` / `http_endpoint` for Playwright's
`connect_over_cdp`, Puppeteer's `connect`, chromedp, etc. No Chromium and no
runtime dependencies are involved. It is a separate process from `Browser`,
since the binary cannot serve MCP and CDP from one process.

The spawn loop in client.py becomes a shared `_spawn(binary, mode, ...)`
helper with an optional readiness probe (CDP probes `/json/version`) and a
fixed-port path that reports a port collision clearly.

Tests cover the process lifecycle with the standard library, plus real
`connect_over_cdp` sessions through both endpoints using the `playwright`
package as a CDP client (dev group only, no browser download; skipped when
absent).
@arrufat
arrufat merged commit 5aee5b0 into main Sep 2, 2026
10 checks passed
@arrufat
arrufat deleted the cdp-server branch September 2, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant