11import fs from 'fs' ;
22import path from 'path' ;
33import { Browser , BrowserContext , Page } from "@playwright/test"
4- import { Context } from "../types.js"
4+ import { Context , DiscoveryErrors } from "../types.js"
55import * as utils from "./utils.js"
66import constants from './constants.js'
77import chalk from 'chalk' ;
@@ -10,9 +10,9 @@ import sharp from 'sharp';
1010async function captureScreenshotsForConfig (
1111 ctx : Context ,
1212 browsers : Record < string , Browser > ,
13- urlConfig : Record < string , any > ,
13+ urlConfig : Record < string , any > ,
1414 browserName : string ,
15- renderViewports : Array < Record < string , any > >
15+ renderViewports : Array < Record < string , any > >
1616) : Promise < void > {
1717 ctx . log . debug ( `*** urlConfig ${ JSON . stringify ( urlConfig ) } ` ) ;
1818
@@ -22,7 +22,18 @@ async function captureScreenshotsForConfig(
2222 let beforeSnapshotScript = execute ?. beforeSnapshot ;
2323 let waitUntilEvent = pageEvent || process . env . SMARTUI_PAGE_WAIT_UNTIL_EVENT || 'load' ;
2424
25- let pageOptions = { waitUntil : waitUntilEvent , timeout : ctx . config . waitForPageRender || constants . DEFAULT_PAGE_LOAD_TIMEOUT } ;
25+ let discoveryErrors : DiscoveryErrors = {
26+ name : "" ,
27+ url : "" ,
28+ timestamp : "" ,
29+ snapshotUUID : "" ,
30+ browsers : { }
31+ } ;
32+
33+ let globalViewport = ""
34+ let globalBrowser = constants . CHROME
35+
36+ let pageOptions = { waitUntil : waitUntilEvent , timeout : ctx . config . waitForPageRender || constants . DEFAULT_PAGE_LOAD_TIMEOUT } ;
2637 ctx . log . debug ( `url: ${ url } pageOptions: ${ JSON . stringify ( pageOptions ) } ` ) ;
2738 let ssId = name . toLowerCase ( ) . replace ( / \s / g, '_' ) ;
2839 let context : BrowserContext ;
@@ -106,8 +117,8 @@ async function captureScreenshotsForConfig(
106117 else if ( browserName == constants . SAFARI ) contextOptions . userAgent = constants . SAFARI_USER_AGENT ;
107118 else if ( browserName == constants . EDGE ) contextOptions . userAgent = constants . EDGE_USER_AGENT ;
108119 if ( ctx . config . userAgent || userAgent ) {
109- if ( ctx . config . userAgent !== "" ) {
110- contextOptions . userAgent = ctx . config . userAgent ;
120+ if ( ctx . config . userAgent !== "" ) {
121+ contextOptions . userAgent = ctx . config . userAgent ;
111122 }
112123 if ( userAgent && userAgent !== "" ) {
113124 contextOptions . userAgent = userAgent ;
@@ -155,27 +166,112 @@ async function captureScreenshotsForConfig(
155166 await page . setExtraHTTPHeaders ( headersObject ) ;
156167 }
157168
169+
170+ if ( ctx . env . CAPTURE_RENDERING_ERRORS ) {
171+ await page . route ( '**/*' , async ( route , request ) => {
172+ const requestUrl = request . url ( )
173+ const requestHostname = new URL ( requestUrl ) . hostname ;
174+ let requestOptions : Record < string , any > = {
175+ timeout : 30000 ,
176+ headers : {
177+ ...await request . allHeaders ( ) ,
178+ ...constants . REQUEST_HEADERS
179+ }
180+ }
181+
182+ try {
183+
184+ // get response
185+ let response , body ;
186+ response = await page . request . fetch ( request , requestOptions ) ;
187+ body = await response . body ( ) ;
188+
189+ let data = {
190+ statusCode : `${ response . status ( ) } ` ,
191+ url : requestUrl ,
192+ }
193+
194+ if ( ( response . status ( ) >= 400 && response . status ( ) < 600 ) && response . status ( ) !== 0 ) {
195+ if ( ! discoveryErrors . browsers [ globalBrowser ] ) {
196+ discoveryErrors . browsers [ globalBrowser ] = { } ;
197+ }
198+
199+ // Check if the discoveryErrors.browsers[globalBrowser] exists, and if not, initialize it
200+ if ( discoveryErrors . browsers [ globalBrowser ] && ! discoveryErrors . browsers [ globalBrowser ] [ globalViewport ] ) {
201+ discoveryErrors . browsers [ globalBrowser ] [ globalViewport ] = [ ] ;
202+ }
203+
204+ // Dynamically push the data into the correct browser and viewport
205+ if ( discoveryErrors . browsers [ globalBrowser ] ) {
206+ discoveryErrors . browsers [ globalBrowser ] [ globalViewport ] ?. push ( data as any ) ;
207+ }
208+
209+ ctx . build . hasDiscoveryError = true ;
210+ }
211+
212+ // Continue the request with the fetched response
213+ route . fulfill ( {
214+ status : response . status ( ) ,
215+ headers : response . headers ( ) ,
216+ body : body ,
217+ } ) ;
218+ } catch ( error : any ) {
219+ ctx . log . debug ( `Handling request ${ requestUrl } \n - aborted due to ${ error . message } ` ) ;
220+ route . abort ( ) ;
221+ }
222+ } ) ;
223+ }
224+
225+ if ( renderViewports && renderViewports . length > 0 ) {
226+ const first = renderViewports [ 0 ] ;
227+ globalViewport = first . viewportString ;
228+ globalBrowser = browserName ;
229+ if ( globalViewport . toLowerCase ( ) . includes ( "iphone" ) || globalViewport . toLowerCase ( ) . includes ( "ipad" ) ) {
230+ globalBrowser = constants . WEBKIT ;
231+ }
232+ }
233+
234+ if ( browserName == constants . SAFARI || ( globalViewport . toLowerCase ( ) . includes ( "iphone" ) || globalViewport . toLowerCase ( ) . includes ( "ipad" ) ) ) {
235+ globalBrowser = constants . WEBKIT ;
236+ }
237+
158238 await page ?. goto ( url . trim ( ) , pageOptions ) ;
159239 await executeDocumentScripts ( ctx , page , "afterNavigation" , afterNavigationScript )
160240
161241 for ( let { viewport, viewportString, fullPage } of renderViewports ) {
242+ globalViewport = viewportString ;
243+ globalBrowser = browserName
244+ ctx . log . debug ( `globalViewport : ${ globalViewport } ` ) ;
245+ if ( browserName == constants . SAFARI || ( globalViewport . toLowerCase ( ) . includes ( "iphone" ) || globalViewport . toLowerCase ( ) . includes ( "ipad" ) ) ) {
246+ globalBrowser = constants . WEBKIT ;
247+ }
162248 let ssPath = `screenshots/${ ssId } /${ `${ browserName } -${ viewport . width } x${ viewport . height } ` } -${ ssId } .png` ;
163249 await page ?. setViewportSize ( { width : viewport . width , height : viewport . height || constants . MIN_VIEWPORT_HEIGHT } ) ;
164250 if ( fullPage ) await page ?. evaluate ( utils . scrollToBottomAndBackToTop ) ;
165251 await page ?. waitForTimeout ( waitForTimeout || 0 ) ;
166252 await executeDocumentScripts ( ctx , page , "beforeSnapshot" , beforeSnapshotScript )
167253
254+ discoveryErrors . name = name ;
255+ discoveryErrors . url = url ;
256+ discoveryErrors . timestamp = new Date ( ) . toISOString ( ) ;
168257 await page ?. screenshot ( { path : ssPath , fullPage } ) ;
169258
170- await ctx . client . uploadScreenshot ( ctx . build , ssPath , name , browserName , viewportString , url , ctx . log ) ;
259+ await ctx . client . uploadScreenshot ( ctx . build , ssPath , name , browserName , viewportString , url , ctx . log , discoveryErrors , ctx ) ;
260+ discoveryErrors = {
261+ name : "" ,
262+ url : "" ,
263+ timestamp : "" ,
264+ snapshotUUID : "" ,
265+ browsers : { }
266+ } ;
171267 }
172268 } catch ( error ) {
173269 throw new Error ( `captureScreenshotsForConfig failed for browser ${ browserName } ; error: ${ error } ` ) ;
174270 } finally {
175271 await page ?. close ( ) ;
176272 await context ?. close ( ) ;
177273 }
178-
274+
179275}
180276
181277async function captureScreenshotsAsync (
@@ -185,8 +281,8 @@ async function captureScreenshotsAsync(
185281) : Promise < void [ ] > {
186282 let capturePromises : Array < Promise < void > > = [ ] ;
187283
188- // capture screenshots for web config
189- if ( ctx . config . web ) {
284+ // capture screenshots for web config
285+ if ( ctx . config . web ) {
190286 for ( let browserName of ctx . config . web . browsers ) {
191287 let webRenderViewports = utils . getWebRenderViewports ( ctx ) ;
192288 capturePromises . push ( captureScreenshotsForConfig ( ctx , browsers , staticConfig , browserName , webRenderViewports ) )
@@ -211,8 +307,8 @@ async function captureScreenshotsSync(
211307 staticConfig : Record < string , any > ,
212308 browsers : Record < string , Browser >
213309) : Promise < void > {
214- // capture screenshots for web config
215- if ( ctx . config . web ) {
310+ // capture screenshots for web config
311+ if ( ctx . config . web ) {
216312 for ( let browserName of ctx . config . web . browsers ) {
217313 let webRenderViewports = utils . getWebRenderViewports ( ctx ) ;
218314 await captureScreenshotsForConfig ( ctx , browsers , staticConfig , browserName , webRenderViewports ) ;
@@ -230,11 +326,11 @@ async function captureScreenshotsSync(
230326 }
231327}
232328
233- export async function captureScreenshots ( ctx : Context ) : Promise < Record < string , any > > {
329+ export async function captureScreenshots ( ctx : Context ) : Promise < Record < string , any > > {
234330 // Clean up directory to store screenshots
235331 utils . delDir ( 'screenshots' ) ;
236332
237- let browsers : Record < string , Browser > = { } ;
333+ let browsers : Record < string , Browser > = { } ;
238334 let capturedScreenshots : number = 0 ;
239335 let output : string = '' ;
240336
@@ -363,7 +459,13 @@ export async function uploadScreenshots(ctx: Context): Promise<void> {
363459 }
364460 }
365461
366- await ctx . client . uploadScreenshot ( ctx . build , filePath , ssId , 'default' , viewport , "" , ctx . log ) ;
462+ await ctx . client . uploadScreenshot ( ctx . build , filePath , ssId , 'default' , viewport , "" , ctx . log , {
463+ name : "" ,
464+ url : "" ,
465+ timestamp : new Date ( ) . toISOString ( ) ,
466+ snapshotUUID : "" ,
467+ browsers : { }
468+ } , ctx ) ;
367469 ctx . log . info ( `${ filePath } : uploaded successfully` )
368470 noOfScreenshots ++ ;
369471 } else {
@@ -374,20 +476,20 @@ export async function uploadScreenshots(ctx: Context): Promise<void> {
374476 }
375477
376478 await processDirectory ( ctx . uploadFilePath ) ;
377- if ( noOfScreenshots == 0 ) {
479+ if ( noOfScreenshots == 0 ) {
378480 ctx . log . info ( `No screenshots uploaded.` ) ;
379481 } else {
380482 ctx . log . info ( `${ noOfScreenshots } screenshots uploaded successfully.` ) ;
381483 }
382484}
383485
384- export async function captureScreenshotsConcurrent ( ctx : Context ) : Promise < Record < string , any > > {
486+ export async function captureScreenshotsConcurrent ( ctx : Context ) : Promise < Record < string , any > > {
385487 // Clean up directory to store screenshots
386488 utils . delDir ( 'screenshots' ) ;
387489
388490 let totalSnapshots = ctx . webStaticConfig && ctx . webStaticConfig . length ;
389491 let browserInstances = ctx . options . parallel || 1 ;
390- let optimizeBrowserInstances : number = 0
492+ let optimizeBrowserInstances : number = 0
391493 optimizeBrowserInstances = Math . floor ( Math . log2 ( totalSnapshots ) ) ;
392494 if ( optimizeBrowserInstances < 1 ) {
393495 optimizeBrowserInstances = 1 ;
@@ -398,11 +500,11 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
398500 }
399501
400502 // If force flag is set, use the requested browser instances
401- if ( ctx . options . force && browserInstances > 1 ) {
503+ if ( ctx . options . force && browserInstances > 1 ) {
402504 optimizeBrowserInstances = browserInstances ;
403505 }
404506
405- let urlsPerInstance : number = 0 ;
507+ let urlsPerInstance : number = 0 ;
406508 if ( optimizeBrowserInstances == 1 ) {
407509 urlsPerInstance = totalSnapshots ;
408510 } else {
@@ -418,9 +520,9 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
418520 let output : any = '' ;
419521
420522 const responses = await Promise . all ( staticURLChunks . map ( async ( urlConfig ) => {
421- let { capturedScreenshots, finalOutput} = await processChunk ( ctx , urlConfig ) ;
523+ let { capturedScreenshots, finalOutput } = await processChunk ( ctx , urlConfig ) ;
422524 return { capturedScreenshots, finalOutput } ;
423- } ) ) ;
525+ } ) ) ;
424526
425527 responses . forEach ( ( response : Record < string , any > ) => {
426528 totalCapturedScreenshots += response . capturedScreenshots ;
@@ -432,17 +534,17 @@ export async function captureScreenshotsConcurrent(ctx: Context): Promise<Record
432534 return { totalCapturedScreenshots, output } ;
433535}
434536
435- function splitURLs ( arr : any , chunkSize : number ) {
537+ function splitURLs ( arr : any , chunkSize : number ) {
436538 const result = [ ] ;
437539 for ( let i = 0 ; i < arr . length ; i += chunkSize ) {
438- result . push ( arr . slice ( i , i + chunkSize ) ) ;
540+ result . push ( arr . slice ( i , i + chunkSize ) ) ;
439541 }
440542 return result ;
441543}
442544
443- async function processChunk ( ctx : Context , urlConfig : Array < Record < string , any > > ) : Promise < Record < string , any > > {
444-
445- let browsers : Record < string , Browser > = { } ;
545+ async function processChunk ( ctx : Context , urlConfig : Array < Record < string , any > > ) : Promise < Record < string , any > > {
546+
547+ let browsers : Record < string , Browser > = { } ;
446548 let capturedScreenshots : number = 0 ;
447549 let finalOutput : string = '' ;
448550
@@ -454,13 +556,13 @@ async function processChunk(ctx: Context, urlConfig: Array<Record<string, any>>)
454556 throw new Error ( `Failed launching browsers ${ error } ` ) ;
455557 }
456558
457- for ( let staticConfig of urlConfig ) {
559+ for ( let staticConfig of urlConfig ) {
458560 try {
459561 await captureScreenshotsAsync ( ctx , staticConfig , browsers ) ;
460562
461563 utils . delDir ( `screenshots/${ staticConfig . name . toLowerCase ( ) . replace ( / \s / g, '_' ) } ` ) ;
462564 let output = ( `${ chalk . gray ( staticConfig . name ) } ${ chalk . green ( '\u{2713}' ) } \n` ) ;
463- ctx . task . output = ctx . task . output ? ctx . task . output + output : output ;
565+ ctx . task . output = ctx . task . output ? ctx . task . output + output : output ;
464566 finalOutput += output ;
465567 capturedScreenshots ++ ;
466568 } catch ( error ) {
@@ -488,6 +590,6 @@ async function executeDocumentScripts(ctx: Context, page: Page, actionType: stri
488590 }
489591 } catch ( error ) {
490592 ctx . log . error ( `Error executing script for action ${ actionType } : ` , error ) ;
491- throw error ;
593+ throw error ;
492594 }
493595}
0 commit comments