2021-06-18 21:04:48 +03:00
/ * *
* 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 .
* /
2022-06-07 19:00:51 +03:00
import type { Route , Request } from 'playwright-core' ;
2022-02-26 00:56:51 +03:00
import { expect , test as base } from './pageTest' ;
2021-11-13 11:29:51 +03:00
import fs from 'fs' ;
import path from 'path' ;
2021-06-18 21:04:48 +03:00
2022-12-01 04:26:19 +03:00
// We access test servers at 10.0.2.2 from inside the browser on Android,
// which is actually forwarded to the desktop localhost.
// To use request such an url with apiRequestContext on the desktop, we need to change it back to localhost.
const it = base . extend < { rewriteAndroidLoopbackURL ( url : string ) : string } > ( {
rewriteAndroidLoopbackURL : ( { isAndroid } , use ) = > use ( givenURL = > {
if ( ! isAndroid )
return givenURL ;
const requestURL = new URL ( givenURL ) ;
requestURL . hostname = 'localhost' ;
return requestURL . toString ( ) ;
} )
} ) ;
2022-02-26 00:56:51 +03:00
2024-06-21 01:43:26 +03:00
it ( 'should fulfill intercepted response' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2021-06-18 21:04:48 +03:00
await page . route ( '**/*' , async route = > {
2021-11-03 03:48:38 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-06-18 21:04:48 +03:00
await route . fulfill ( {
2021-11-03 03:48:38 +03:00
response ,
2021-06-18 21:04:48 +03:00
status : 201 ,
headers : {
foo : 'bar'
} ,
contentType : 'text/plain' ,
body : 'Yo, page!'
} ) ;
} ) ;
const response = await page . goto ( server . PREFIX + '/empty.html' ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( response . headers ( ) . foo ) . toBe ( 'bar' ) ;
expect ( response . headers ( ) [ 'content-type' ] ) . toBe ( 'text/plain' ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( 'Yo, page!' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should fulfill response with empty body' , async ( { page , server , isAndroid , isElectron , electronMajorVersion , browserName , browserMajorVersion } ) = > {
2022-02-26 02:17:37 +03:00
it . skip ( browserName === 'chromium' && browserMajorVersion <= 91 , 'Fails in Electron that uses old Chromium' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2024-06-21 01:43:26 +03:00
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2021-08-12 00:47:05 +03:00
await page . route ( '**/*' , async route = > {
2021-11-03 03:48:38 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-08-12 00:47:05 +03:00
await route . fulfill ( {
2021-08-27 01:44:20 +03:00
response ,
2022-06-17 08:07:43 +03:00
headers : { 'content-length' : '0' } ,
2021-08-12 00:47:05 +03:00
status : 201 ,
body : ''
} ) ;
} ) ;
const response = await page . goto ( server . PREFIX + '/title.html' ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( await response . text ( ) ) . toBe ( '' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should override with defaults when intercepted response not provided' , async ( { page , server , browserName , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2021-08-24 21:07:54 +03:00
server . setRoute ( '/empty.html' , ( req , res ) = > {
res . setHeader ( 'foo' , 'bar' ) ;
res . end ( 'my content' ) ;
} ) ;
await page . route ( '**/*' , async route = > {
2021-11-03 03:48:38 +03:00
await page . request . fetch ( route . request ( ) ) ;
2021-08-24 21:07:54 +03:00
await route . fulfill ( {
status : 201 ,
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( await response . text ( ) ) . toBe ( '' ) ;
if ( browserName === 'webkit' )
2021-09-27 19:58:08 +03:00
expect ( response . headers ( ) ) . toEqual ( { 'content-type' : 'text/plain' } ) ;
2021-08-24 21:07:54 +03:00
else
expect ( response . headers ( ) ) . toEqual ( { } ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should fulfill with any response' , async ( { page , server , isElectron , electronMajorVersion , rewriteAndroidLoopbackURL } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2021-08-24 21:07:54 +03:00
server . setRoute ( '/sample' , ( req , res ) = > {
res . setHeader ( 'foo' , 'bar' ) ;
res . end ( 'Woo-hoo' ) ;
} ) ;
2022-02-26 00:56:51 +03:00
const sampleResponse = await page . request . get ( rewriteAndroidLoopbackURL ( ` ${ server . PREFIX } /sample ` ) ) ;
2021-08-24 21:07:54 +03:00
await page . route ( '**/*' , async route = > {
await route . fulfill ( {
2021-08-27 01:44:20 +03:00
response : sampleResponse ,
2021-08-24 21:07:54 +03:00
status : 201 ,
2021-08-25 03:34:29 +03:00
contentType : 'text/plain'
2021-08-24 21:07:54 +03:00
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( await response . text ( ) ) . toBe ( 'Woo-hoo' ) ;
expect ( response . headers ( ) [ 'foo' ] ) . toBe ( 'bar' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should support fulfill after intercept' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2021-08-04 01:29:51 +03:00
const requestPromise = server . waitForRequest ( '/title.html' ) ;
2021-06-18 21:04:48 +03:00
await page . route ( '**' , async route = > {
2021-11-03 03:48:38 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-08-27 01:44:20 +03:00
await route . fulfill ( { response } ) ;
2021-06-18 21:04:48 +03:00
} ) ;
2021-08-04 01:29:51 +03:00
const response = await page . goto ( server . PREFIX + '/title.html' ) ;
2021-06-18 21:04:48 +03:00
const request = await requestPromise ;
2021-08-04 01:29:51 +03:00
expect ( request . url ) . toBe ( '/title.html' ) ;
2021-11-13 11:29:51 +03:00
const original = await fs . promises . readFile ( path . join ( __dirname , '..' , 'assets' , 'title.html' ) , 'utf8' ) ;
expect ( await response . text ( ) ) . toBe ( original ) ;
2021-06-18 21:04:48 +03:00
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should give access to the intercepted response' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2021-06-18 21:04:48 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
let routeCallback ;
const routePromise = new Promise < Route > ( f = > routeCallback = f ) ;
await page . route ( '**/title.html' , routeCallback ) ;
2021-08-14 02:28:42 +03:00
const evalPromise = page . evaluate ( url = > fetch ( url ) , server . PREFIX + '/title.html' ) ;
2021-06-18 21:04:48 +03:00
const route = await routePromise ;
2021-11-03 03:48:38 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-06-18 21:04:48 +03:00
expect ( response . status ( ) ) . toBe ( 200 ) ;
2021-11-03 03:48:38 +03:00
expect ( response . statusText ( ) ) . toBe ( 'OK' ) ;
2021-06-18 21:04:48 +03:00
expect ( response . ok ( ) ) . toBeTruthy ( ) ;
2022-02-26 00:56:51 +03:00
expect ( response . url ( ) . endsWith ( '/title.html' ) ) . toBe ( true ) ;
2021-06-18 21:04:48 +03:00
expect ( response . headers ( ) [ 'content-type' ] ) . toBe ( 'text/html; charset=utf-8' ) ;
2022-02-26 00:56:51 +03:00
expect ( response . headersArray ( ) . filter ( ( { name } ) = > name . toLowerCase ( ) === 'content-type' ) ) . toEqual ( [ { name : 'Content-Type' , value : 'text/html; charset=utf-8' } ] ) ;
2021-06-18 21:04:48 +03:00
2021-08-27 01:44:20 +03:00
await Promise . all ( [ route . fulfill ( { response } ) , evalPromise ] ) ;
2021-06-18 21:04:48 +03:00
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should give access to the intercepted response body' , async ( { page , server , isAndroid , isElectron , electronMajorVersion } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-02-26 00:56:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
2021-06-18 21:04:48 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
let routeCallback ;
const routePromise = new Promise < Route > ( f = > routeCallback = f ) ;
await page . route ( '**/simple.json' , routeCallback ) ;
2021-10-27 18:28:53 +03:00
const evalPromise = page . evaluate ( url = > fetch ( url ) , server . PREFIX + '/simple.json' ) . catch ( ( ) = > { } ) ;
2021-06-18 21:04:48 +03:00
const route = await routePromise ;
2021-11-03 03:48:38 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-06-18 21:04:48 +03:00
2022-02-26 00:56:51 +03:00
expect ( await response . text ( ) ) . toBe ( '{"foo": "bar"}\n' ) ;
2021-06-18 21:04:48 +03:00
2021-08-27 01:44:20 +03:00
await Promise . all ( [ route . fulfill ( { response } ) , evalPromise ] ) ;
2021-06-18 21:04:48 +03:00
} ) ;
2022-06-07 19:00:51 +03:00
it ( 'should intercept multipart/form-data request body' , async ( { page , server , asset , browserName } ) = > {
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/14624' } ) ;
2024-06-17 20:20:57 +03:00
it . fixme ( browserName !== 'firefox' ) ;
2022-06-07 19:00:51 +03:00
await page . goto ( server . PREFIX + '/input/fileupload.html' ) ;
const filePath = path . relative ( process . cwd ( ) , asset ( 'file-to-upload.txt' ) ) ;
await page . locator ( 'input[type=file]' ) . setInputFiles ( filePath ) ;
const requestPromise = new Promise < Request > ( async fulfill = > {
await page . route ( '**/upload' , route = > {
fulfill ( route . request ( ) ) ;
2023-12-15 20:00:12 +03:00
} ) ;
2022-06-07 19:00:51 +03:00
} ) ;
const [ request ] = await Promise . all ( [
requestPromise ,
page . click ( 'input[type=submit]' , { noWaitAfter : true } )
] ) ;
expect ( request . method ( ) ) . toBe ( 'POST' ) ;
expect ( request . postData ( ) ) . toContain ( fs . readFileSync ( filePath , 'utf8' ) ) ;
2022-12-01 04:26:19 +03:00
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should fulfill intercepted response using alias' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-12-01 04:26:19 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
await page . route ( '**/*' , async route = > {
const response = await route . fetch ( ) ;
await route . fulfill ( { response } ) ;
} ) ;
const response = await page . goto ( server . PREFIX + '/empty.html' ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
expect ( response . headers ( ) [ 'content-type' ] ) . toContain ( 'text/html' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should support timeout option in route.fetch' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2023-04-20 18:41:33 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
server . setRoute ( '/slow' , ( req , res ) = > {
res . writeHead ( 200 , {
'content-length' : 4096 ,
'content-type' : 'text/html' ,
} ) ;
} ) ;
await page . route ( '**/*' , async route = > {
const error = await route . fetch ( { timeout : 1000 } ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( ` Request timed out after 1000ms ` ) ;
2023-12-15 20:00:12 +03:00
} ) ;
2023-04-20 18:41:33 +03:00
const error = await page . goto ( server . PREFIX + '/slow' , { timeout : 2000 } ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( ` Timeout 2000ms exceeded ` ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should not follow redirects when maxRedirects is set to 0 in route.fetch' , async ( { page , server , isAndroid , isElectron , electronMajorVersion } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2023-02-02 01:43:21 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
server . setRedirect ( '/foo' , '/empty.html' ) ;
await page . route ( '**/*' , async route = > {
const response = await route . fetch ( { maxRedirects : 0 } ) ;
expect ( response . headers ( ) [ 'location' ] ) . toBe ( '/empty.html' ) ;
expect ( response . status ( ) ) . toBe ( 302 ) ;
await route . fulfill ( { body : 'hello' } ) ;
} ) ;
await page . goto ( server . PREFIX + '/foo' ) ;
expect ( await page . content ( ) ) . toContain ( 'hello' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should intercept with url override' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-12-01 04:26:19 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
await page . route ( '**/*.html' , async route = > {
const response = await route . fetch ( { url : server.PREFIX + '/one-style.html' } ) ;
await route . fulfill ( { response } ) ;
} ) ;
const response = await page . goto ( server . PREFIX + '/empty.html' ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
expect ( ( await response . body ( ) ) . toString ( ) ) . toContain ( 'one-style.css' ) ;
} ) ;
2024-06-21 01:43:26 +03:00
it ( 'should intercept with post data override' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2022-12-01 04:26:19 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
const requestPromise = server . waitForRequest ( '/empty.html' ) ;
await page . route ( '**/*.html' , async route = > {
const response = await route . fetch ( {
2022-12-14 01:01:39 +03:00
postData : { 'foo' : 'bar' } ,
2022-12-01 04:26:19 +03:00
} ) ;
await route . fulfill ( { response } ) ;
} ) ;
await page . goto ( server . PREFIX + '/empty.html' ) ;
const request = await requestPromise ;
expect ( ( await request . postBody ) . toString ( ) ) . toBe ( JSON . stringify ( { 'foo' : 'bar' } ) ) ;
} ) ;
2023-08-22 02:48:51 +03:00
2024-06-21 01:43:26 +03:00
it ( 'should fulfill popup main request using alias' , async ( { page , server , isElectron , electronMajorVersion , isAndroid } ) = > {
it . skip ( isElectron && electronMajorVersion < 30 , 'error: Browser context management is not supported.' ) ;
2023-08-22 02:48:51 +03:00
it . skip ( isAndroid , 'The internal Android localhost (10.0.0.2) != the localhost on the host' ) ;
await page . context ( ) . route ( '**/*' , async route = > {
const response = await route . fetch ( ) ;
await route . fulfill ( { response , body : 'hello' } ) ;
} ) ;
await page . setContent ( ` <a target=_blank href=" ${ server . EMPTY_PAGE } ">click me</a> ` ) ;
const [ popup ] = await Promise . all ( [
page . waitForEvent ( 'popup' ) ,
page . getByText ( 'click me' ) . click ( ) ,
] ) ;
await expect ( popup . locator ( 'body' ) ) . toHaveText ( 'hello' ) ;
} ) ;
2024-06-14 23:30:22 +03:00
it ( 'request.postData is not null when fetching FormData with a Blob' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/24077' }
2024-09-06 15:27:56 +03:00
} , async ( { server , page , browserName , isElectron , electronMajorVersion , isAndroid } ) = > {
2024-06-21 01:43:26 +03:00
it . skip ( isElectron && electronMajorVersion < 31 ) ;
2024-09-06 15:27:56 +03:00
it . fixme ( isAndroid , 'postData is null for some reason' ) ;
2024-06-14 23:30:22 +03:00
it . fixme ( browserName === 'webkit' , 'The body is empty in WebKit when intercepting' ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< script >
function doStuff() {
const formData = new FormData ( ) ;
formData . append ( 'file' , new Blob ( [ "hello" ] , { type : "text/plain" } ) ) ;
fetch ( '/upload' , {
method : 'POST' ,
body : formData
} ) ;
}
< / script >
< body >
< button onclick = "doStuff()" data - testid = "click-me" > Click me ! < / button >
< / body > ` );
let resolvePostData ;
const postDataPromise = new Promise < string > ( resolve = > resolvePostData = resolve ) ;
await page . route ( server . PREFIX + '/upload' , async ( route , request ) = > {
expect ( request . method ( ) ) . toBe ( 'POST' ) ;
resolvePostData ( await request . postData ( ) ) ;
await route . fulfill ( {
status : 200 ,
body : 'ok' ,
} ) ;
} ) ;
await page . getByTestId ( 'click-me' ) . click ( ) ;
const postData = await postDataPromise ;
expect ( postData ) . toContain ( 'Content-Disposition: form-data; name="file"; filename="blob"' ) ;
expect ( postData ) . toContain ( '\r\nhello\r\n' ) ;
2024-11-18 22:01:39 +03:00
} ) ;
it ( 'should abort favicon requests if interception is enabled' , async ( { page , server , browserName } ) = > {
let requestCount = 0 ;
server . setRoute ( '/favicon.ico' , ( req , res ) = > {
++ requestCount ;
res . setHeader ( 'content-type' , 'text/plain' ) ;
res . end ( 'my content' ) ;
} ) ;
// Intercept all requests.
await page . route ( '**/*' , async route = > {
await route . fulfill ( {
status : 200 ,
body : 'Hello, world!' ,
} ) ;
} ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const response = await page . evaluate ( ( ) = > fetch ( '/favicon.ico' ) . then ( r = > r . text ( ) ) . catch ( e = > 'load failed' ) ) ;
expect ( response ) . toBe ( 'load failed' ) ;
// Browsers can send favicon requests in the background.
await new Promise ( f = > setTimeout ( f , 1000 ) ) ;
expect ( requestCount ) . toBe ( 0 ) ;
} ) ;