2020-08-04 07:30:37 +08: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 .
* /
2021-05-06 22:08:22 +08:00
import { test as it , expect } from './pageTest' ;
2021-05-06 10:10:28 +08:00
import { attachFrame } from '../config/utils' ;
2020-08-04 07:30:37 +08:00
2021-09-28 00:58:08 +08:00
it ( 'should fire for navigation requests' , async ( { page , server } ) = > {
2020-08-04 07:30:37 +08:00
const requests = [ ] ;
page . on ( 'request' , request = > requests . push ( request ) ) ;
await page . goto ( server . EMPTY_PAGE ) ;
expect ( requests . length ) . toBe ( 1 ) ;
} ) ;
2021-09-28 00:58:08 +08:00
it ( 'should fire for iframes' , async ( { page , server } ) = > {
2020-08-04 07:30:37 +08:00
const requests = [ ] ;
page . on ( 'request' , request = > requests . push ( request ) ) ;
await page . goto ( server . EMPTY_PAGE ) ;
2020-09-19 06:52:14 +08:00
await attachFrame ( page , 'frame1' , server . EMPTY_PAGE ) ;
2020-08-04 07:30:37 +08:00
expect ( requests . length ) . toBe ( 2 ) ;
} ) ;
2021-09-28 00:58:08 +08:00
it ( 'should fire for fetches' , async ( { page , server } ) = > {
2020-08-04 07:30:37 +08:00
const requests = [ ] ;
page . on ( 'request' , request = > requests . push ( request ) ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > fetch ( '/empty.html' ) ) ;
expect ( requests . length ) . toBe ( 2 ) ;
} ) ;
2025-01-27 22:39:59 +08:00
it ( 'should fire for fetches with keepalive: true' , {
annotation : {
type : 'issue' ,
description : 'https://github.com/microsoft/playwright/issues/34497'
}
} , async ( { page , server , browserName } ) = > {
const requests = [ ] ;
page . on ( 'request' , request = > requests . push ( request ) ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > fetch ( '/empty.html' , { keepalive : true } ) ) ;
expect ( requests . length ) . toBe ( 2 ) ;
} ) ;
2021-10-02 10:40:47 +08:00
it ( 'should report requests and responses handled by service worker' , async ( { page , server , isAndroid , isElectron } ) = > {
2021-04-09 22:59:09 +08:00
it . fixme ( isAndroid ) ;
2021-10-02 10:40:47 +08:00
it . fixme ( isElectron ) ;
2021-04-03 05:23:42 +08:00
2020-08-04 07:30:37 +08:00
await page . goto ( server . PREFIX + '/serviceworkers/fetchdummy/sw.html' ) ;
2020-08-28 19:20:29 +08:00
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
2023-09-28 05:09:56 +08:00
const [ request , swResponse ] = await Promise . all ( [
2020-08-04 07:30:37 +08:00
page . waitForEvent ( 'request' ) ,
2023-09-28 05:09:56 +08:00
page . evaluate ( ( ) = > window [ 'fetchDummy' ] ( 'foo' ) ) ,
2020-08-04 07:30:37 +08:00
] ) ;
expect ( swResponse ) . toBe ( 'responseFromServiceWorker:foo' ) ;
expect ( request . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/foo' ) ;
2023-06-09 01:33:28 +08:00
expect ( request . serviceWorker ( ) ) . toBe ( null ) ;
2020-08-04 07:30:37 +08:00
const response = await request . response ( ) ;
expect ( response . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/foo' ) ;
expect ( await response . text ( ) ) . toBe ( 'responseFromServiceWorker:foo' ) ;
2023-06-09 01:33:28 +08:00
expect ( response . fromServiceWorker ( ) ) . toBe ( true ) ;
const [ failedRequest ] = await Promise . all ( [
page . waitForEvent ( 'requestfailed' ) ,
page . evaluate ( ( ) = > window [ 'fetchDummy' ] ( 'error' ) ) . catch ( e = > e ) ,
] ) ;
expect ( failedRequest . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/error' ) ;
expect ( failedRequest . failure ( ) ) . not . toBe ( null ) ;
expect ( failedRequest . serviceWorker ( ) ) . toBe ( null ) ;
expect ( await failedRequest . response ( ) ) . toBe ( null ) ;
} ) ;
2023-12-05 05:02:00 +08:00
it ( 'should report requests and responses handled by service worker with routing' , async ( { page , server , isAndroid , isElectron , mode , browserName , platform } ) = > {
2023-06-09 01:33:28 +08:00
it . fixme ( isAndroid ) ;
it . fixme ( isElectron ) ;
2023-08-01 02:24:04 +08:00
it . fixme ( mode . startsWith ( 'service' ) && platform === 'linux' , 'Times out for no clear reason' ) ;
2023-06-09 01:33:28 +08:00
2023-12-05 05:02:00 +08:00
const interceptedUrls = [ ] ;
await page . route ( '**/*' , route = > {
interceptedUrls . push ( route . request ( ) . url ( ) ) ;
void route . continue ( ) ;
} ) ;
2023-06-09 01:33:28 +08:00
await page . goto ( server . PREFIX + '/serviceworkers/fetchdummy/sw.html' ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
const [ swResponse , request ] = await Promise . all ( [
page . evaluate ( ( ) = > window [ 'fetchDummy' ] ( 'foo' ) ) ,
page . waitForEvent ( 'request' ) ,
] ) ;
expect ( swResponse ) . toBe ( 'responseFromServiceWorker:foo' ) ;
expect ( request . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/foo' ) ;
expect ( request . serviceWorker ( ) ) . toBe ( null ) ;
const response = await request . response ( ) ;
expect ( response . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/foo' ) ;
expect ( await response . text ( ) ) . toBe ( 'responseFromServiceWorker:foo' ) ;
const [ failedRequest ] = await Promise . all ( [
page . waitForEvent ( 'requestfailed' ) ,
page . evaluate ( ( ) = > window [ 'fetchDummy' ] ( 'error' ) ) . catch ( e = > e ) ,
] ) ;
expect ( failedRequest . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/fetchdummy/error' ) ;
expect ( failedRequest . failure ( ) ) . not . toBe ( null ) ;
expect ( failedRequest . serviceWorker ( ) ) . toBe ( null ) ;
expect ( await failedRequest . response ( ) ) . toBe ( null ) ;
2023-12-05 05:02:00 +08:00
const expectedUrls = [ server . PREFIX + '/serviceworkers/fetchdummy/sw.html' ] ;
if ( browserName === 'webkit' )
expectedUrls . push ( server . PREFIX + '/serviceworkers/fetchdummy/sw.js' ) ;
expect ( interceptedUrls ) . toEqual ( expectedUrls ) ;
2023-06-09 01:33:28 +08:00
} ) ;
2024-06-21 06:43:26 +08:00
it ( 'should report navigation requests and responses handled by service worker' , async ( { page , server , isAndroid , browserName } ) = > {
2023-06-09 01:33:28 +08:00
it . fixme ( isAndroid ) ;
await page . goto ( server . PREFIX + '/serviceworkers/stub/sw.html' ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
const reloadResponse = await page . reload ( ) ;
expect ( await page . evaluate ( 'window.fromSW' ) ) . toBe ( true ) ;
expect ( reloadResponse . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/stub/sw.html' ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
if ( browserName !== 'firefox' ) {
// When SW fetch throws, Firefox does not fail the navigation,
// but rather falls back to the real network.
const [ , failedRequest ] = await Promise . all ( [
page . evaluate ( ( ) = > {
window . location . href = '/serviceworkers/stub/error.html' ;
} ) ,
page . waitForEvent ( 'requestfailed' ) ,
] ) ;
expect ( failedRequest . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/stub/error.html' ) ;
expect ( failedRequest . failure ( ) . errorText ) . toContain ( browserName === 'chromium' ? 'net::ERR_FAILED' : 'uh oh' ) ;
expect ( failedRequest . serviceWorker ( ) ) . toBe ( null ) ;
expect ( await failedRequest . response ( ) ) . toBe ( null ) ;
}
} ) ;
2024-06-21 06:43:26 +08:00
it ( 'should report navigation requests and responses handled by service worker with routing' , async ( { page , server , isAndroid , browserName } ) = > {
2023-06-09 01:33:28 +08:00
it . fixme ( isAndroid ) ;
await page . route ( '**/*' , route = > route . continue ( ) ) ;
await page . goto ( server . PREFIX + '/serviceworkers/stub/sw.html' ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
const reloadResponse = await page . reload ( ) ;
expect ( await page . evaluate ( 'window.fromSW' ) ) . toBe ( true ) ;
expect ( reloadResponse . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/stub/sw.html' ) ;
await page . evaluate ( ( ) = > window [ 'activationPromise' ] ) ;
if ( browserName !== 'firefox' ) {
// When SW fetch throws, Firefox does not fail the navigation,
// but rather falls back to the real network.
const [ , failedRequest ] = await Promise . all ( [
page . evaluate ( ( ) = > {
window . location . href = '/serviceworkers/stub/error.html' ;
2023-07-26 21:50:38 +08:00
// eslint-disable-next-line
undefined
2023-06-09 01:33:28 +08:00
} ) ,
page . waitForEvent ( 'requestfailed' ) ,
] ) ;
expect ( failedRequest . url ( ) ) . toBe ( server . PREFIX + '/serviceworkers/stub/error.html' ) ;
expect ( failedRequest . failure ( ) . errorText ) . toContain ( browserName === 'chromium' ? 'net::ERR_FAILED' : 'uh oh' ) ;
expect ( failedRequest . serviceWorker ( ) ) . toBe ( null ) ;
expect ( await failedRequest . response ( ) ) . toBe ( null ) ;
}
2020-08-04 07:30:37 +08:00
} ) ;
2021-09-29 00:54:05 +08:00
it ( 'should return response body when Cross-Origin-Opener-Policy is set' , async ( { page , server , browserName } ) = > {
server . setRoute ( '/empty.html' , ( req , res ) = > {
res . setHeader ( 'Cross-Origin-Opener-Policy' , 'same-origin' ) ;
res . end ( 'Hello there!' ) ;
} ) ;
const response = await page . goto ( server . EMPTY_PAGE ) ;
expect ( page . url ( ) ) . toBe ( server . EMPTY_PAGE ) ;
await response . finished ( ) ;
expect ( response . request ( ) . failure ( ) ) . toBeNull ( ) ;
expect ( await response . text ( ) ) . toBe ( 'Hello there!' ) ;
} ) ;
2021-11-13 11:06:53 +08:00
it ( 'should fire requestfailed when intercepting race' , async ( { page , server , browserName } ) = > {
2021-12-02 23:44:13 +08:00
it . skip ( browserName !== 'chromium' , 'This test is specifically testing Chromium race' ) ;
2023-10-05 10:56:42 +08:00
const promise = new Promise < void > ( resolve = > {
2021-11-13 11:06:53 +08:00
let counter = 0 ;
const failures = new Set ( ) ;
const alive = new Set ( ) ;
page . on ( 'request' , request = > {
expect ( alive . has ( request ) ) . toBe ( false ) ;
expect ( failures . has ( request ) ) . toBe ( false ) ;
alive . add ( request ) ;
} ) ;
page . on ( 'requestfailed' , request = > {
expect ( failures . has ( request ) ) . toBe ( false ) ;
expect ( alive . has ( request ) ) . toBe ( true ) ;
alive . delete ( request ) ;
failures . add ( request ) ;
if ( ++ counter === 10 )
resolve ( ) ;
} ) ;
} ) ;
// Stall requests to make sure we don't get requestfinished.
2023-12-16 01:00:12 +08:00
await page . route ( '**' , route = > { } ) ;
2021-11-13 11:06:53 +08:00
await page . setContent ( `
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< iframe src = "${server.EMPTY_PAGE}" > < / iframe >
< script >
function abortAll() {
const frames = document . querySelectorAll ( "iframe" ) ;
for ( const frame of frames )
frame . src = "about:blank" ;
}
2021-12-02 23:44:13 +08:00
abortAll ( ) ;
2021-11-13 11:06:53 +08:00
< / script >
` );
2023-10-05 10:56:42 +08:00
await promise ;
2021-11-13 11:06:53 +08:00
} ) ;
2023-05-20 08:59:17 +08:00
2023-05-26 00:43:41 +08:00
it ( 'main resource xhr should have type xhr' , async ( { page , server } ) = > {
2023-05-20 08:59:17 +08:00
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/22812' } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const [ request ] = await Promise . all ( [
page . waitForEvent ( 'request' ) ,
page . evaluate ( ( ) = > {
const x = new XMLHttpRequest ( ) ;
x . open ( 'GET' , location . href , false ) ;
x . send ( ) ;
} )
] ) ;
expect ( request . isNavigationRequest ( ) ) . toBe ( false ) ;
expect ( request . resourceType ( ) ) . toBe ( 'xhr' ) ;
} ) ;
2024-09-24 05:30:40 +08:00
it ( 'should finish 204 request' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/32752' }
} , async ( { page , server , browserName } ) = > {
it . fixme ( browserName === 'chromium' ) ;
server . setRoute ( '/204' , ( req , res ) = > {
res . writeHead ( 204 , { 'Content-type' : 'text/plain' } ) ;
res . end ( ) ;
} ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const reqPromise = Promise . race ( [
page . waitForEvent ( 'requestfailed' , r = > r . url ( ) . endsWith ( '/204' ) ) . then ( ( ) = > 'requestfailed' ) ,
page . waitForEvent ( 'requestfinished' , r = > r . url ( ) . endsWith ( '/204' ) ) . then ( ( ) = > 'requestfinished' ) ,
] ) ;
page . evaluate ( async url = > { await fetch ( url ) ; } , server . PREFIX + '/204' ) . catch ( ( ) = > { } ) ;
expect ( await reqPromise ) . toBe ( 'requestfinished' ) ;
} ) ;
2024-10-21 17:14:48 +08:00
it ( '<picture> resource should have type image' , async ( { page } ) = > {
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/33148' } ) ;
const [ request ] = await Promise . all ( [
page . waitForEvent ( 'request' ) ,
page . setContent ( `
< picture >
< source >
< img src = "https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2@2x.png" >
< / source >
< / picture >
` )
] ) ;
expect ( request . resourceType ( ) ) . toBe ( 'image' ) ;
2024-12-21 01:17:09 +08:00
} ) ;
2025-07-01 06:05:53 +08:00
// Chromium: requestWillBeSentEvent.frameId is undefined for OPTIONS.
// WebKit: no requestWillBeSent event in the protocol for OPTIONS (at least on Mac).
// Firefox: OPTIONS request can be dispatched.
it ( 'should not expose preflight OPTIONS request' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/36311' }
} , async ( { page , server , browserName } ) = > {
const serverRequests = [ ] ;
server . setRoute ( '/cors' , ( req , res ) = > {
serverRequests . push ( ` ${ req . method } ${ req . url } ` ) ;
if ( req . method === 'OPTIONS' ) {
res . writeHead ( 204 , {
'Access-Control-Allow-Origin' : '*' ,
'Access-Control-Allow-Methods' : 'GET, POST, PUT, OPTIONS' ,
'Access-Control-Allow-Headers' : '*' ,
} ) ;
res . end ( ) ;
return ;
}
res . writeHead ( 200 , { 'Content-type' : 'text/plain' , 'Access-Control-Allow-Origin' : '*' } ) ;
res . end ( 'Hello there!' ) ;
} ) ;
const clientRequests = [ ] ;
page . on ( 'request' , request = > {
clientRequests . push ( ` ${ request . method ( ) } ${ request . url ( ) } ` ) ;
} ) ;
const response = await page . evaluate ( async url = > {
const response = await fetch ( url , {
method : 'POST' ,
body : '' ,
headers : {
'Content-Type' : 'application/json' ,
'X-Custom-Header' : 'test-value'
}
} ) ;
return await response . text ( ) ;
} , server . CROSS_PROCESS_PREFIX + '/cors' ) . catch ( ( ) = > { } ) ;
expect ( response ) . toBe ( 'Hello there!' ) ;
expect ( serverRequests ) . toEqual ( [
'OPTIONS /cors' ,
'POST /cors' ,
] ) ;
expect ( clientRequests ) . toEqual ( [
` POST ${ server . CROSS_PROCESS_PREFIX } /cors ` ,
] ) ;
} ) ;
it ( 'should not expose preflight OPTIONS request with network interception' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/36311' }
} , async ( { page , server , browserName } ) = > {
const serverRequests = [ ] ;
server . setRoute ( '/cors' , ( req , res ) = > {
serverRequests . push ( ` ${ req . method } ${ req . url } ` ) ;
if ( req . method === 'OPTIONS' ) {
res . writeHead ( 204 , {
'Access-Control-Allow-Origin' : '*' ,
'Access-Control-Allow-Methods' : 'GET, POST, PUT, OPTIONS' ,
'Access-Control-Allow-Headers' : '*' ,
} ) ;
res . end ( ) ;
return ;
}
res . writeHead ( 200 , { 'Content-type' : 'text/plain' , 'Access-Control-Allow-Origin' : '*' } ) ;
res . end ( 'Hello there!' ) ;
} ) ;
await page . route ( '**/*' , route = > route . continue ( ) ) ;
const clientRequests = [ ] ;
page . on ( 'request' , request = > {
clientRequests . push ( ` ${ request . method ( ) } ${ request . url ( ) } ` ) ;
} ) ;
const response = await page . evaluate ( async url = > {
const response = await fetch ( url , {
method : 'POST' ,
body : '' ,
headers : {
'Content-Type' : 'application/json' ,
'X-Custom-Header' : 'test-value'
}
} ) ;
return await response . text ( ) ;
} , server . CROSS_PROCESS_PREFIX + '/cors' ) . catch ( ( ) = > { } ) ;
expect ( response ) . toBe ( 'Hello there!' ) ;
expect . soft ( serverRequests ) . toEqual ( [
. . . ( browserName !== 'chromium' ? [ 'OPTIONS /cors' ] : [ ] ) ,
'POST /cors' ,
] ) ;
expect . soft ( clientRequests ) . toEqual ( [
` POST ${ server . CROSS_PROCESS_PREFIX } /cors ` ,
] ) ;
} ) ;
2025-09-20 21:17:56 +08:00
it ( 'should return last requests' , async ( { page , server } ) = > {
await page . goto ( server . PREFIX + '/title.html' ) ;
for ( let i = 0 ; i < 200 ; ++ i )
server . setRoute ( '/fetch?' + i , ( req , res ) = > res . end ( 'url:' + server . PREFIX + req . url ) ) ;
// #0 is the navigation request, so start with #1.
for ( let i = 1 ; i < 50 ; ++ i )
await page . evaluate ( url = > fetch ( url ) , server . PREFIX + '/fetch?' + i ) ;
const first50Requests = await page . requests ( ) ;
const firstReponse = await first50Requests [ 1 ] . response ( ) ;
expect ( await firstReponse . text ( ) ) . toBe ( 'url:' + server . PREFIX + '/fetch?1' ) ;
page . on ( 'request' , ( ) = > { } ) ;
for ( let i = 50 ; i < 100 ; ++ i )
await page . evaluate ( url = > fetch ( url ) , server . PREFIX + '/fetch?' + i ) ;
const first100Requests = await page . requests ( ) ;
for ( let i = 100 ; i < 200 ; ++ i )
await page . evaluate ( url = > fetch ( url ) , server . PREFIX + '/fetch?' + i ) ;
const last100Requests = await page . requests ( ) ;
// Last 100 requests are fully functional.
const received = await Promise . all ( last100Requests . map ( async request = > {
const response = await request . response ( ) ;
return { text : await response . text ( ) , url : request.url ( ) } ;
} ) ) ;
const expected = [ ] ;
for ( let i = 100 ; i < 200 ; ++ i ) {
const url = server . PREFIX + '/fetch?' + i ;
expected . push ( { url , text : 'url:' + url } ) ;
}
expect ( received ) . toEqual ( expected ) ;
// First 50 requests were collected.
const error = await first50Requests [ 1 ] . response ( ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'request.response: The object has been collected to prevent unbounded heap growth.' ) ;
// Second 50 requests are functional, because they were reported through the event and not collected.
const reponse50 = await first100Requests [ 50 ] . response ( ) ;
expect ( await reponse50 . text ( ) ) . toBe ( 'url:' + server . PREFIX + '/fetch?50' ) ;
} ) ;