From 9cf068ad061f8b134dae98c8f000fe7b1b5214f0 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 13 Jun 2022 16:56:16 -0800 Subject: [PATCH] feat(fallback): allow falling back w/ overrides (#14849) --- docs/src/api/class-route.md | 169 +++++++++++- .../src/client/browserContext.ts | 2 +- .../playwright-core/src/client/network.ts | 59 ++++- packages/playwright-core/types/types.d.ts | 67 ++++- tests/library/defaultbrowsercontext-2.spec.ts | 32 --- tests/page/page-request-continue.spec.ts | 5 +- tests/page/page-request-fallback.spec.ts | 245 ++++++++++++++++++ tests/page/page-route.spec.ts | 80 +----- 8 files changed, 516 insertions(+), 143 deletions(-) create mode 100644 tests/page/page-request-fallback.spec.ts diff --git a/docs/src/api/class-route.md b/docs/src/api/class-route.md index bebadc7b83..3ed25b7106 100644 --- a/docs/src/api/class-route.md +++ b/docs/src/api/class-route.md @@ -42,8 +42,8 @@ await page.route('**/*', (route, request) => { // Override headers const headers = { ...request.headers(), - foo: 'bar', // set "foo" header - origin: undefined, // remove "origin" header + foo: 'foo-value', // set "foo" header + bar: undefined, // remove "bar" header }; route.continue({headers}); }); @@ -53,8 +53,8 @@ await page.route('**/*', (route, request) => { page.route("**/*", route -> { // Override headers Map headers = new HashMap<>(route.request().headers()); - headers.put("foo", "bar"); // set "foo" header - headers.remove("origin"); // remove "origin" header + headers.put("foo", "foo-value"); // set "foo" header + headers.remove("bar"); // remove "bar" header route.resume(new Route.ResumeOptions().setHeaders(headers)); }); ``` @@ -64,8 +64,8 @@ async def handle(route, request): # override headers headers = { **request.headers, - "foo": "bar" # set "foo" header - "origin": None # remove "origin" header + "foo": "foo-value" # set "foo" header + "bar": None # remove "bar" header } await route.continue_(headers=headers) } @@ -77,8 +77,8 @@ def handle(route, request): # override headers headers = { **request.headers, - "foo": "bar" # set "foo" header - "origin": None # remove "origin" header + "foo": "foo-value" # set "foo" header + "bar": None # remove "bar" header } route.continue_(headers=headers) } @@ -116,9 +116,75 @@ If set changes the request HTTP headers. Header values will be converted to a st ## async method: Route.fallback -Proceeds to the next registered route in the route chain. If no more routes are -registered, continues the request as is. This allows registering multiple routes -with the same mask and falling back from one to another. +When several routes match the given pattern, they run in the order opposite to their registration. +That way the last registered route can always override all the previos ones. In the example below, +request will be handled by the bottom-most handler first, then it'll fall back to the previous one and +in the end will be aborted by the first registered route. + +```js +await page.route('**/*', route => { + // Runs last. + route.abort(); +}); +await page.route('**/*', route => { + // Runs second. + route.fallback(); +}); +await page.route('**/*', route => { + // Runs first. + route.fallback(); +}); +``` + +```java +page.route("**/*", route -> { + // Runs last. + route.abort(); +}); + +page.route("**/*", route -> { + // Runs second. + route.fallback(); +}); + +page.route("**/*", route -> { + // Runs first. + route.fallback(); +}); +``` + +```python async +await page.route("**/*", lambda route: route.abort()) # Runs last. +await page.route("**/*", lambda route: route.fallback()) # Runs second. +await page.route("**/*", lambda route: route.fallback()) # Runs first. +``` + +```python sync +page.route("**/*", lambda route: route.abort()) # Runs last. +page.route("**/*", lambda route: route.fallback()) # Runs second. +page.route("**/*", lambda route: route.fallback()) # Runs first. +``` + +```csharp +await page.RouteAsync("**/*", route => { + // Runs last. + await route.AbortAsync(); +}); + +await page.RouteAsync("**/*", route => { + // Runs second. + await route.FallbackAsync(); +}); + +await page.RouteAsync("**/*", route => { + // Runs first. + await route.FallbackAsync(); +}); +``` + +Registering multiple routes is useful when you want separate handlers to +handle different kinds of requests, for example API calls vs page resources or +GET requests vs POST requests as in the example below. ```js // Handle GET requests. @@ -228,6 +294,87 @@ await page.RouteAsync("**/*", route => { }); ``` +One can also modify request while falling back to the subsequent handler, that way intermediate +route handler can modify url, method, headers and postData of the request. + +```js +await page.route('**/*', (route, request) => { + // Override headers + const headers = { + ...request.headers(), + foo: 'foo-value', // set "foo" header + bar: undefined, // remove "bar" header + }; + route.fallback({headers}); +}); +``` + +```java +page.route("**/*", route -> { + // Override headers + Map headers = new HashMap<>(route.request().headers()); + headers.put("foo", "foo-value"); // set "foo" header + headers.remove("bar"); // remove "bar" header + route.fallback(new Route.ResumeOptions().setHeaders(headers)); +}); +``` + +```python async +async def handle(route, request): + # override headers + headers = { + **request.headers, + "foo": "foo-value" # set "foo" header + "bar": None # remove "bar" header + } + await route.fallback(headers=headers) +} +await page.route("**/*", handle) +``` + +```python sync +def handle(route, request): + # override headers + headers = { + **request.headers, + "foo": "foo-value" # set "foo" header + "bar": None # remove "bar" header + } + route.fallback(headers=headers) +} +page.route("**/*", handle) +``` + +```csharp +await page.RouteAsync("**/*", route => +{ + var headers = new Dictionary(route.Request.Headers) { { "foo", "foo-value" } }; + headers.Remove("bar"); + route.FallbackAsync(headers); +}); +``` + +### option: Route.fallback.url +- `url` <[string]> + +If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't +affect the route matching, all the routes are matched using the original request URL. + +### option: Route.fallback.method +- `method` <[string]> + +If set changes the request method (e.g. GET or POST) + +### option: Route.fallback.postData +- `postData` <[string]|[Buffer]> + +If set changes the post data of request + +### option: Route.fallback.headers +- `headers` <[Object]<[string], [string]>> + +If set changes the request HTTP headers. Header values will be converted to a string. + ## async method: Route.fulfill Fulfills route's request with given response. diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index ab8429aed1..e0bb1e7835 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -154,7 +154,7 @@ export class BrowserContext extends ChannelOwner if (handled) return; } - await route._innerContinue({}, true); + await route._innerContinue(true); } async _onBinding(bindingCall: BindingCall) { diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 13247364a9..05c9fea8ab 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -60,6 +60,13 @@ type RouteHAR = { path: string; }; +type FallbackOverrides = { + url?: string; + method?: string; + headers?: Headers; + postData?: string | Buffer; +}; + export class Request extends ChannelOwner implements api.Request { private _redirectedFrom: Request | null = null; private _redirectedTo: Request | null = null; @@ -68,6 +75,7 @@ export class Request extends ChannelOwner implements ap private _actualHeadersPromise: Promise | undefined; private _postData: Buffer | null; _timing: ResourceTiming; + private _fallbackOverrides: FallbackOverrides = {}; static from(request: channels.RequestChannel): Request { return (request as any)._object; @@ -98,7 +106,7 @@ export class Request extends ChannelOwner implements ap } url(): string { - return this._initializer.url; + return this._fallbackOverrides.url || this._initializer.url; } resourceType(): string { @@ -106,14 +114,21 @@ export class Request extends ChannelOwner implements ap } method(): string { - return this._initializer.method; + return this._fallbackOverrides.method || this._initializer.method; } postData(): string | null { + if (this._fallbackOverrides.postData) + return this._fallbackOverrides.postData.toString('utf-8'); return this._postData ? this._postData.toString('utf8') : null; } postDataBuffer(): Buffer | null { + if (this._fallbackOverrides.postData) { + if (isString(this._fallbackOverrides.postData)) + return Buffer.from(this._fallbackOverrides.postData, 'utf-8'); + return this._fallbackOverrides.postData; + } return this._postData; } @@ -142,7 +157,9 @@ export class Request extends ChannelOwner implements ap * @deprecated */ headers(): Headers { - return this._provisionalHeaders.headers(); + if (this._fallbackOverrides.headers) + return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers(); + return this._fallbackOverrides.headers || this._provisionalHeaders.headers(); } _context() { @@ -151,6 +168,9 @@ export class Request extends ChannelOwner implements ap } _actualHeaders(): Promise { + if (this._fallbackOverrides.headers) + return Promise.resolve(RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers)); + if (!this._actualHeadersPromise) { this._actualHeadersPromise = this._wrapApiCall(async () => { return new RawHeaders((await this._channel.rawRequestHeaders()).headers); @@ -219,14 +239,15 @@ export class Request extends ChannelOwner implements ap _finalRequest(): Request { return this._redirectedTo ? this._redirectedTo._finalRequest() : this; } -} -type OverridesForContinue = { - url?: string; - method?: string; - headers?: Headers; - postData?: string | Buffer; -}; + _applyFallbackOverrides(overrides: FallbackOverrides) { + this._fallbackOverrides = { ...this._fallbackOverrides, ...overrides }; + } + + _fallbackOverridesForContinue() { + return this._fallbackOverrides; + } +} export class Route extends ChannelOwner implements api.Route { private _handlingPromise: ManualPromise | null = null; @@ -259,8 +280,9 @@ export class Route extends ChannelOwner implements api.Ro return this._handlingPromise; } - async fallback() { + async fallback(options: FallbackOverrides = {}) { this._checkNotHandled(); + this.request()._applyFallbackOverrides(options); this._reportHandled(false); } @@ -360,9 +382,10 @@ export class Route extends ChannelOwner implements api.Ro return 'done'; } - async continue(options: OverridesForContinue = {}) { + async continue(options: FallbackOverrides = {}) { this._checkNotHandled(); - await this._innerContinue(options); + this.request()._applyFallbackOverrides(options); + await this._innerContinue(); this._reportHandled(true); } @@ -377,7 +400,8 @@ export class Route extends ChannelOwner implements api.Ro chain.resolve(done); } - async _innerContinue(options: OverridesForContinue = {}, internal = false) { + async _innerContinue(internal = false) { + const options = this.request()._fallbackOverridesForContinue(); return await this._wrapApiCall(async () => { const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData; await this._raceWithPageClose(this._channel.continue({ @@ -624,6 +648,13 @@ export class RawHeaders { private _headersArray: HeadersArray; private _headersMap = new MultiMap(); + static _fromHeadersObjectLossy(headers: Headers): RawHeaders { + const headersArray: HeadersArray = Object.entries(headers).map(([name, value]) => ({ + name, value + })).filter(header => header.value !== undefined); + return new RawHeaders(headersArray); + } + constructor(headers: HeadersArray) { this._headersArray = headers; for (const header of headers) diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 3ca693f58e..dc61b639b6 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -14796,8 +14796,8 @@ export interface Route { * // Override headers * const headers = { * ...request.headers(), - * foo: 'bar', // set "foo" header - * origin: undefined, // remove "origin" header + * foo: 'foo-value', // set "foo" header + * bar: undefined, // remove "bar" header * }; * route.continue({headers}); * }); @@ -14828,8 +14828,28 @@ export interface Route { }): Promise; /** - * Proceeds to the next registered route in the route chain. If no more routes are registered, continues the request as is. - * This allows registering multiple routes with the same mask and falling back from one to another. + * When several routes match the given pattern, they run in the order opposite to their registration. That way the last + * registered route can always override all the previos ones. In the example below, request will be handled by the + * bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first + * registered route. + * + * ```js + * await page.route('**\/*', route => { + * // Runs last. + * route.abort(); + * }); + * await page.route('**\/*', route => { + * // Runs second. + * route.fallback(); + * }); + * await page.route('**\/*', route => { + * // Runs first. + * route.fallback(); + * }); + * ``` + * + * Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example + * API calls vs page resources or GET requests vs POST requests as in the example below. * * ```js * // Handle GET requests. @@ -14853,8 +14873,45 @@ export interface Route { * }); * ``` * + * One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify + * url, method, headers and postData of the request. + * + * ```js + * await page.route('**\/*', (route, request) => { + * // Override headers + * const headers = { + * ...request.headers(), + * foo: 'foo-value', // set "foo" header + * bar: undefined, // remove "bar" header + * }; + * route.fallback({headers}); + * }); + * ``` + * + * @param options */ - fallback(): Promise; + fallback(options?: { + /** + * If set changes the request HTTP headers. Header values will be converted to a string. + */ + headers?: { [key: string]: string; }; + + /** + * If set changes the request method (e.g. GET or POST) + */ + method?: string; + + /** + * If set changes the post data of request + */ + postData?: string|Buffer; + + /** + * If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route + * matching, all the routes are matched using the original request URL. + */ + url?: string; + }): Promise; /** * Fulfills route's request with given response. diff --git a/tests/library/defaultbrowsercontext-2.spec.ts b/tests/library/defaultbrowsercontext-2.spec.ts index e109309b8e..15b1f17507 100644 --- a/tests/library/defaultbrowsercontext-2.spec.ts +++ b/tests/library/defaultbrowsercontext-2.spec.ts @@ -223,35 +223,3 @@ it('should connect to a browser with the default page', async ({ browserType,cre expect(context.pages().length).toBe(1); await context.close(); }); - -it('route.continue should delete the origin header', async ({ launchPersistent, server, isAndroid, browserName }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' }); - it.skip(isAndroid, 'No cross-process on Android'); - it.fail(browserName === 'webkit', 'Does not delete origin in webkit'); - - const { page } = await launchPersistent(); - - await page.goto(server.PREFIX + '/empty.html'); - server.setRoute('/something', (request, response) => { - response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); - response.end('done'); - }); - let interceptedRequest; - await page.route(server.CROSS_PROCESS_PREFIX + '/something', async (route, request) => { - interceptedRequest = request; - const headers = await request.allHeaders(); - delete headers['origin']; - route.continue({ headers }); - }); - - const [text, serverRequest] = await Promise.all([ - page.evaluate(async url => { - const data = await fetch(url); - return data.text(); - }, server.CROSS_PROCESS_PREFIX + '/something'), - server.waitForRequest('/something') - ]); - expect(text).toBe('done'); - expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX); - expect(serverRequest.headers.origin).toBeFalsy(); -}); diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index 50fc36e48d..b801d4899e 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -69,7 +69,7 @@ it('should delete header with undefined value', async ({ page, server, browserNa server.waitForRequest('/something') ]); expect(text).toBe('done'); - expect(interceptedRequest.headers()['foo']).toEqual('a'); + expect(interceptedRequest.headers()['foo']).toEqual(undefined); expect(serverRequest.headers.foo).toBeFalsy(); expect(serverRequest.headers.bar).toBe('b'); }); @@ -311,7 +311,6 @@ it('should delete the origin header', async ({ page, server, isAndroid, browserN server.waitForRequest('/something') ]); expect(text).toBe('done'); - expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX); + expect(interceptedRequest.headers()['origin']).toEqual(undefined); expect(serverRequest.headers.origin).toBeFalsy(); }); - diff --git a/tests/page/page-request-fallback.spec.ts b/tests/page/page-request-fallback.spec.ts new file mode 100644 index 0000000000..a679da7254 --- /dev/null +++ b/tests/page/page-request-fallback.spec.ts @@ -0,0 +1,245 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test as it, expect } from './pageTest'; + +it('should work', async ({ page, server }) => { + await page.route('**/*', route => route.fallback()); + await page.goto(server.EMPTY_PAGE); +}); + +it('should fall back', async ({ page, server }) => { + const intercepted = []; + await page.route('**/empty.html', route => { + intercepted.push(1); + route.fallback(); + }); + await page.route('**/empty.html', route => { + intercepted.push(2); + route.fallback(); + }); + await page.route('**/empty.html', route => { + intercepted.push(3); + route.fallback(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([3, 2, 1]); +}); + +it('should not chain fulfill', async ({ page, server }) => { + let failed = false; + await page.route('**/empty.html', route => { + failed = true; + }); + await page.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'fulfilled' }); + }); + await page.route('**/empty.html', route => { + route.fallback(); + }); + const response = await page.goto(server.EMPTY_PAGE); + const body = await response.body(); + expect(body.toString()).toEqual('fulfilled'); + expect(failed).toBeFalsy(); +}); + +it('should not chain abort', async ({ page, server }) => { + let failed = false; + await page.route('**/empty.html', route => { + failed = true; + }); + await page.route('**/empty.html', route => { + route.abort(); + }); + await page.route('**/empty.html', route => { + route.fallback(); + }); + const e = await page.goto(server.EMPTY_PAGE).catch(e => e); + expect(e).toBeTruthy(); + expect(failed).toBeFalsy(); +}); + +it('should fall back after exception', async ({ page, server }) => { + await page.route('**/empty.html', route => { + route.continue(); + }); + await page.route('**/empty.html', async route => { + try { + await route.fulfill({ har: { path: 'file' }, response: {} as any }); + } catch (e) { + route.fallback(); + } + }); + await page.goto(server.EMPTY_PAGE); +}); + +it('should chain once', async ({ page, server }) => { + await page.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'fulfilled one' }); + }, { times: 1 }); + await page.route('**/empty.html', route => { + route.fallback(); + }, { times: 1 }); + const response = await page.goto(server.EMPTY_PAGE); + const body = await response.body(); + expect(body.toString()).toEqual('fulfilled one'); +}); + +it('should amend HTTP headers', async ({ page, server }) => { + const values = []; + await page.route('**/sleep.zzz', async route => { + values.push(route.request().headers().foo); + values.push(await route.request().headerValue('FOO')); + route.continue(); + }); + await page.route('**/*', route => { + route.fallback({ headers: { ...route.request().headers(), FOO: 'bar' } }); + }); + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + values.push(request.headers['foo']); + expect(values).toEqual(['bar', 'bar', 'bar']); +}); + +it('should delete header with undefined value', async ({ page, server, browserName }) => { + await page.goto(server.PREFIX + '/empty.html'); + server.setRoute('/something', (request, response) => { + response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); + response.end('done'); + }); + let interceptedRequest; + await page.route('**/*', (route, request) => { + interceptedRequest = request; + route.continue(); + }); + await page.route(server.PREFIX + '/something', async (route, request) => { + const headers = await request.allHeaders(); + route.fallback({ + headers: { + ...headers, + foo: undefined + } + }); + }); + const [text, serverRequest] = await Promise.all([ + page.evaluate(async url => { + const data = await fetch(url, { + headers: { + foo: 'a', + bar: 'b', + } + }); + return data.text(); + }, server.PREFIX + '/something'), + server.waitForRequest('/something') + ]); + expect(text).toBe('done'); + expect(interceptedRequest.headers()['foo']).toEqual(undefined); + expect(interceptedRequest.headers()['bar']).toEqual('b'); + expect(serverRequest.headers.foo).toBeFalsy(); + expect(serverRequest.headers.bar).toBe('b'); +}); + +it('should amend method', async ({ page, server }) => { + const sRequest = server.waitForRequest('/sleep.zzz'); + await page.goto(server.EMPTY_PAGE); + + let method: string; + await page.route('**/*', route => { + method = route.request().method(); + route.continue(); + }); + await page.route('**/*', route => route.fallback({ method: 'POST' })); + + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + expect(method).toBe('POST'); + expect(request.method).toBe('POST'); + expect((await sRequest).method).toBe('POST'); +}); + +it('should override request url', async ({ page, server }) => { + const request = server.waitForRequest('/global-var.html'); + + let url: string; + await page.route('**/foo', route => { + url = route.request().url(); + route.continue(); + }); + + await page.route('**/foo', route => route.fallback({ url: server.PREFIX + '/global-var.html' })); + + const [response] = await Promise.all([ + page.waitForEvent('response'), + page.goto(server.PREFIX + '/foo'), + ]); + expect(url).toBe(server.PREFIX + '/global-var.html'); + expect(response.url()).toBe(server.PREFIX + '/foo'); + expect(await page.evaluate(() => window['globalVar'])).toBe(123); + expect((await request).method).toBe('GET'); +}); + +it.describe('post data', () => { + it.fixme(({ isAndroid }) => isAndroid, 'Post data does not work'); + + it('should amend post data', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + let postData: string; + await page.route('**/*', route => { + postData = route.request().postData(); + route.continue(); + }); + await page.route('**/*', route => { + route.fallback({ postData: 'doggo' }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect(postData).toBe('doggo'); + expect((await serverRequest.postBody).toString('utf8')).toBe('doggo'); + }); + + it('should amend binary post data', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + const arr = Array.from(Array(256).keys()); + let postDataBuffer: Buffer; + await page.route('**/*', route => { + postDataBuffer = route.request().postDataBuffer(); + route.continue(); + }); + await page.route('**/*', route => { + route.fallback({ postData: Buffer.from(arr) }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + const buffer = await serverRequest.postBody; + expect(postDataBuffer.length).toBe(arr.length); + expect(buffer.length).toBe(arr.length); + for (let i = 0; i < arr.length; ++i) { + expect(buffer[i]).toBe(arr[i]); + expect(postDataBuffer[i]).toBe(arr[i]); + } + }); +}); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index ac9b5ceba6..0656f2778c 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -86,6 +86,7 @@ it('should work when POST is redirected with 302', async ({ page, server }) => { page.waitForNavigation() ]); }); + // @see https://github.com/GoogleChrome/puppeteer/issues/3973 it('should work when header manipulation headers with redirect', async ({ page, server }) => { server.setRedirect('/rrredirect', '/empty.html'); @@ -97,6 +98,7 @@ it('should work when header manipulation headers with redirect', async ({ page, }); await page.goto(server.PREFIX + '/rrredirect'); }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4743 it('should be able to remove headers', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); @@ -149,6 +151,7 @@ it('should show custom HTTP headers', async ({ page, server }) => { const response = await page.goto(server.EMPTY_PAGE); expect(response.ok()).toBe(true); }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4337 it('should work with redirect inside sync XHR', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); @@ -837,80 +840,3 @@ for (const method of ['fulfill', 'continue', 'fallback', 'abort'] as const) { expect(e.message).toContain('Route is already handled!'); }); } - -it('should fall back', async ({ page, server }) => { - const intercepted = []; - await page.route('**/empty.html', route => { - intercepted.push(1); - route.fallback(); - }); - await page.route('**/empty.html', route => { - intercepted.push(2); - route.fallback(); - }); - await page.route('**/empty.html', route => { - intercepted.push(3); - route.fallback(); - }); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([3, 2, 1]); -}); - -it('should not chain fulfill', async ({ page, server }) => { - let failed = false; - await page.route('**/empty.html', route => { - failed = true; - }); - await page.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'fulfilled' }); - }); - await page.route('**/empty.html', route => { - route.fallback(); - }); - const response = await page.goto(server.EMPTY_PAGE); - const body = await response.body(); - expect(body.toString()).toEqual('fulfilled'); - expect(failed).toBeFalsy(); -}); - -it('should not chain abort', async ({ page, server }) => { - let failed = false; - await page.route('**/empty.html', route => { - failed = true; - }); - await page.route('**/empty.html', route => { - route.abort(); - }); - await page.route('**/empty.html', route => { - route.fallback(); - }); - const e = await page.goto(server.EMPTY_PAGE).catch(e => e); - expect(e).toBeTruthy(); - expect(failed).toBeFalsy(); -}); - -it('should fall back after exception', async ({ page, server }) => { - await page.route('**/empty.html', route => { - route.continue(); - }); - await page.route('**/empty.html', async route => { - try { - await route.fulfill({ har: { path: 'file' }, response: {} as any }); - } catch (e) { - route.fallback(); - } - }); - await page.goto(server.EMPTY_PAGE); -}); - -it('should chain once', async ({ page, server }) => { - await page.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'fulfilled one' }); - }, { times: 1 }); - await page.route('**/empty.html', route => { - route.fallback(); - }, { times: 1 }); - const response = await page.goto(server.EMPTY_PAGE); - const body = await response.body(); - expect(body.toString()).toEqual('fulfilled one'); -});