microsoft/playwright · error · Error

Function " " 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.

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@f1d33b5029 (2026-08-12). Data as JSON: /api/errors/9821ca612957e4dd. Report an issue: GitHub.

Appendix: source

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

  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 f1d33b5029)