@@ -21,12 +21,31 @@ interface FetchOptions {
2121 [ key : string ] : unknown ;
2222}
2323
24+ /**
25+ * Resolves a potentially relative URL to an absolute URL
26+ */
27+ function resolveUrl ( inputUrl : string , responseUrl : string ) : string {
28+ // If response URL is absolute, use it
29+ if ( responseUrl . startsWith ( 'http://' ) || responseUrl . startsWith ( 'https://' ) ) {
30+ return responseUrl ;
31+ }
32+
33+ // If input URL is absolute, use it
34+ if ( inputUrl . startsWith ( 'http://' ) || inputUrl . startsWith ( 'https://' ) ) {
35+ return inputUrl ;
36+ }
37+
38+ // Both are relative - return response URL as-is (will be shown as relative path)
39+ return responseUrl ;
40+ }
41+
2442/**
2543 * Creates APIRequestContext wrapper that intercepts requests
2644 */
2745function createApiRequestContextProxy (
2846 context : APIRequestContext ,
29- spy : ApiSpyInstance
47+ spy : ApiSpyInstance ,
48+ baseURL ?: string
3049) : APIRequestContext {
3150 const methodsToIntercept = [ 'get' , 'post' , 'put' , 'patch' , 'delete' , 'head' , 'fetch' ] as const ;
3251
@@ -59,8 +78,16 @@ function createApiRequestContextProxy(
5978 // Execute original request first to get actual URL from response
6079 const response : APIResponse = await ( originalValue as Function ) . call ( target , url , options ) ;
6180
62- // Use response.url() for the actual full URL (handles redirects, base URL, etc.)
63- const actualUrl = response . url ( ) ;
81+ // Get URL - response.url() should be absolute, but may be relative in some cases
82+ let actualUrl = response . url ( ) ;
83+
84+ // If response URL is relative and we have baseURL, construct full URL
85+ if ( baseURL && ! actualUrl . startsWith ( 'http://' ) && ! actualUrl . startsWith ( 'https://' ) ) {
86+ // Construct full URL from baseURL
87+ const base = baseURL . endsWith ( '/' ) ? baseURL . slice ( 0 , - 1 ) : baseURL ;
88+ const path = actualUrl . startsWith ( '/' ) ? actualUrl : '/' + actualUrl ;
89+ actualUrl = base + path ;
90+ }
6491
6592 // Capture request with actual URL
6693 const capturedRequest = await spy . captureRequest ( method , actualUrl , {
@@ -154,8 +181,11 @@ export const test = base.extend<ApiSpyFixtures>({
154181 line : testInfo . line ,
155182 } ) ;
156183
184+ // Get baseURL from project config
185+ const baseURL = testInfo . project . use . baseURL ;
186+
157187 // Replace original request context with proxy
158- const proxiedRequest = createApiRequestContextProxy ( request , spy ) ;
188+ const proxiedRequest = createApiRequestContextProxy ( request , spy , baseURL ) ;
159189
160190 // Replace request in testInfo so tests use proxy
161191 // @ts -expect-error - modifying private field
@@ -170,21 +200,31 @@ export const test = base.extend<ApiSpyFixtures>({
170200
171201 // Attach to Playwright report if enabled
172202 if ( config . attachToPlaywrightReport && entries . length > 0 ) {
173- // Attach requests
174- const requests = entries . map ( e => e . request ) ;
175- await testInfo . attach ( 'requests' , {
176- body : Buffer . from ( JSON . stringify ( requests , null , 2 ) ) ,
177- contentType : 'application/json' ,
178- } ) ;
179-
180- // Attach responses
181- const responses = entries
182- . filter ( e => e . response )
183- . map ( e => ( { requestId : e . request . id , ...e . response } ) ) ;
184- await testInfo . attach ( 'responses' , {
185- body : Buffer . from ( JSON . stringify ( responses , null , 2 ) ) ,
186- contentType : 'application/json' ,
187- } ) ;
203+ for ( const entry of entries ) {
204+ const { request, response, error } = entry ;
205+
206+ // Attach request
207+ await testInfo . attach ( `Request ${ request . method } ${ request . url } ` , {
208+ body : Buffer . from ( JSON . stringify ( request , null , 2 ) ) ,
209+ contentType : 'application/json' ,
210+ } ) ;
211+
212+ // Attach response if exists
213+ if ( response ) {
214+ await testInfo . attach ( `Response ${ response . status } ${ request . method } ${ request . url } ` , {
215+ body : Buffer . from ( JSON . stringify ( response , null , 2 ) ) ,
216+ contentType : 'application/json' ,
217+ } ) ;
218+ }
219+
220+ // Attach error if exists
221+ if ( error ) {
222+ await testInfo . attach ( `Error ${ request . method } ${ request . url } ` , {
223+ body : Buffer . from ( JSON . stringify ( error , null , 2 ) ) ,
224+ contentType : 'application/json' ,
225+ } ) ;
226+ }
227+ }
188228 }
189229 } ,
190230} ) ;
@@ -197,6 +237,9 @@ export const testWithApiSpy = base.extend<ApiSpyFixtures & { request: APIRequest
197237 const config = globalApiSpyStore . config ;
198238 const spy = new ApiSpyInstance ( config ) ;
199239
240+ // Store baseURL for request proxy
241+ ( spy as ApiSpyInstance & { _baseURL ?: string } ) . _baseURL = testInfo . project . use . baseURL ;
242+
200243 spy . setTestInfo ( {
201244 title : testInfo . title ,
202245 file : testInfo . file ,
@@ -210,27 +253,38 @@ export const testWithApiSpy = base.extend<ApiSpyFixtures & { request: APIRequest
210253
211254 // Attach to Playwright report if enabled
212255 if ( config . attachToPlaywrightReport && entries . length > 0 ) {
213- // Attach requests
214- const requests = entries . map ( e => e . request ) ;
215- await testInfo . attach ( 'requests' , {
216- body : Buffer . from ( JSON . stringify ( requests , null , 2 ) ) ,
217- contentType : 'application/json' ,
218- } ) ;
219-
220- // Attach responses
221- const responses = entries
222- . filter ( e => e . response )
223- . map ( e => ( { requestId : e . request . id , ...e . response } ) ) ;
224- await testInfo . attach ( 'responses' , {
225- body : Buffer . from ( JSON . stringify ( responses , null , 2 ) ) ,
226- contentType : 'application/json' ,
227- } ) ;
256+ for ( const entry of entries ) {
257+ const { request, response, error } = entry ;
258+
259+ // Attach request
260+ await testInfo . attach ( `Request ${ request . method } ${ request . url } ` , {
261+ body : Buffer . from ( JSON . stringify ( request , null , 2 ) ) ,
262+ contentType : 'application/json' ,
263+ } ) ;
264+
265+ // Attach response if exists
266+ if ( response ) {
267+ await testInfo . attach ( `Response ${ response . status } ${ request . method } ${ request . url } ` , {
268+ body : Buffer . from ( JSON . stringify ( response , null , 2 ) ) ,
269+ contentType : 'application/json' ,
270+ } ) ;
271+ }
272+
273+ // Attach error if exists
274+ if ( error ) {
275+ await testInfo . attach ( `Error ${ request . method } ${ request . url } ` , {
276+ body : Buffer . from ( JSON . stringify ( error , null , 2 ) ) ,
277+ contentType : 'application/json' ,
278+ } ) ;
279+ }
280+ }
228281 }
229282 } ,
230283
231- request : async ( { request, apiSpy } , use ) => {
284+ request : async ( { request, apiSpy } , use , testInfo ) => {
232285 // Create proxy for request context
233- const proxiedRequest = createApiRequestContextProxy ( request , apiSpy as ApiSpyInstance ) ;
286+ const baseURL = testInfo . project . use . baseURL ;
287+ const proxiedRequest = createApiRequestContextProxy ( request , apiSpy as ApiSpyInstance , baseURL ) ;
234288 await use ( proxiedRequest ) ;
235289 } ,
236290} ) ;
0 commit comments