@@ -89,6 +89,9 @@ const defaultBrowsers = [
8989 playwrightLauncher ( { product : 'chromium' } ) ,
9090] ;
9191
92+ // Enable real-time progress reporting for local dev (single browser)
93+ const localDev = ! sauceLabsConfig && ! process . env . CI ;
94+
9295export default {
9396 // The test file(s)
9497 files : [ 'dist/test.js' ] ,
@@ -113,8 +116,51 @@ export default {
113116 // Parallel browser execution
114117 concurrency : sauceLabsConfig ? 4 : 1 ,
115118
119+ // Use static logging for real-time progress (local dev only)
120+ staticLogging : localDev ,
121+
122+ // Custom reporter for local dev; undefined falls back to default for CI
123+ reporters : localDev ? [
124+ {
125+ onTestRunFinished ( { sessions } ) {
126+ let passed = 0 , failed = 0 , skipped = 0 ;
127+ for ( const session of sessions ) {
128+ const countTests = ( suite ) => {
129+ for ( const test of suite . tests || [ ] ) {
130+ if ( test . skipped ) skipped ++ ;
131+ else if ( test . passed ) passed ++ ;
132+ else failed ++ ;
133+ }
134+ for ( const child of suite . suites || [ ] ) countTests ( child ) ;
135+ } ;
136+ if ( session . testResults ) countTests ( session . testResults ) ;
137+ }
138+ const total = passed + failed + skipped ;
139+ process . stdout . write ( `\n\n${ total } tests: ${ passed } passed, ${ failed } failed, ${ skipped } skipped.\n\n` ) ;
140+ } ,
141+ } ,
142+ ] : undefined ,
143+
116144 // Middleware to serve test fixtures and QUnit from local files
117145 middleware : [
146+ // Real-time test progress reporting
147+ async function testProgressReporter ( context , next ) {
148+ if ( context . method === 'POST' && context . url === '/test-progress' ) {
149+ const chunks = [ ] ;
150+ for await ( const chunk of context . req ) {
151+ chunks . push ( chunk ) ;
152+ }
153+ const body = Buffer . concat ( chunks ) . toString ( ) ;
154+ const { status } = JSON . parse ( body ) ;
155+ // Match the same logic used in the reporter: skipped, failed, or passed (everything else)
156+ const progressIndicator = status === 'skipped' ? 'S' : status === 'failed' ? 'F' : '.' ;
157+ process . stdout . write ( progressIndicator ) ;
158+ context . status = 200 ;
159+ context . body = 'ok' ;
160+ return ;
161+ }
162+ return next ( ) ;
163+ } ,
118164 function serveLocalFiles ( context , next ) {
119165 // Serve test fixtures from src/test/test_helpers/fixtures/
120166 if ( context . url . startsWith ( '/test_helpers/fixtures/' ) ) {
@@ -177,6 +223,9 @@ export default {
177223 import { getConfig, sessionStarted, sessionFinished, sessionFailed }
178224 from '@web/test-runner-core/browser/session.js';
179225
226+ // Real-time progress reporting only for local dev (single browser)
227+ const reportProgress = ${ localDev } ;
228+
180229 try {
181230 await sessionStarted();
182231
@@ -192,6 +241,15 @@ export default {
192241 });
193242
194243 QUnit.on('testEnd', (result) => {
244+ // POST progress to server for real-time output
245+ if (reportProgress) {
246+ fetch('/test-progress', {
247+ method: 'POST',
248+ headers: { 'Content-Type': 'application/json' },
249+ body: JSON.stringify({ status: result.status })
250+ }).catch(() => {});
251+ }
252+
195253 // Navigate to correct suite in hierarchy
196254 const modules = result.fullName.slice(0, -1);
197255 let currentSuite = testSuite;
0 commit comments