diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 6f9894eac4fa1..921d9c1ba2517 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -70,6 +70,7 @@ export abstract class Browser extends SdkObject { private _startedClosing = false; readonly _idToVideo = new Map(); private _contextForReuse: { context: BrowserContext, hash: string } | undefined; + private _contextForReuseQueue = Promise.resolve(); _closeReason: string | undefined; _isCollocatedWithServer: boolean = true; @@ -116,20 +117,24 @@ export abstract class Browser extends SdkObject { } } - async newContextForReuse(progress: Progress, params: channels.BrowserNewContextForReuseParams): Promise { - const hash = BrowserContext.reusableContextHash(params); - if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) { - if (this._contextForReuse) - await this._contextForReuse.context.close({ reason: 'Context reused' }); - this._contextForReuse = { context: await this.newContext(progress, params), hash }; - return this._contextForReuse.context; - } - await this._contextForReuse.context.resetForReuse(progress, params); - return this._contextForReuse.context; + private _queueContextForReuseOperation(op: () => Promise): Promise { + const promise = this._contextForReuseQueue.then(() => op()); + this._contextForReuseQueue = promise.then(() => {}, () => {}); + return promise; } - contextForReuse() { - return this._contextForReuse?.context; + async newContextForReuse(progress: Progress, params: channels.BrowserNewContextForReuseParams): Promise { + return await this._queueContextForReuseOperation(async () => { + const hash = BrowserContext.reusableContextHash(params); + if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) { + if (this._contextForReuse) + await this._contextForReuse.context.close({ reason: 'Context reused' }); + this._contextForReuse = { context: await this.newContext(progress, params), hash }; + return this._contextForReuse.context; + } + await this._contextForReuse.context.resetForReuse(progress, params); + return this._contextForReuse.context; + }); } _downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) { diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index bda9e1f42650d..e200ba0f6f0ae 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -38,6 +38,7 @@ export class BrowserDispatcher extends Dispatcher(); + private _reusedContext: BrowserContextDispatcher | undefined; constructor(scope: BrowserTypeDispatcher, browser: Browser, options: BrowserDispatcherOptions = {}) { super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name }); @@ -78,17 +79,16 @@ export class BrowserDispatcher extends Dispatcher { const context = await this._object.newContextForReuse(progress, params); - const contextDispatcher = BrowserContextDispatcher.from(this, context); - this._dispatchEvent('context', { context: contextDispatcher }); - return { context: contextDispatcher }; + this._reusedContext = BrowserContextDispatcher.from(this, context); + this._dispatchEvent('context', { context: this._reusedContext }); + return { context: this._reusedContext }; } async disconnectFromReusedContext(params: channels.BrowserDisconnectFromReusedContextParams, progress: Progress): Promise { - const context = this._object.contextForReuse(); - const contextDispatcher = context ? this.connection.existingDispatcher(context) : undefined; - if (contextDispatcher) { - await contextDispatcher.stopPendingOperations(new Error(params.reason)); - contextDispatcher._dispose(); + if (this._reusedContext) { + await this._reusedContext.stopPendingOperations(new Error(params.reason)); + this._reusedContext._dispose(); + this._reusedContext = undefined; } }