microsoft/playwright · error · Error
CDP session must be initiated with either Page or Frame, not
Error message
CDP session must be initiated with either Page or Frame, not none or both
What it means
Thrown by BrowserContextDispatcher.newCDPSession() when the params do not contain exactly one of page or frame. The server requires precisely one target handle: either neither is set (no target) or both are set (ambiguous) is rejected. The public client API always passes a single Page, so this fires mainly from low-level protocol misuse or custom clients.
Source
Thrown at packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts:380
async disableRecorder(params: channels.BrowserContextDisableRecorderParams, progress: Progress): Promise<void> {
const recorder = await progress.race(Recorder.existingForContext(this._context));
if (recorder)
await progress.race(recorder.setMode('none'));
}
async exposeConsoleApi(params: channels.BrowserContextExposeConsoleApiParams, progress: Progress): Promise<void> {
await this._context.exposeConsoleApi(progress);
}
async pause(params: channels.BrowserContextPauseParams, progress: Progress) {
// Debugger will take care of this.
}
async newCDPSession(params: channels.BrowserContextNewCDPSessionParams, progress: Progress): Promise<channels.BrowserContextNewCDPSessionResult> {
if (this._object._browser.options.browserType !== 'chromium')
throw new Error(`CDP session is only available in Chromium`);
if (!params.page && !params.frame || params.page && params.frame)
throw new Error(`CDP session must be initiated with either Page or Frame, not none or both`);
const crBrowserContext = this._object as CRBrowserContext;
return { session: new CDPSessionDispatcher(this, await progress.race(crBrowserContext.newCDPSession((params.page ? params.page as PageDispatcher : params.frame as FrameDispatcher)._object))) };
}
async clockFastForward(params: channels.BrowserContextClockFastForwardParams, progress: Progress): Promise<channels.BrowserContextClockFastForwardResult> {
await progress.race(this._context.clock.fastForward(params.ticksString ?? params.ticksNumber ?? 0));
}
async clockInstall(params: channels.BrowserContextClockInstallParams, progress: Progress): Promise<channels.BrowserContextClockInstallResult> {
await progress.race(this._context.clock.install(params.timeString ?? params.timeNumber ?? undefined));
}
async clockPauseAt(params: channels.BrowserContextClockPauseAtParams, progress: Progress): Promise<channels.BrowserContextClockPauseAtResult> {
await progress.race(this._context.clock.pauseAt(params.timeString ?? params.timeNumber ?? 0));
this._clockPaused = true;
}
async clockResume(params: channels.BrowserContextClockResumeParams, progress: Progress): Promise<channels.BrowserContextClockResumeResult> {View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Pass exactly one target: use the public context.newCDPSession(page) helper which always sends page and omits frame.
- If building params manually, set either params.page OR params.frame, never both, and ensure at least one is present.
- Add an assertion in your call site: assert((page ? 1 : 0) + (frame ? 1 : 0) === 1).
Example fix
// before: ambiguous or empty target
await connection.send('browserContext', 'newCDPSession', { context }); // no page/frame
// after: exactly one target handle
await context.newCDPSession(page); Defensive patterns
Strategy: validation
Validate before calling
// Ensure exactly one of page/frame is supplied.
function validateCdpTarget(params: { page?: any; frame?: any }) {
const count = (params.page ? 1 : 0) + (params.frame ? 1 : 0);
if (count !== 1) throw new Error('newCDPSession needs exactly one of page or frame');
} Prevention
- Use the typed client helper context.newCDPSession(page) instead of building params by hand.
- If you must build params, assert exactly one target field is set before sending.
- Avoid conditional code that can populate both page and frame simultaneously.
When it happens
Trigger: Sending the newCDPSession RPC with both params.page and params.frame populated, or with neither. Happens with hand-rolled protocol clients, buggy serializers that duplicate a handle into both fields, or code that builds the params object conditionally and ends up with both branches setting a value.
Common situations: Custom transport/protocol clients built on playwright-core; rare in normal usage because the typed client API (page -> newCDPSession) always supplies exactly one target.
Related errors
- CDP session is only available in Chromium
- ${path}: expected channel ${names.toString()}
- Object with guid ${arg.guid} was not bound in the connection
- ${path}: expected channel ${names.toString()}
- CDP connections are only supported by Chromium
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/accac594d5bbaa1e.
Report an issue: GitHub.