2020-08-05 02:32:10 +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-02-26 00:56:51 +03:00
import { test as base , expect } from './pageTest' ;
2020-08-12 01:50:53 +03:00
import fs from 'fs' ;
2022-09-21 04:41:51 +03:00
import type * as har from '../../packages/trace/src/har' ;
2020-08-05 02:32:10 +03:00
2022-02-26 00:56:51 +03:00
const it = base . extend < {
// 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.
rewriteAndroidLoopbackURL ( url : string ) : string
} > ( {
rewriteAndroidLoopbackURL : ( { isAndroid } , use ) = > use ( givenURL = > {
if ( ! isAndroid )
return givenURL ;
const requestURL = new URL ( givenURL ) ;
requestURL . hostname = 'localhost' ;
return requestURL . toString ( ) ;
} )
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should work' , async ( { page , server } ) = > {
2020-08-05 02:32:10 +03:00
await page . route ( '**/*' , route = > {
route . fulfill ( {
status : 201 ,
headers : {
foo : 'bar'
} ,
contentType : 'text/html' ,
body : 'Yo, page!'
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( response . headers ( ) . foo ) . toBe ( 'bar' ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( 'Yo, page!' ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should work with buffer as body' , async ( { page , server , browserName , isLinux } ) = > {
2021-08-25 03:34:29 +03:00
await page . route ( '**/*' , route = > {
route . fulfill ( {
status : 200 ,
2023-01-25 12:03:50 +03:00
contentType : 'text/plain' ,
2021-08-25 03:34:29 +03:00
body : Buffer.from ( 'Yo, page!' )
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( 'Yo, page!' ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should work with status code 422' , async ( { page , server } ) = > {
2020-08-05 02:32:10 +03:00
await page . route ( '**/*' , route = > {
route . fulfill ( {
status : 422 ,
body : 'Yo, page!'
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 422 ) ;
expect ( response . statusText ( ) ) . toBe ( 'Unprocessable Entity' ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( 'Yo, page!' ) ;
} ) ;
2022-04-15 22:11:38 +03:00
it ( 'should allow mocking binary responses' , async ( { page , server , browserName , headless , asset , isAndroid , mode } ) = > {
it . skip ( mode === 'service' ) ;
2021-05-13 20:22:23 +03:00
it . skip ( browserName === 'firefox' && ! headless , 'Firefox headed produces a different image.' ) ;
2021-04-09 17:59:09 +03:00
it . skip ( isAndroid ) ;
2021-04-05 05:32:14 +03:00
2020-08-05 02:32:10 +03:00
await page . route ( '**/*' , route = > {
2021-04-06 01:51:45 +03:00
const imageBuffer = fs . readFileSync ( asset ( 'pptr.png' ) ) ;
2020-08-05 02:32:10 +03:00
route . fulfill ( {
contentType : 'image/png' ,
body : imageBuffer
} ) ;
} ) ;
await page . evaluate ( PREFIX = > {
const img = document . createElement ( 'img' ) ;
img . src = PREFIX + '/does-not-exist.png' ;
document . body . appendChild ( img ) ;
return new Promise ( fulfill = > img . onload = fulfill ) ;
} , server . PREFIX ) ;
const img = await page . $ ( 'img' ) ;
2020-10-07 01:51:18 +03:00
expect ( await img . screenshot ( ) ) . toMatchSnapshot ( 'mock-binary-response.png' ) ;
2020-08-05 02:32:10 +03:00
} ) ;
2022-07-28 16:41:32 +03:00
it ( 'should allow mocking svg with charset' , async ( { page , server , browserName , headless , isAndroid , isElectron , mode } ) = > {
2022-04-15 22:11:38 +03:00
it . skip ( mode === 'service' ) ;
2021-05-13 20:22:23 +03:00
it . skip ( browserName === 'firefox' && ! headless , 'Firefox headed produces a different image.' ) ;
2021-04-09 17:59:09 +03:00
it . skip ( isAndroid ) ;
2022-07-28 16:41:32 +03:00
it . skip ( isElectron , 'Protocol error (Storage.getCookies): Browser context management is not supported' ) ;
2021-04-05 05:32:14 +03:00
2020-08-05 02:32:10 +03:00
await page . route ( '**/*' , route = > {
route . fulfill ( {
contentType : 'image/svg+xml ; charset=utf-8' ,
body : '<svg width="50" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/></svg>'
} ) ;
} ) ;
await page . evaluate ( PREFIX = > {
const img = document . createElement ( 'img' ) ;
img . src = PREFIX + '/does-not-exist.svg' ;
document . body . appendChild ( img ) ;
return new Promise ( ( f , r ) = > { img . onload = f ; img . onerror = r ; } ) ;
} , server . PREFIX ) ;
const img = await page . $ ( 'img' ) ;
2020-10-07 01:51:18 +03:00
expect ( await img . screenshot ( ) ) . toMatchSnapshot ( 'mock-svg.png' ) ;
2020-08-05 02:32:10 +03:00
} ) ;
2022-08-03 11:51:45 +03:00
it ( 'should work with file path' , async ( { page , server , asset , mode , isAndroid } ) = > {
it . skip ( mode === 'service' || isAndroid ) ;
2021-04-05 05:32:14 +03:00
2021-04-06 01:51:45 +03:00
await page . route ( '**/*' , route = > route . fulfill ( { contentType : 'shouldBeIgnored' , path : asset ( 'pptr.png' ) } ) ) ;
2020-08-05 02:32:10 +03:00
await page . evaluate ( PREFIX = > {
const img = document . createElement ( 'img' ) ;
img . src = PREFIX + '/does-not-exist.png' ;
document . body . appendChild ( img ) ;
return new Promise ( fulfill = > img . onload = fulfill ) ;
} , server . PREFIX ) ;
const img = await page . $ ( 'img' ) ;
2020-10-07 01:51:18 +03:00
expect ( await img . screenshot ( ) ) . toMatchSnapshot ( 'mock-binary-response.png' ) ;
2020-08-05 02:32:10 +03:00
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should stringify intercepted request response headers' , async ( { page , server } ) = > {
2020-08-05 02:32:10 +03:00
await page . route ( '**/*' , route = > {
route . fulfill ( {
status : 200 ,
headers : {
2020-08-12 01:50:53 +03:00
'foo' : 'true'
2020-08-05 02:32:10 +03:00
} ,
body : 'Yo, page!'
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
const headers = response . headers ( ) ;
expect ( headers . foo ) . toBe ( 'true' ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( 'Yo, page!' ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should not modify the headers sent to the server' , async ( { page , server } ) = > {
2020-08-05 02:32:10 +03:00
await page . goto ( server . PREFIX + '/empty.html' ) ;
const interceptedRequests = [ ] ;
2020-08-28 14:20:29 +03:00
// this is just to enable request interception, which disables caching in chromium
2020-08-12 01:50:53 +03:00
await page . route ( server . PREFIX + '/unused' , ( ) = > { } ) ;
2020-08-05 02:32:10 +03:00
server . setRoute ( '/something' , ( request , response ) = > {
interceptedRequests . push ( request ) ;
response . writeHead ( 200 , { 'Access-Control-Allow-Origin' : '*' } ) ;
response . end ( 'done' ) ;
} ) ;
const text = await page . evaluate ( async url = > {
const data = await fetch ( url ) ;
return data . text ( ) ;
} , server . CROSS_PROCESS_PREFIX + '/something' ) ;
expect ( text ) . toBe ( 'done' ) ;
await page . route ( server . CROSS_PROCESS_PREFIX + '/something' , ( route , request ) = > {
route . continue ( {
headers : {
. . . request . headers ( )
}
} ) ;
} ) ;
const textAfterRoute = await page . evaluate ( async url = > {
const data = await fetch ( url ) ;
return data . text ( ) ;
} , server . CROSS_PROCESS_PREFIX + '/something' ) ;
expect ( textAfterRoute ) . toBe ( 'done' ) ;
expect ( interceptedRequests . length ) . toBe ( 2 ) ;
expect ( interceptedRequests [ 1 ] . headers ) . toEqual ( interceptedRequests [ 0 ] . headers ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should include the origin header' , async ( { page , server , isAndroid } ) = > {
2021-04-09 17:59:09 +03:00
it . skip ( isAndroid , 'No cross-process on Android' ) ;
2021-04-05 05:32:14 +03:00
2020-08-05 02:32:10 +03:00
await page . goto ( server . PREFIX + '/empty.html' ) ;
let interceptedRequest ;
await page . route ( server . CROSS_PROCESS_PREFIX + '/something' , ( route , request ) = > {
interceptedRequest = request ;
route . fulfill ( {
headers : {
'Access-Control-Allow-Origin' : '*' ,
} ,
contentType : 'text/plain' ,
body : 'done'
} ) ;
} ) ;
const text = await page . evaluate ( async url = > {
const data = await fetch ( url ) ;
return data . text ( ) ;
} , server . CROSS_PROCESS_PREFIX + '/something' ) ;
expect ( text ) . toBe ( 'done' ) ;
expect ( interceptedRequest . headers ( ) [ 'origin' ] ) . toEqual ( server . PREFIX ) ;
} ) ;
2021-09-09 00:59:12 +03:00
2022-02-26 00:56:51 +03:00
it ( 'should fulfill with global fetch result' , async ( { playwright , page , server , isElectron , rewriteAndroidLoopbackURL } ) = > {
2021-09-16 00:02:55 +03:00
it . fixme ( isElectron , 'error: Browser context management is not supported.' ) ;
await page . route ( '**/*' , async route = > {
2021-10-06 04:53:19 +03:00
const request = await playwright . request . newContext ( ) ;
2022-02-26 00:56:51 +03:00
const response = await request . get ( rewriteAndroidLoopbackURL ( server . PREFIX + '/simple.json' ) ) ;
2021-09-16 00:02:55 +03:00
route . fulfill ( { response } ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
2021-09-27 19:58:08 +03:00
expect ( await response . json ( ) ) . toEqual ( { 'foo' : 'bar' } ) ;
2021-09-16 00:02:55 +03:00
} ) ;
2022-02-26 00:56:51 +03:00
it ( 'should fulfill with fetch result' , async ( { page , server , isElectron , rewriteAndroidLoopbackURL } ) = > {
2021-09-10 19:33:12 +03:00
it . fixme ( isElectron , 'error: Browser context management is not supported.' ) ;
2021-09-09 00:59:12 +03:00
await page . route ( '**/*' , async route = > {
2022-02-26 00:56:51 +03:00
const response = await page . request . get ( rewriteAndroidLoopbackURL ( server . PREFIX + '/simple.json' ) ) ;
2021-09-09 00:59:12 +03:00
route . fulfill ( { response } ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
2021-09-27 19:58:08 +03:00
expect ( await response . json ( ) ) . toEqual ( { 'foo' : 'bar' } ) ;
2021-09-09 00:59:12 +03:00
} ) ;
2022-02-26 00:56:51 +03:00
it ( 'should fulfill with fetch result and overrides' , async ( { page , server , isElectron , rewriteAndroidLoopbackURL } ) = > {
2021-09-10 19:33:12 +03:00
it . fixme ( isElectron , 'error: Browser context management is not supported.' ) ;
2021-09-09 00:59:12 +03:00
await page . route ( '**/*' , async route = > {
2022-02-26 00:56:51 +03:00
const response = await page . request . get ( rewriteAndroidLoopbackURL ( server . PREFIX + '/simple.json' ) ) ;
2021-09-09 00:59:12 +03:00
route . fulfill ( {
response ,
status : 201 ,
headers : {
2021-10-01 23:04:03 +03:00
'Content-Type' : 'application/json' , // Case matters for the tested behavior
2021-09-09 00:59:12 +03:00
'foo' : 'bar'
}
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( ( await response . allHeaders ( ) ) . foo ) . toEqual ( 'bar' ) ;
2021-09-27 19:58:08 +03:00
expect ( await response . json ( ) ) . toEqual ( { 'foo' : 'bar' } ) ;
2021-09-09 00:59:12 +03:00
} ) ;
2021-09-09 02:31:58 +03:00
2022-02-26 00:56:51 +03:00
it ( 'should fetch original request and fulfill' , async ( { page , server , isElectron , isAndroid } ) = > {
2021-09-10 19:33:12 +03:00
it . fixme ( isElectron , '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-09-09 02:31:58 +03:00
await page . route ( '**/*' , async route = > {
2021-10-06 02:36:15 +03:00
const response = await page . request . fetch ( route . request ( ) ) ;
2021-09-09 02:31:58 +03:00
route . fulfill ( {
response ,
} ) ;
} ) ;
const response = await page . goto ( server . PREFIX + '/title.html' ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
expect ( await page . title ( ) ) . toEqual ( 'Woof-Woof' ) ;
} ) ;
2021-10-04 23:19:05 +03:00
2022-08-02 23:51:10 +03:00
it ( 'should fulfill with multiple set-cookie' , async ( { page , server , isElectron } ) = > {
2021-10-12 11:59:39 +03:00
it . fixme ( isElectron , 'Electron 14+ is required' ) ;
2021-10-04 23:19:05 +03:00
const cookies = [ 'a=b' , 'c=d' ] ;
await page . route ( '**/empty.html' , async route = > {
route . fulfill ( {
status : 200 ,
headers : {
'X-Header-1' : 'v1' ,
'Set-Cookie' : cookies . join ( '\n' ) ,
'X-Header-2' : 'v2' ,
} ,
body : ''
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( ( await page . evaluate ( ( ) = > document . cookie ) ) . split ( ';' ) . map ( s = > s . trim ( ) ) . sort ( ) ) . toEqual ( cookies ) ;
expect ( await response . headerValue ( 'X-Header-1' ) ) . toBe ( 'v1' ) ;
expect ( await response . headerValue ( 'X-Header-2' ) ) . toBe ( 'v2' ) ;
} ) ;
2022-08-03 11:51:45 +03:00
it ( 'should fulfill with fetch response that has multiple set-cookie' , async ( { playwright , page , server , isAndroid } ) = > {
it . fixme ( isAndroid ) ;
2021-10-04 23:19:05 +03:00
server . setRoute ( '/empty.html' , ( req , res ) = > {
res . setHeader ( 'Set-Cookie' , [ 'a=b' , 'c=d' ] ) ;
2021-10-06 04:30:07 +03:00
res . setHeader ( 'Content-Type' , 'text/html' ) ;
2021-10-04 23:19:05 +03:00
res . end ( ) ;
} ) ;
await page . route ( '**/empty.html' , async route = > {
2021-10-06 04:53:19 +03:00
const request = await playwright . request . newContext ( ) ;
2021-10-04 23:19:05 +03:00
const response = await request . fetch ( route . request ( ) ) ;
route . fulfill ( { response } ) ;
} ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const cookie = await page . evaluate ( ( ) = > document . cookie ) ;
expect ( cookie . split ( ';' ) . map ( s = > s . trim ( ) ) . sort ( ) ) . toEqual ( [ 'a=b' , 'c=d' ] ) ;
} ) ;
it ( 'headerValue should return set-cookie from intercepted response' , async ( { page , server , browserName } ) = > {
it . fail ( browserName === 'chromium' , 'Set-Cookie is missing in response after interception' ) ;
2021-10-14 03:00:25 +03:00
it . fixme ( browserName === 'webkit' , 'Set-Cookie with \n in intercepted response does not pass validation in WebCore, see also https://github.com/microsoft/playwright/pull/9273' ) ;
2021-10-04 23:19:05 +03:00
await page . route ( '**/empty.html' , async route = > {
route . fulfill ( {
status : 200 ,
headers : {
'Set-Cookie' : 'a=b' ,
} ,
body : ''
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( await response . headerValue ( 'Set-Cookie' ) ) . toBe ( 'a=b' ) ;
} ) ;
2022-06-09 06:29:03 +03:00
2022-08-02 23:51:10 +03:00
it ( 'should fulfill with har response' , async ( { page , asset } ) = > {
2022-06-15 18:41:46 +03:00
const harPath = asset ( 'har-fulfill.har' ) ;
2022-06-22 22:16:29 +03:00
const har = JSON . parse ( await fs . promises . readFile ( harPath , 'utf-8' ) ) as har . HARFile ;
2022-06-15 18:41:46 +03:00
await page . route ( '**/*' , async route = > {
const response = findResponse ( har , route . request ( ) . url ( ) ) ;
2022-06-21 01:19:54 +03:00
const headers = { } ;
for ( const { name , value } of response . headers )
headers [ name ] = value ;
await route . fulfill ( {
status : response.status ,
headers ,
body : Buffer.from ( response . content . text || '' , ( response . content . encoding as 'base64' | undefined ) || 'utf-8' ) ,
} ) ;
2022-06-09 06:29:03 +03:00
} ) ;
2022-06-15 18:41:46 +03:00
await page . goto ( 'http://no.playwright/' ) ;
// HAR contains a redirect for the script.
expect ( await page . evaluate ( 'window.value' ) ) . toBe ( 'foo' ) ;
// HAR contains a POST for the css file but we match ignoring the method, so the file should be served.
await expect ( page . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(0, 255, 255)' ) ;
2022-06-09 06:29:03 +03:00
} ) ;
2022-06-22 22:16:29 +03:00
function findResponse ( har : har.HARFile , url : string ) : har . Response {
2022-06-15 18:41:46 +03:00
let entry ;
const originalUrl = url ;
while ( url . trim ( ) ) {
entry = har . log . entries . find ( entry = > entry . request . url === url ) ;
url = entry ? . response . redirectURL ;
}
expect ( entry , originalUrl ) . toBeTruthy ( ) ;
return entry ? . response ;
}
2022-08-30 20:35:55 +03:00
it ( 'should fulfill preload link requests' , async ( { page , server , browserName } ) = > {
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/16745' } ) ;
let intercepted = false ;
await page . route ( '**/one-style.css' , route = > {
intercepted = true ;
route . fulfill ( {
status : 200 ,
headers : {
'content-type' : 'text/css; charset=utf-8' ,
'cache-control' : 'no-cache, no-store' ,
'custom' : 'value'
} ,
body : 'body { background-color: green; }'
} ) ;
} ) ;
const [ response ] = await Promise . all ( [
page . waitForResponse ( '**/one-style.css' ) ,
page . goto ( server . PREFIX + '/preload.html' )
] ) ;
expect ( await response . headerValue ( 'custom' ) ) . toBe ( 'value' ) ;
await page . waitForFunction ( ( ) = > ( window as any ) . preloadedStyles ) ;
expect ( intercepted ) . toBe ( true ) ;
const color = await page . evaluate ( ( ) = > window . getComputedStyle ( document . body ) . backgroundColor ) ;
expect ( color ) . toBe ( 'rgb(0, 128, 0)' ) ;
} ) ;
2022-12-01 04:26:19 +03:00
it ( 'should fulfill json' , async ( { page , server } ) = > {
await page . route ( '**/*' , route = > {
route . fulfill ( {
status : 201 ,
headers : {
foo : 'bar'
} ,
json : { bar : 'baz' } ,
} ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( response . status ( ) ) . toBe ( 201 ) ;
expect ( response . headers ( ) [ 'content-type' ] ) . toBe ( 'application/json' ) ;
expect ( await page . evaluate ( ( ) = > document . body . textContent ) ) . toBe ( JSON . stringify ( { bar : 'baz' } ) ) ;
} ) ;