@@ -17,6 +17,7 @@ function parseArgs(argv) {
1717 '--fps-samples-path' : 'samplesCsvPath' ,
1818 '--actions' : 'actions' ,
1919 '--state' : 'state' ,
20+ '--roi' : 'roi' ,
2021 } ;
2122
2223 for ( let i = 0 ; i < argv . length ; i ++ ) {
@@ -56,6 +57,15 @@ const openWindow = Boolean(process.env.DISPLAY && process.env.OPEN_WINDOW);
5657const executablePath = process . env . PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ;
5758const screenshotOnly = options . screenshotOnly ;
5859const actions = options . actions ? JSON . parse ( options . actions ) : [ ] ;
60+ const roi = options . roi ? JSON . parse ( options . roi ) : undefined ;
61+ const roiScale = ( ( ) => {
62+ if ( ! roi ) { return undefined ; }
63+ const value = Number ( roi . scale || 2 ) ;
64+ if ( ! Number . isFinite ( value ) || value <= 0 ) {
65+ throw new Error ( `ROI scale must be a positive number: ${ JSON . stringify ( roi ) } ` ) ;
66+ }
67+ return value ;
68+ } ) ( ) ;
5969const state = options . state ? path . join ( '/tmp' , `${ options . state } .json` ) : undefined ;
6070const saveState = path . join ( '/tmp' , `${ name } .json` ) ;
6171const commitSha = ( ) => {
@@ -108,6 +118,7 @@ function printUsage() {
108118 ' --screenshot-only Take a screenshot without FPS metrics.' ,
109119 ' --actions <json> Perform ordered actions after page load.' ,
110120 ' --state <name> Load cookies and localStorage from /tmp/<name>.json.' ,
121+ ' --roi <json> Crop screenshots to {x,y,width,height}.' ,
111122 '' ,
112123 'Environment:' ,
113124 ' CI Use CI browser launch behavior.' ,
@@ -206,6 +217,50 @@ async function performAction(page, action) {
206217 throw new Error ( `Unknown action: ${ JSON . stringify ( action ) } ` ) ;
207218}
208219
220+ function screenshotOptions ( destination ) {
221+ const screenshot = {
222+ path : destination ,
223+ fullPage : true ,
224+ timeout : 60_000 ,
225+ } ;
226+ if ( ! roi ) { return screenshot ; }
227+
228+ const clip = { } ;
229+ for ( const key of [ 'x' , 'y' , 'width' , 'height' ] ) {
230+ const value = Number ( roi [ key ] ) ;
231+ if ( ! Number . isFinite ( value ) ) {
232+ throw new Error ( `ROI ${ key } must be a finite number: ${ JSON . stringify ( roi ) } ` ) ;
233+ }
234+ clip [ key ] = value ;
235+ }
236+ if ( clip . width <= 0 || clip . height <= 0 || clip . x < 0 || clip . y < 0 ) {
237+ throw new Error ( `ROI must have non-negative x/y and positive width/height: ${ JSON . stringify ( roi ) } ` ) ;
238+ }
239+ delete screenshot . fullPage ;
240+ screenshot . clip = clip ;
241+ return screenshot ;
242+ }
243+
244+ async function saveScreenshot ( page , destination , label ) {
245+ fs . mkdirSync ( path . dirname ( destination ) , { recursive : true } ) ;
246+ await page . screenshot ( screenshotOptions ( destination ) ) ;
247+ console . log ( `${ label } =${ destination } ` ) ;
248+ }
249+
250+ function actionScreenshotPath ( index ) {
251+ const extension = path . extname ( screenshotPath ) ;
252+ const basePath = extension
253+ ? screenshotPath . slice ( 0 , - extension . length )
254+ : screenshotPath ;
255+ return `${ basePath } _action_${ index + 1 } ${ extension } ` ;
256+ }
257+
258+ async function performScreenshotAction ( page , action , destination ) {
259+ await performAction ( page , action ) ;
260+ await page . waitForTimeout ( 1000 ) ;
261+ await saveScreenshot ( page , destination , 'ACTION_SCREENSHOT' ) ;
262+ }
263+
209264async function main ( ) {
210265 console . log ( `Launching Chromium with args:\n ${ chromiumArgs . join ( '\n ' ) } \n` ) ;
211266 if ( executablePath ) {
@@ -216,7 +271,10 @@ async function main() {
216271 executablePath,
217272 args : chromiumArgs ,
218273 } ) ;
219- const contextOptions = state ? { storageState : state } : { } ;
274+ const contextOptions = {
275+ ...( state ? { storageState : state } : { } ) ,
276+ ...( roiScale ? { deviceScaleFactor : roiScale } : { } ) ,
277+ } ;
220278 if ( state ) {
221279 console . log ( `STATE=${ state } ` ) ;
222280 }
@@ -227,18 +285,18 @@ async function main() {
227285 try {
228286 await page . goto ( url , { waitUntil : 'domcontentloaded' } ) ;
229287 await prepareStressResources ( page , url ) ;
230- for ( const action of actions ) {
231- await performAction ( page , action ) ;
288+ for ( const [ index , action ] of actions . entries ( ) ) {
289+ if ( screenshotOnly ) {
290+ await performScreenshotAction ( page , action , actionScreenshotPath ( index ) ) ;
291+ } else {
292+ await performAction ( page , action ) ;
293+ }
232294 }
233295 if ( screenshotOnly ) {
234- await page . waitForTimeout ( 1000 ) ;
235- fs . mkdirSync ( path . dirname ( screenshotPath ) , { recursive : true } ) ;
236- await page . screenshot ( {
237- path : screenshotPath ,
238- fullPage : true ,
239- timeout : 60_000 ,
240- } ) ;
241- console . log ( `SCREENSHOT=${ screenshotPath } ` ) ;
296+ if ( actions . length === 0 ) {
297+ await page . waitForTimeout ( 1000 ) ;
298+ await saveScreenshot ( page , screenshotPath , 'SCREENSHOT' ) ;
299+ }
242300 return ;
243301 }
244302 const renderer = await webglRenderer ( page ) ;
@@ -307,13 +365,7 @@ async function main() {
307365 throw new Error ( 'window.__scene_metrics was not available' ) ;
308366 }
309367 console . log ( `SCENE_METRICS=${ data } ` ) ;
310- fs . mkdirSync ( path . dirname ( screenshotPath ) , { recursive : true } ) ;
311- await page . screenshot ( {
312- path : screenshotPath ,
313- fullPage : true ,
314- timeout : 60_000 ,
315- } ) ;
316- console . log ( `FPS_SCREENSHOT=${ screenshotPath } ` ) ;
368+ await saveScreenshot ( page , screenshotPath , 'FPS_SCREENSHOT' ) ;
317369 saveFpsSamplesCsv ( sampleValues , samplesCsvPath ) ;
318370 console . log ( `FPS_SAMPLES_CSV=${ samplesCsvPath } ` ) ;
319371 await saveStorage ( page ) ;
0 commit comments