From 8f056fbbcebe4dd1f1dcedebb43870b81cc475d1 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 5 Dec 2023 10:26:17 -0800 Subject: [PATCH] chore: do not wait for route on close if route.continue() threw an error (#28487) Reference https://github.com/microsoft/playwright/issues/23781 --- .../playwright-core/src/client/network.ts | 58 +++++++++++++------ tests/page/page-request-continue.spec.ts | 2 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 8aefd3dce7..2d3a51943d 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -286,6 +286,7 @@ export class Request extends ChannelOwner implements ap export class Route extends ChannelOwner implements api.Route { private _handlingPromise: ManualPromise | null = null; _context!: BrowserContext; + _didThrow: boolean = false; static from(route: channels.RouteChannel): Route { return (route as any)._object; @@ -318,15 +319,15 @@ export class Route extends ChannelOwner implements api.Ro } async abort(errorCode?: string) { - this._checkNotHandled(); - await this._raceWithTargetClose(this._channel.abort({ requestUrl: this.request()._initializer.url, errorCode })); - this._reportHandled(true); + await this._handleRoute(async () => { + await this._raceWithTargetClose(this._channel.abort({ requestUrl: this.request()._initializer.url, errorCode })); + }); } async _redirectNavigationRequest(url: string) { - this._checkNotHandled(); - await this._raceWithTargetClose(this._channel.redirectNavigationRequest({ url })); - this._reportHandled(true); + await this._handleRoute(async () => { + await this._raceWithTargetClose(this._channel.redirectNavigationRequest({ url })); + }); } async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise { @@ -336,13 +337,24 @@ export class Route extends ChannelOwner implements api.Ro } async fulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, json?: any, path?: string } = {}) { - this._checkNotHandled(); - await this._wrapApiCall(async () => { - await this._innerFulfill(options); - this._reportHandled(true); + await this._handleRoute(async () => { + await this._wrapApiCall(async () => { + await this._innerFulfill(options); + }); }); } + private async _handleRoute(callback: () => Promise) { + this._checkNotHandled(); + try { + await callback(); + this._reportHandled(true); + } catch (e) { + this._didThrow = true; + throw e; + } + } + private async _innerFulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, json?: any, path?: string } = {}): Promise { let fetchResponseUid; let { status: statusOption, headers: headersOption, body } = options; @@ -402,10 +414,10 @@ export class Route extends ChannelOwner implements api.Ro } async continue(options: FallbackOverrides = {}) { - this._checkNotHandled(); - this.request()._applyFallbackOverrides(options); - await this._innerContinue(); - this._reportHandled(true); + await this._handleRoute(async () => { + this.request()._applyFallbackOverrides(options); + await this._innerContinue(); + }); } _checkNotHandled() { @@ -636,7 +648,7 @@ export class RouteHandler { readonly url: URLMatch; readonly handler: RouteHandlerCallback; readonly noWaitOnUnrouteOrClose: boolean; - private _activeInvocations: MultiMap }> = new MultiMap(); + private _activeInvocations: MultiMap, route: Route }> = new MultiMap(); constructor(baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times: number = Number.MAX_SAFE_INTEGER, noWaitOnUnrouteOrClose: boolean = false) { this._baseURL = baseURL; @@ -668,7 +680,7 @@ export class RouteHandler { public async handle(route: Route): Promise { const page = route.request()._safePage(); - const handlerInvocation = { ignoreException: false, complete: new ManualPromise() } ; + const handlerInvocation = { ignoreException: false, complete: new ManualPromise(), route } ; this._activeInvocations.set(page, handlerInvocation); try { return await this._handleInternal(route); @@ -691,10 +703,18 @@ export class RouteHandler { // Note that context.route handler may be later invoked on a different page, // so we only swallow errors for the current page's routes. const handlerActivations = page ? this._activeInvocations.get(page) : [...this._activeInvocations.values()]; - if (this.noWaitOnUnrouteOrClose || noWait) + if (this.noWaitOnUnrouteOrClose || noWait) { handlerActivations.forEach(h => h.ignoreException = true); - else - await Promise.all(handlerActivations.map(h => h.complete)); + } else { + const promises = []; + for (const activation of handlerActivations) { + if (activation.route._didThrow) + activation.ignoreException = true; + else + promises.push(activation.complete); + } + await Promise.all(promises); + } } private async _handleInternal(route: Route): Promise { diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index c641b7ca9e..56a58ec630 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -108,7 +108,7 @@ it('should not allow changing protocol when overriding url', async ({ page, serv } catch (e) { resolve(e); } - }, { noWaitForFinish: true }); + }); page.goto(server.EMPTY_PAGE).catch(() => {}); const error = await errorPromise; expect(error).toBeTruthy();