Transport

Understand how PHP starts the bridge, exchanges commands, receives events, and reports failures.

The transport is the private boundary between the PHP client and the Node.js bridge. It starts the bridge process, writes framed JSON-RPC messages to its standard input, reads responses and events from standard output, and maps failures to PHP exceptions.

Normal tests should not build or parse transport messages. This page is for runtime diagnosis and contribution work.

The shipped path

text
PHP objects
  -> JsonRpcTransport
      -> Node.js bridge process
          -> playwright-core
              -> browser

TransportFactory assembles this path:

  1. ServerFinder locates the bundled bridge script.
  2. NodeBinaryResolver selects a compatible Node.js executable.
  3. ProcessLauncher starts the bridge.
  4. JsonRpcTransport sends commands and waits for responses.
  5. ErrorMapper turns transport and driver errors into PHP exceptions.

The connection is local and process-based. It does not need an HTTP server or an open TCP port.

Commands and responses

Public method calls become internal action payloads. The JSON-RPC client adds a request id, writes an LSP-style Content-Length frame, and waits for the response with the matching id.

Conceptually:

text
PHP:     page.goto(url)
Request: action=goto, pageId=..., url=...
Bridge:  call Playwright Page.goto()
Result:  navigation response data
PHP:     return ResponseInterface

The exact JSON shape is internal. It can change while the public PHP method remains stable. Do not copy message payloads into application tests or treat them as a supported integration protocol.

Events while PHP is synchronous

The public API is synchronous, but browsers emit asynchronous events: requests, responses, console messages, dialogs, downloads, page errors, and frame changes.

The transport drains queued events while calls are in progress and dispatches them to handlers registered on the relevant PHP object. That lets a synchronous PHP test observe browser events without exposing promises or an event loop in user code.

Register handlers before the action that may emit the event:

php
$messages = [];

$page->events()->onConsole(function ($message) use (&$messages): void {
    $messages[] = $message->text();
});

$page->goto('https://example.com');

For event-specific method names and callback types, use Events and the API reference.

Process failures

The layer that failed determines what to inspect:

Symptom Likely boundary
Node.js executable not found Node resolution
Bridge script not found Package installation or server discovery
Browser executable missing Browser installation
Process exits during startup Bridge or browser launch
Request times out with a running browser Command, page state, or timeout
Connection closes unexpectedly Bridge or browser crash

Relevant exception families include:

  • ProcessLaunchException;
  • ProcessCrashedException;
  • DisconnectedException;
  • TransportException;
  • ProtocolErrorException;
  • TimeoutException.

Catch specific exceptions when recovery differs. Otherwise let the failure retain its original context and collect logs around startup.

Logging without leaking data

The client accepts a PSR-3 logger when created through PlaywrightFactory. Use a debug logger temporarily when diagnosing startup or command flow:

php
$client = PlaywrightFactory::create($config, $logger);

Transport logs may contain URLs, headers, form values, or response data. Keep verbose logs out of normal CI output, redact secrets, and use short retention periods for failure artifacts.

Browser servers are different

launchServer() creates a long-lived browser server and returns a WebSocket endpoint. Connecting to that endpoint is a browser deployment pattern; it does not turn the PHP client transport itself into a public WebSocket protocol.

Likewise, WebSocket, WebSocketRoute, and related classes describe WebSocket traffic from the tested application. They are not the PHP-to-bridge transport.

Contributor rules

When changing this layer:

  • preserve the public API even if the bridge payload changes;
  • keep request ids and event ordering deterministic;
  • add fixtures for malformed, timed-out, and out-of-order responses;
  • map failures to meaningful exceptions;
  • never require users to understand internal payloads for normal browser work.

Go next