{"record":{"id":"7f61c9a5d13d5074","repo":"microsoft/playwright","slug":"function-name-has-been-already-registered-7f61c9","errorCode":null,"errorMessage":"Function \"${name}\" has been already registered","messagePattern":"Function \"(.+?)\" has been already registered","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/page.ts","lineNumber":355,"sourceCode":"    const fileChooser = new FileChooser(handle, multiple);\n    this.emit(Page.Events.FileChooser, fileChooser);\n  }\n\n  opener(): Page | undefined {\n    return this._opener;\n  }\n\n  mainFrame(): frames.Frame {\n    return this.frameManager.mainFrame();\n  }\n\n  frames(): frames.Frame[] {\n    return this.frameManager.frames();\n  }\n\n  async exposeBinding(progress: Progress, name: string, playwrightBinding: frames.FunctionWithSource, noGlobal?: boolean): Promise<PageBinding> {\n    if (this._pageBindings.has(name))\n      throw new Error(`Function \"${name}\" has been already registered`);\n    if (this.browserContext._pageBindings.has(name))\n      throw new Error(`Function \"${name}\" has been already registered in the browser context`);\n    await progress.race(this.browserContext.exposePlaywrightBindingIfNeeded());\n    const binding = new PageBinding(this, name, playwrightBinding, noGlobal);\n    this._pageBindings.set(name, binding);\n    try {\n      await progress.race(this.delegate.addInitScript(binding.initScript));\n      await progress.race(this.safeNonStallingEvaluateInAllFrames(binding.initScript.source, 'main'));\n      return binding;\n    } catch (error) {\n      this._pageBindings.delete(name);\n      throw error;\n    }\n  }\n\n  async removeExposedBinding(binding: PageBinding) {\n    if (this._pageBindings.get(binding.name) !== binding)\n      return;","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/page.ts#L337-L373","documentation":"Thrown by Page.exposeBinding when a page-level binding with the same name has already been registered on this Page. Playwright forbids two bindings sharing a name because each one is installed as a global function on the page, so a duplicate would silently overwrite the earlier callback. The check happens before any init script is injected, so the second call fails fast.","triggerScenarios":"Calling page.exposeFunction('foo', fn) or page.exposeBinding('foo', fn) twice on the same Page object with the identical name; calling page.exposeBinding then page.exposeFunction with the same name; re-exposing inside a beforeEach hook without scoping the binding to the page lifetime.","commonSituations":"Test suites that re-run setup per test without creating a fresh page; helpers that wrap exposeFunction and are invoked more than once; refactoring that moved exposeFunction into a loop or a reused fixture.","solutions":["Give each binding a unique name, or dispose the previous binding (the Disposable returned by exposeFunction/exposeBinding) before re-registering.","Move page.exposeFunction to a once-per-page location (e.g. before page.goto, or in a fixture that runs once when the page is created).","Expose the function at the BrowserContext level (browserContext.exposeFunction) once, so every page in the context inherits it without per-page re-registration."],"exampleFix":"// before\nfor (const t of tests) {\n  await page.exposeFunction('getValue', () => 1); // throws on 2nd iteration\n}\n// after\nawait context.exposeFunction('getValue', () => 1); // once per context\n// or dispose the returned handle\nconst handle = await page.exposeFunction('getValue', () => 1);\nawait handle[Symbol.asyncDispose](); // before re-registering","handlingStrategy":"validation","validationCode":"// Before re-exposing, check the page has no such binding by tracking your own set\nconst exposed = new Set<string>();\nasync function exposeOnce(page, name, fn) {\n  if (exposed.has(name))\n    throw new Error(`'${name}' already exposed; dispose it first or use a unique name`);\n  const handle = await page.exposeFunction(name, fn);\n  exposed.add(name);\n  handle[Symbol.asyncDispose] && await handle[Symbol.asyncDispose]().then(() => exposed.delete(name));\n  return handle;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register bindings once per page/context lifetime, not per test iteration.","Prefer BrowserContext.exposeFunction for app-wide helpers so pages inherit them.","Track exposed names in a set and dispose handles before re-registering."],"tags":["bindings","expose-function","page","duplicate-name"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}