@@ -3,13 +3,15 @@ import fs from 'fs';
33import { resolve } from 'path' ;
44import { VitePlaywrightRunner , buildPage } from './playwright.js' ;
55import { withSoftwareWebGPUChromiumArgs } from './utils.js' ;
6+ import { profilePhase } from './profile.js' ;
7+ import { LIGHTHOUSE_WORKERS , MAX_LIGHTHOUSE_WORKERS } from './lighthouse-workers.js' ;
68
79const output = process . env . CI ? [ 'json' ] : [ 'json' , 'html' ] ;
810const RUNNER_ID = 'lighthouse' ;
911const LIGHTHOUSE_FLAGS = { logLevel : 'error' , output } ;
1012const CHROMIUM_ARGS = withSoftwareWebGPUChromiumArgs ( [
1113 '--headless' ,
12- '--remote-debugging-port=9222 ' ,
14+ '--remote-debugging-port=0 ' ,
1315 '--disable-dev-shm-usage' ,
1416 '--disable-extensions' ,
1517 '--disable-background-networking' ,
@@ -38,27 +40,78 @@ const LIGHTHOUSE_CONFIG = {
3840
3941export class LighthouseRunner {
4042 #runner;
43+ #pool = [ ] ;
44+ #chromiumArgs;
4145
4246 constructor ( { chromiumArgs = CHROMIUM_ARGS } = { } ) {
43- this . #runner = new VitePlaywrightRunner ( { runnerID : RUNNER_ID , chromiumArgs } ) ;
47+ this . #chromiumArgs = chromiumArgs ;
48+ this . #runner = new VitePlaywrightRunner ( {
49+ runnerID : RUNNER_ID ,
50+ instanceID : process . env . VITEST_POOL_ID ?? '1' ,
51+ chromiumArgs
52+ } ) ;
4453 }
4554
46- async open ( ) {
47- await this . #runner. open ( ) ;
55+ async open ( { workers = LIGHTHOUSE_WORKERS } = { } ) {
56+ if ( ! Number . isInteger ( workers ) || workers < 1 || workers > MAX_LIGHTHOUSE_WORKERS ) {
57+ throw new Error ( `Lighthouse requires between 1 and ${ MAX_LIGHTHOUSE_WORKERS } workers` ) ;
58+ }
59+ // Global setup owns the browsers. Each Vitest worker reads only its own ports.
60+ // The project-wide browser lock bounds machine-wide audit concurrency.
61+ this . #pool = Array . from (
62+ { length : workers } ,
63+ ( _ , index ) =>
64+ new VitePlaywrightRunner ( {
65+ runnerID : RUNNER_ID ,
66+ instanceID : String ( index + 1 ) ,
67+ chromiumArgs : this . #chromiumArgs
68+ } )
69+ ) ;
70+ try {
71+ for ( const runner of this . #pool) {
72+ await profilePhase ( 'browser-server-setup' , ( ) => runner . open ( ) ) ;
73+ }
74+ } catch ( error ) {
75+ try {
76+ await this . close ( ) ;
77+ } catch ( cleanupError ) {
78+ throw new AggregateError ( [ error , cleanupError ] , 'Lighthouse setup and cleanup failed' ) ;
79+ }
80+ throw error ;
81+ }
4882 }
4983
5084 async close ( ) {
51- await this . #runner. close ( ) ;
85+ const results = await Promise . allSettled (
86+ this . #pool. map ( runner => profilePhase ( 'report-collection-cleanup' , ( ) => runner . close ( ) ) )
87+ ) ;
88+ this . #pool = [ ] ;
89+ const failures = results . filter ( result => result . status === 'rejected' ) . map ( result => result . reason ) ;
90+ if ( failures . length ) throw new AggregateError ( failures , 'Failed to close Lighthouse browsers' ) ;
5291 }
5392
5493 async getReport ( name , content ) {
5594 console . log ( 'lighthouse-runner: building test page...' ) ;
56- await buildPage ( name , RUNNER_ID , t => t . replace ( '</body>' , `${ content } </body>` ) ) ;
95+ await profilePhase (
96+ 'fixture-build' ,
97+ ( ) => buildPage ( name , RUNNER_ID , t => t . replace ( '</body>' , `${ content } </body>` ) ) ,
98+ { name }
99+ ) ;
57100 console . log ( 'lighthouse-runner: running tests...' ) ;
58- const runnerResult = await lighthouse (
59- `http://localhost:${ this . #runner. port } /${ name } /index.html` ,
60- LIGHTHOUSE_FLAGS ,
61- LIGHTHOUSE_CONFIG
101+ const runnerResult = await profilePhase (
102+ 'lighthouse-execution' ,
103+ ( ) =>
104+ lighthouse (
105+ `http://localhost:${ this . #runner. port } /${ name } /index.html` ,
106+ { ...LIGHTHOUSE_FLAGS , port : this . #runner. debuggingPort } ,
107+ LIGHTHOUSE_CONFIG
108+ ) ,
109+ {
110+ name,
111+ worker : process . env . VITEST_POOL_ID ?? '1' ,
112+ port : this . #runner. port ,
113+ debuggingPort : this . #runner. debuggingPort
114+ }
62115 ) ;
63116
64117 const networkRequests = runnerResult . lhr . audits [ 'network-requests' ] ?. details ?. items ;
@@ -83,7 +136,11 @@ export class LighthouseRunner {
83136 }
84137
85138 console . log ( 'lighthouse-runner: writing test report...' ) ;
86- fs . writeFileSync ( `${ dist } /report.json` , JSON . stringify ( report , null , 2 ) ) ;
139+ await profilePhase (
140+ 'report-write' ,
141+ async ( ) => fs . writeFileSync ( `${ dist } /report.json` , JSON . stringify ( report , null , 2 ) ) ,
142+ { name }
143+ ) ;
87144 console . log ( `lighthouse-runner: test report written to ${ dist } /report.json` ) ;
88145 return report ;
89146 }
0 commit comments