microsoft/playwright · error · Error

Function "${name}" is not exposed

Error message

Function "${name}" is not exposed

What it means

Thrown by PageBinding.dispatch when the page sends a binding invocation for a name that no PageBinding is registered for. dispatch is the server-side receiver for calls to window.__playwright__binding__; if the binding was disposed, removed, or never installed under that name, Playwright cannot route the call and reports it as not exposed.

Source

Thrown at packages/playwright-core/src/server/page.ts:1084

  readonly initScript: InitScript;
  readonly cleanupScript: string;
  forClient?: unknown;

  constructor(parent: BrowserContext | Page, name: string, playwrightFunction: frames.FunctionWithSource, noGlobal?: boolean) {
    super(parent);
    this.name = name;
    this.playwrightFunction = playwrightFunction;
    this.initScript = new InitScript(parent, `globalThis['${kBindingsControllerProperty}'].addBinding(${JSON.stringify(name)}, ${!!noGlobal})`);
    this.cleanupScript = `globalThis['${kBindingsControllerProperty}'].removeBinding(${JSON.stringify(name)})`;
  }

  static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) {
    const { name, seq, serializedArgs } = JSON.parse(payload) as BindingPayload;
    try {
      assert(context.world);
      const binding = page.getBinding(name);
      if (!binding)
        throw new Error(`Function "${name}" is not exposed`);
      if (!Array.isArray(serializedArgs))
        throw new Error(`serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly`);
      const args = serializedArgs.map(a => parseEvaluationResultValue(a));
      const result = await binding.playwrightFunction({ frame: context.frame, page, context: page.browserContext }, ...args);
      context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, result }).catch(e => debugLogger.log('error', e));
    } catch (error) {
      context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, error }).catch(e => debugLogger.log('error', e));
    }
  }

  override async dispose(): Promise<void> {
    await this.parent.removeExposedBinding(this);
  }
}

export class InitScript extends DisposableObject {
  readonly source: string;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Keep the binding registered for as long as the page might call it; do not dispose the handle until the page is closed or navigated to a state that no longer uses it.
  2. Re-expose the function (page.exposeFunction) after navigation/disposal if the page still needs it.
  3. Avoid calling the internal bindings controller directly from page code; use the exposed function reference returned by exposeFunction.

Example fix

// before
const handle = await page.exposeFunction('cb', fn);
await handle[Symbol.asyncDispose]();
await page.evaluate(() => window.cb()); // dispatch finds no binding
// after
await page.evaluate(() => window.cb());
await handle[Symbol.asyncDispose](); // dispose only after the page is done
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the binding is (still) registered before the page can call it
const exposed = new Set<string>();
const handle = await page.exposeFunction('cb', fn); exposed.add('cb');
// before disposing in teardown:
if (exposed.has('cb')) { await handle[Symbol.asyncDispose](); exposed.delete('cb'); }

Prevention

When it happens

Trigger: Page-side code invokes an exposed function after the binding was disposed (e.g. the test disposed the handle but the page still holds a reference); a binding removed via cleanupScript races with an in-flight call; page code manually triggers the binding controller with an arbitrary name.

Common situations: Disposing a page.exposeFunction handle while the page still calls it asynchronously; navigating away and back so init scripts are re-evaluated but the server-side binding map was cleared; manual/low-level use of the bindings controller.

Related errors


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