microsoft/playwright · error · Error
Function "${name}" has been already registered in one of the
Error message
Function "${name}" has been already registered in one of the pages What it means
Same exposeBinding flow, second guard: even if the context doesn't have the name, if any already-created page has a page-level binding with the same name, Playwright rejects it to prevent shadowing.
Source
Thrown at packages/playwright-core/src/server/browserContext.ts:382
this.bindingsInitScript = PageBinding.createInitScript(this);
this.initScripts.push(this.bindingsInitScript);
await this.doAddInitScript(this.bindingsInitScript);
await this.safeNonStallingEvaluateInAllFrames(this.bindingsInitScript.source, 'main');
})();
return await this._playwrightBindingExposed;
}
needsPlaywrightBinding(): boolean {
return this._playwrightBindingExposed !== undefined;
}
async exposeBinding(progress: Progress, name: string, playwrightBinding: frames.FunctionWithSource, forClient?: unknown, noGlobal?: boolean): Promise<PageBinding> {
if (this._pageBindings.has(name))
throw new Error(`Function "${name}" has been already registered`);
for (const page of this.pages()) {
if (page.getBinding(name))
throw new Error(`Function "${name}" has been already registered in one of the pages`);
}
await progress.race(this.exposePlaywrightBindingIfNeeded());
const binding = new PageBinding(this, name, playwrightBinding, noGlobal);
binding.forClient = forClient;
this._pageBindings.set(name, binding);
try {
await progress.race(this.doAddInitScript(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
- Use distinct names for page vs context bindings
- Expose at context level before creating pages, or stick to one level
- Dispose page-level bindings before promoting to context
Example fix
// before
await page.exposeBinding('cb', h);
await context.exposeBinding('cb', h); // throws
// after
await page.exposeBinding('pageCb', h);
await context.exposeBinding('ctxCb', h); Defensive patterns
Strategy: validation
Validate before calling
const ctxNames = new Set<string>();
const pageNames = new Set<string>();
async function exposeCtx(ctx: BrowserContext, name: string, fn: Function) {
if (ctxNames.has(name) || pageNames.has(name))
throw new Error(`Binding '${name}' already registered (page or context).`);
ctxNames.add(name);
await ctx.exposeBinding(name, fn as any);
} Prevention
- Don't mix page-level and context-level bindings with the same name
- Expose context bindings before creating pages
- Pick one level (page or context) and stay consistent
When it happens
Trigger: `page.exposeBinding('foo', ...)` followed by `context.exposeBinding('foo', ...)`; or any ordering where pages already exist with a colliding binding.
Common situations: Mixing page-level and context-level `exposeBinding`/`exposeFunction` with the same name; adding context bindings after pages were created; global setup registering context bindings after per-page setup ran.
Related errors
- Function "${name}" has been already registered
- Please use browser.newContext()
- page: expected Page or Frame
- Page has been closed.
- "deviceScaleFactor" option is not supported with null "viewp
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/465e9ec6d3f237de.
Report an issue: GitHub.