microsoft/playwright · error · Error

ios_webkit_debug_proxy ${proxyBase}/json returned ${res.stat

Error message

ios_webkit_debug_proxy ${proxyBase}/json returned ${res.status}

What it means

When connecting to Mobile Safari via ios_webkit_debug_proxy, Playwright fetches `${proxyBase}/json` to list debuggable tabs. If that HTTP endpoint returns a non-OK status (any non-2xx), listing aborts. The proxy must be reachable and healthy, and it must be the ios_webkit_debug_proxy endpoint (or a compatible CDP-over-HTTP gateway), not an arbitrary URL.

Source

Thrown at packages/playwright-core/src/server/webkit/webview/wvBrowser.ts:69

};

function deriveProxyBase(endpointURL: string): string {
  const u = new URL(endpointURL);
  if (u.protocol === 'http:' || u.protocol === 'https:')
    return `${u.protocol}//${u.host}`;
  const httpProto = u.protocol === 'wss:' ? 'https:' : 'http:';
  return `${httpProto}//${u.host}`;
}

function pageIdFromWsUrl(wsUrl: string): string {
  const m = /\/devtools\/page\/([^/]+)/.exec(wsUrl);
  return m ? m[1] : wsUrl;
}

async function listTabs(proxyBase: string, headers: { [key: string]: string }): Promise<ProxyTab[]> {
  const res = await fetch(`${proxyBase}/json`, { headers });
  if (!res.ok)
    throw new Error(`ios_webkit_debug_proxy ${proxyBase}/json returned ${res.status}`);
  const data = await res.json() as ProxyTab[];
  return data.filter(t => !!t.webSocketDebuggerUrl);
}

// Local WebSocket-backed transport: defers opening the socket until `open()`
// is called, so listeners on `onmessage` are wired before the remote side
// starts emitting events.
class DeferredWebSocketTransport implements ConnectOverCDPTransport {
  private readonly _url: string;
  private readonly _headers: { [key: string]: string };
  private _ws: ws | undefined;
  private _closed = false;

  onmessage?: (message: object) => void;
  onclose?: (reason?: string) => void;

  constructor(url: string, headers: { [key: string]: string }) {
    this._url = url;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Verify the proxy: `curl http://<host>:<port>/json` should return a JSON tab array.
  2. Start/restart ios_webkit_debug_proxy and confirm the device is trusted and connected.
  3. Pass the correct base URL (scheme + host + port) — Playwright derives proxyBase from your endpoint.

Example fix

// before
const browser = await webkit.connectOverCDP('http://localhost:9222'); // wrong port

// after
// first: curl http://localhost:27753/json  (ios_webkit_debug_proxy default)
const browser = await webkit.connectOverCDP('http://localhost:27753');
Defensive patterns

Strategy: validation

Validate before calling

async function checkProxy(base) {
  const res = await fetch(`${base}/json`);
  if (!res.ok) throw new Error(`ios_webkit_debug_proxy unhealthy (${res.status}) at ${base}`);
  return res.json();
}
await checkProxy(endpointBase);

Try / catch

try { browser = await webkit.connectOverCDP(base); }
catch (e) {
  if (/\/json returned \d+/.test(String(e.message))) {
    throw new Error(`Proxy unhealthy — restart ios_webkit_debug_proxy at ${base}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling connectOverCDP with a webview/Safari endpoint whose /json returns an error (404, 500, 502); pointing at a wrong port (another dev server); the proxy crashed or isn't ios_webkit_debug_proxy; auth required and not supplied via headers.

Common situations: Wrong port for ios_webkit_debug_proxy; proxy not started; device disconnected so proxy returns an error; pointing at a Chrome DevTools /json endpoint that behaves differently.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/efc92fff6af972f8. Report an issue: GitHub.