From ce1082ec9fe917dbdeb1965fa0eb0a8325f619b5 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 6 Oct 2025 15:16:01 +0200 Subject: [PATCH] fix: `exposeBinding` should work in parallel (#37721) --- .../src/server/browserContext.ts | 22 +++++++++---------- tests/page/page-expose-function.spec.ts | 11 ++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 1866e05a21..9edfd3a43f 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -93,7 +93,7 @@ export abstract class BrowserContext extends SdkObject { _closeReason: string | undefined; readonly clock: Clock; _clientCertificatesProxy: ClientCertificatesProxy | undefined; - private _playwrightBindingExposed = false; + private _playwrightBindingExposed?: Promise; readonly dialogManager: DialogManager; constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { @@ -304,19 +304,19 @@ export abstract class BrowserContext extends SdkObject { } async exposePlaywrightBindingIfNeeded() { - if (this._playwrightBindingExposed) - return; - this._playwrightBindingExposed = true; - await this.doExposePlaywrightBinding(); + this._playwrightBindingExposed ??= (async () => { + await this.doExposePlaywrightBinding(); - this.bindingsInitScript = PageBinding.createInitScript(); - this.initScripts.push(this.bindingsInitScript); - await this.doAddInitScript(this.bindingsInitScript); - await this.safeNonStallingEvaluateInAllFrames(this.bindingsInitScript.source, 'main'); + this.bindingsInitScript = PageBinding.createInitScript(); + this.initScripts.push(this.bindingsInitScript); + await this.doAddInitScript(this.bindingsInitScript); + await this.safeNonStallingEvaluateInAllFrames(this.bindingsInitScript.source, 'main'); + })(); + return await this._playwrightBindingExposed; } - needsPlaywrightBinding() { - return this._playwrightBindingExposed; + needsPlaywrightBinding(): boolean { + return this._playwrightBindingExposed !== undefined; } async exposeBinding(progress: Progress, name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource, forClient?: unknown): Promise { diff --git a/tests/page/page-expose-function.spec.ts b/tests/page/page-expose-function.spec.ts index e9334c4df0..8bafdff2b3 100644 --- a/tests/page/page-expose-function.spec.ts +++ b/tests/page/page-expose-function.spec.ts @@ -292,3 +292,14 @@ it('should fail with busted Array.prototype.toJSON', async ({ page }) => { expect.soft(await page.evaluate(() => ([] as any).toJSON())).toBe('"[]"'); }); + +it('exposeBinding should work in parallel', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/37712' } }, async ({ page }) => { + await Promise.all([ + page.exposeBinding('foo', () => 42), + page.exposeBinding('bar', () => 42), + ]); + await page.evaluate(() => { + (window as any).foo(); + (window as any).bar(); + }); +});