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

  1. Pass an actual Page (from browserContext.newPage()) or a Frame (from page.frames() / page.mainFrame() / childFrames()).
  2. For service/shared workers, attach to the worker target via the Browser CDP target API instead of newCDPSession.
  3. 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

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


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