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
- Verify the proxy: `curl http://<host>:<port>/json` should return a JSON tab array.
- Start/restart ios_webkit_debug_proxy and confirm the device is trusted and connected.
- 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
- Start ios_webkit_debug_proxy before connecting and confirm `curl <base>/json` returns JSON.
- Point at the correct port (commonly 27753).
- Keep the device trusted and unlocked so the proxy stays healthy.
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
- SyncServer: failed to bind HTTP server
- No Mobile Safari tabs found at ${this._proxyBase}/json — ope
- Not supported
- Socks4 proxy protocol does not support authentication
- Browser does not support socks5 proxy authentication
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/efc92fff6af972f8.
Report an issue: GitHub.