microsoft/playwright · error · Error
page: expected Page or Frame
Error message
page: expected Page or Frame
What it means
Thrown by Chromium's newCDPSession(page) when the argument is neither a Page nor a Frame instance. The method (crBrowser.ts:609) is the implementation of the chromium-only CDP bridge BrowserContext.newCDPSession; the else branch fires only when the runtime type check fails. The same call also rejects frames without their own CDP session, but with a different message.
Source
Thrown at packages/playwright-core/src/server/chromium/crBrowser.ts:609
browserContextId: this._browserContextId,
});
}
serviceWorkers(): CRServiceWorker[] {
return Array.from(this._browser._serviceWorkers.values()).filter(serviceWorker => serviceWorker.browserContext === this);
}
async newCDPSession(page: Page | Frame): Promise<CDPSession> {
let targetId: string | null = null;
if (page instanceof Page) {
targetId = (page.delegate as CRPage)._targetId;
} else if (page instanceof Frame) {
const session = (page._page.delegate as CRPage)._sessions.get(page._id);
if (!session)
throw new Error(`This frame does not have a separate CDP session, it is a part of the parent frame's session`);
targetId = session._targetId;
} else {
throw new Error('page: expected Page or Frame');
}
const rootSession = await this._browser._clientRootSession();
return rootSession.attachToTarget(targetId);
}
}
export function shouldProxyLoopback(bypass: string | undefined) {
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK)
return false;
const hosts = (bypass || '').split(',').map(s => s.trim());
const shouldBypassSomeLoopback = ['localhost', '127.0.0.1', '::1', '[::]', '[::1]', '<loopback>', '<-loopback>'].some(host => hosts.includes(host));
return !shouldBypassSomeLoopback;
}
View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Pass an actual Page (from browserContext.newPage()) or a Frame (from page.frames() / page.mainFrame() / childFrames()).
- For service/shared workers, attach to the worker target via the Browser CDP target API instead of newCDPSession.
- If you hold a value of unknown shape, narrow it with instanceof Page / instanceof Frame before calling.
Example fix
// before const session = await context.newCDPSession(worker); // after const session = await context.newCDPSession(page);
Defensive patterns
Strategy: type-guard
Validate before calling
// Validate before opening a CDP session
import { Page, Frame } from 'playwright';
function isPageOrFrame(v: unknown): v is Page | Frame {
return v instanceof Page || v instanceof Frame;
}
if (!isPageOrFrame(target))
throw new TypeError('newCDPSession expects a Page or Frame');
const session = await context.newCDPSession(target); Type guard
function isPageOrFrame(v: unknown): v is Page | Frame {
return v instanceof Page || v instanceof Frame;
} Prevention
- Type the parameter at the API boundary as Page | Frame so TS rejects Worker/BrowserContext at compile time.
- Never pass values from page.workers() to newCDPSession.
- If you handle unknown values, narrow with instanceof before calling.
When it happens
Trigger: Calling browser.newCDPSession(worker) (a Worker, not a Page/Frame); passing a BrowserContext, JSHandle, ElementHandle, or a plain/stub object; passing a Frame belonging to a different browser instance.
Common situations: Trying to attach a CDP session to a WebWorker (workers use a separate target attachment path); test doubles/mocks substituting for Page; cross-browser frame references after context close.
Related errors
- CDP session is only available in Chromium
- Tracing is only available in Chromium
- Could not connect to ${channel}.\n${remoteDebuggingHint(chan
- Playwright manages remote debugging connection itself.
- Connecting to ${channel} by channel name is not supported on
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/43646d965c5f10b5.
Report an issue: GitHub.