import { areQueryStringSimilar , getHarDataFrom , HarEntry , HarHeader , harHeadersToHttpHeaders , HarPostData , hasQueryString , HttpHeaders , HttpRequestMethod , urlToPathWithoutQueryString , } from '../../../utils' ; import { Page , Request , Route } from 'playwright' ; function getAllHarEntries ( harFiles ) { const entries = [ ] ; harFiles . forEach ( ( harFile ) => { const harData = getHarDataFrom ( harFile ) ; entries . push ( ... harData . log . entries ) ; } ) ; return entries ; } export interface HarRequestResponseOptions { memberof filterAllHarEntriesBeforeProcessing : ( entry : HarEntry , index : number ) => boolean ; memberof bypassRequestPredicate : ( request : Request ) => boolean ; memberof filterHarEntryByUrl : ( requestUrl : string , harRequestUrl : string , index : number ) => boolean ; memberof filterHarEntryByPostData : ( requestPostData : string | null , harRequestPostData : HarPostData , index : number ) => boolean ; memberof filterHarEntryByQueryString : ( requestUrl : string , harRequestUrl : string , index : number ) => boolean ; memberof filterHarEntryByResponseStatus : ( status : number ) => boolean ; memberof selectEntryFromFoundHarEntries : ( entries : HarEntry [ ] , requestedUrl : string ) => HarEntry ; memberof onHarEntryNotFound : ( allEntries : HarEntry [ ] , requestedUrl : string , requestedMethod : HttpRequestMethod ) => void ; memberof onHarEntryFound : ( foundEntry : HarEntry , requestedUrl : string , requestedMethod : HttpRequestMethod ) => void ; memberof enrichResponseHeaders : ( headers : HttpHeaders ) => HttpHeaders ; memberof handleRouteOnHarEntryNotFound : ( route : Route , request : Request , entries : HarEntry [ ] ) => void ; memberof provideEntryOnHarEntryNotFound : ( request : Request , entries : HarEntry [ ] ) => HarEntry | null ; } export const defaultHarRequestResponseOptions = { filterAllHarEntriesBeforeProcessing : ( ) => true , bypassRequestPredicate : ( ) => false , filterHarEntryByUrl : ( requestUrl , harRequestUrl ) => urlToPathWithoutQueryString ( requestUrl ) === urlToPathWithoutQueryString ( harRequestUrl ) , filterHarEntryByResponseStatus : ( status ) => status >= 200 && status < 300 , selectEntryFromFoundHarEntries : ( entries ) => entries . pop ( ) ! , onHarEntryNotFound : ( ) => { } , onHarEntryFound : ( ) => { } , enrichResponseHeaders : ( headers ) => headers , filterHarEntryByPostData : ( requestPostData , harPostData ) => requestPostData === harPostData . text , filterHarEntryByQueryString : ( requestUrl , harRequestUrl ) => areQueryStringSimilar ( requestUrl , harRequestUrl ) , handleRouteOnHarEntryNotFound : ( route ) => route . continue ( ) , provideEntryOnHarEntryNotFound : ( ) => null , } ; export async function onRequestToRespondFromHar ( url , harFiles , page , options ) { if ( ! page ) { throw new Error ( `Cannot intercept requests to '${ url }' because no browser has been launched` ) ; } if ( ! Array . isArray ( harFiles ) ) { throw new Error ( `Cannot intercept requests to '${ url }' because no HAR file(s) has been provided. You must provide HAR file(s) inside an array.` ) ; } if ( Array . isArray ( harFiles ) && harFiles . length === 0 ) { throw new Error ( `Cannot intercept requests to '${ url }' because no HAR file(s) has been provided. You must provide at least one HAR file.` ) ; } const allEntries = getAllHarEntries ( harFiles ) . filter ( options . filterAllHarEntriesBeforeProcessing ) ; await page . route ( ( uri ) => { return uri . toString ( ) . includes ( url ) ; } , ( route , request ) => { if ( options . bypassRequestPredicate ( request ) ) { route . continue ( ) ; return ; } const httpMethod = request . method ( ) as HttpRequestMethod ; const requestedUrl = request . url ( ) ; const requestedPostData = request . postData ( ) ; const entries = allEntries . filter ( ( entry ) => entry . request . method === httpMethod ) . filter ( ( entry , index ) => options . filterHarEntryByUrl ( entry . request . url , requestedUrl , index ) ) . filter ( ( entry ) => entry . response ) . filter ( ( entry ) => options . filterHarEntryByResponseStatus ( entry . response . status ) ) . filter ( ( entry , index ) => { if ( hasQueryString ( requestedUrl ) && hasQueryString ( entry . request . url ) ) { return options . filterHarEntryByQueryString ( requestedUrl , entry . request . url , index ) ; } return true ; } ) . filter ( ( entry , index ) => { if ( ! entry . request . postData && ! requestedPostData ) { return true ; } if ( entry . request . postData && requestedPostData !== null ) { return options . filterHarEntryByPostData ( requestedPostData , entry . request . postData , index ) ; } switch ( httpMethod ) { case 'CONNECT' : case 'GET' : case 'HEAD' : case 'OPTIONS' : case 'TRACE' : case 'DELETE' : return true ; default : return false ; } } ) ; if ( entries . length === 0 ) { const providedEntry = options . provideEntryOnHarEntryNotFound ( request , allEntries ) ; if ( providedEntry ) { entries . push ( providedEntry ) ; } } if ( entries . length === 0 ) { options . onHarEntryNotFound ( allEntries , requestedUrl , httpMethod ) ; options . handleRouteOnHarEntryNotFound ( route , request , allEntries ) ; return ; } let foundEntry = entries [ 0 ] ; if ( entries . length > 1 ) { foundEntry = options . selectEntryFromFoundHarEntries ( entries , requestedUrl ) ; } options . onHarEntryFound ( foundEntry , requestedUrl , httpMethod ) ; const headers = buildHeadersFrom ( foundEntry . response . headers ) ; const enrichedHeaders = options . enrichResponseHeaders ( headers ) ; route . fulfill ( { headers : enrichedHeaders , status : foundEntry . response . status , contentType : foundEntry . response . content . mimeType , body : foundEntry . response . content . encoding === 'base64' ? Buffer . from ( foundEntry . response . content . text , 'base64' ) : foundEntry . response . content . text , } ) ; return ; } ) ; } function buildHeadersFrom ( harHeaders ) { const headers = harHeadersToHttpHeaders ( harHeaders ) ; if ( ! hasAccessControlAllowCredentialsHeader ( headers ) ) { headers [ 'Access-Control-Allow-Credentials' ] = 'true' ; } if ( ! hasAccessControlAllowOriginHeader ( headers ) ) { headers [ 'Access-Control-Allow-Origin' ] = '*' ; } return headers ; } function hasAccessControlAllowCredentialsHeader ( headers ) { if ( headers [ 'access-control-allow-credentials' ] ) { return true ; } if ( headers [ 'Access-Control-Allow-Credentials' ] ) { return true ; } return false ; } function hasAccessControlAllowOriginHeader ( headers ) { if ( headers [ 'access-control-allow-origin' ] ) { return true ; } if ( headers [ 'Access-Control-Allow-Origin' ] ) { return true ; } return false ; }