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

  1. Pass exactly one target: use the public context.newCDPSession(page) helper which always sends page and omits frame.
  2. If building params manually, set either params.page OR params.frame, never both, and ensure at least one is present.
  3. 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

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


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