microsoft/playwright · error · Error
Function "${name}" has been already registered
Error message
Function "${name}" has been already registered What it means
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.
Source
Thrown at packages/playwright-core/src/server/page.ts:355
const fileChooser = new FileChooser(handle, multiple);
this.emit(Page.Events.FileChooser, fileChooser);
}
opener(): Page | undefined {
return this._opener;
}
mainFrame(): frames.Frame {
return this.frameManager.mainFrame();
}
frames(): frames.Frame[] {
return this.frameManager.frames();
}
async exposeBinding(progress: Progress, name: string, playwrightBinding: frames.FunctionWithSource, noGlobal?: boolean): Promise<PageBinding> {
if (this._pageBindings.has(name))
throw new Error(`Function "${name}" has been already registered`);
if (this.browserContext._pageBindings.has(name))
throw new Error(`Function "${name}" has been already registered in the browser context`);
await progress.race(this.browserContext.exposePlaywrightBindingIfNeeded());
const binding = new PageBinding(this, name, playwrightBinding, noGlobal);
this._pageBindings.set(name, binding);
try {
await progress.race(this.delegate.addInitScript(binding.initScript));
await progress.race(this.safeNonStallingEvaluateInAllFrames(binding.initScript.source, 'main'));
return binding;
} catch (error) {
this._pageBindings.delete(name);
throw error;
}
}
async removeExposedBinding(binding: PageBinding) {
if (this._pageBindings.get(binding.name) !== binding)
return;View on GitHub (pinned to c8fc3bf8d3)
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.
Example fix
// before
for (const t of tests) {
await page.exposeFunction('getValue', () => 1); // throws on 2nd iteration
}
// after
await context.exposeFunction('getValue', () => 1); // once per context
// or dispose the returned handle
const handle = await page.exposeFunction('getValue', () => 1);
await handle[Symbol.asyncDispose](); // before re-registering Defensive patterns
Strategy: validation
Validate before calling
// Before re-exposing, check the page has no such binding by tracking your own set
const exposed = new Set<string>();
async function exposeOnce(page, name, fn) {
if (exposed.has(name))
throw new Error(`'${name}' already exposed; dispose it first or use a unique name`);
const handle = await page.exposeFunction(name, fn);
exposed.add(name);
handle[Symbol.asyncDispose] && await handle[Symbol.asyncDispose]().then(() => exposed.delete(name));
return handle;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Function "${name}" has been already registered in the browse
- Function "${name}" is not exposed
- serializedArgs is not an array. This can happen when Array.p
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/7f61c9a5d13d5074.
Report an issue: GitHub.