{"record":{"id":"9821ca612957e4dd","repo":"microsoft/playwright","slug":"function-name-is-not-exposed","errorCode":null,"errorMessage":"Function \"${name}\" is not exposed","messagePattern":"Function \"(.+?)\" is not exposed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/page.ts","lineNumber":1084,"sourceCode":"  readonly initScript: InitScript;\n  readonly cleanupScript: string;\n  forClient?: unknown;\n\n  constructor(parent: BrowserContext | Page, name: string, playwrightFunction: frames.FunctionWithSource, noGlobal?: boolean) {\n    super(parent);\n    this.name = name;\n    this.playwrightFunction = playwrightFunction;\n    this.initScript = new InitScript(parent, `globalThis['${kBindingsControllerProperty}'].addBinding(${JSON.stringify(name)}, ${!!noGlobal})`);\n    this.cleanupScript = `globalThis['${kBindingsControllerProperty}'].removeBinding(${JSON.stringify(name)})`;\n  }\n\n  static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) {\n    const { name, seq, serializedArgs } = JSON.parse(payload) as BindingPayload;\n    try {\n      assert(context.world);\n      const binding = page.getBinding(name);\n      if (!binding)\n        throw new Error(`Function \"${name}\" is not exposed`);\n      if (!Array.isArray(serializedArgs))\n        throw new Error(`serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly`);\n      const args = serializedArgs.map(a => parseEvaluationResultValue(a));\n      const result = await binding.playwrightFunction({ frame: context.frame, page, context: page.browserContext }, ...args);\n      context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, result }).catch(e => debugLogger.log('error', e));\n    } catch (error) {\n      context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, error }).catch(e => debugLogger.log('error', e));\n    }\n  }\n\n  override async dispose(): Promise<void> {\n    await this.parent.removeExposedBinding(this);\n  }\n}\n\nexport class InitScript extends DisposableObject {\n  readonly source: string;\n","sourceCodeStart":1066,"sourceCodeEnd":1102,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/page.ts#L1066-L1102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Re-expose the function (page.exposeFunction) after navigation/disposal if the page still needs it.","Avoid calling the internal bindings controller directly from page code; use the exposed function reference returned by exposeFunction."],"exampleFix":"// before\nconst handle = await page.exposeFunction('cb', fn);\nawait handle[Symbol.asyncDispose]();\nawait page.evaluate(() => window.cb()); // dispatch finds no binding\n// after\nawait page.evaluate(() => window.cb());\nawait handle[Symbol.asyncDispose](); // dispose only after the page is done","handlingStrategy":"validation","validationCode":"// Ensure the binding is (still) registered before the page can call it\nconst exposed = new Set<string>();\nconst handle = await page.exposeFunction('cb', fn); exposed.add('cb');\n// before disposing in teardown:\nif (exposed.has('cb')) { await handle[Symbol.asyncDispose](); exposed.delete('cb'); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep bindings registered for as long as the page might invoke them.","Do not dispose exposeFunction handles until the page is closed or navigated away from code that uses them.","Avoid invoking the internal bindings controller directly from page code."],"tags":["bindings","expose-function","dispatch","lifecycle"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}