1- import type { BrowserRPC , IframeChannelEvent } from '@vitest/browser/client'
1+ import type { BrowserRPC , IframeReceivedEvent } from '@vitest/browser/client'
22import type { FileSpecification } from 'vitest/internal/browser'
33import { channel , client , onCancel , registerPageMarkHandler } from '@vitest/browser/client'
44import { parse } from 'flatted'
@@ -35,12 +35,10 @@ let rootTesterSpan: ReturnType<Traces['startContextSpan']> | undefined
3535getBrowserState ( ) . traces = traces
3636
3737channel . addEventListener ( 'message' , async ( e ) => {
38- await client . waitForConnection ( )
39-
4038 const data = e . data
41- debug ?.( 'event from orchestrator' , JSON . stringify ( e . data ) )
4239
4340 if ( ! isEvent ( data ) ) {
41+ await client . waitForConnection ( )
4442 const error = new Error ( `Unknown message: ${ JSON . stringify ( e . data ) } ` )
4543 unhandledError ( error , 'Unknown Iframe Message' )
4644 return
@@ -51,60 +49,83 @@ channel.addEventListener('message', async (e) => {
5149 return
5250 }
5351
54- switch ( data . event ) {
55- case 'execute' : {
56- const { method, files, context, concurrencyId, workerId } = data
57- const state = getWorkerState ( )
58- const parsedContext = parse ( context )
59-
60- state . ctx . concurrencyId = concurrencyId
61- state . ctx . workerId = workerId
62- state . ctx . providedContext = parsedContext
63- state . providedContext = parsedContext
64- state . metaEnv . VITEST_POOL_ID = String ( concurrencyId )
65- state . metaEnv . VITEST_WORKER_ID = String ( workerId )
66-
67- if ( method === 'collect' ) {
68- await executeTests ( 'collect' , files ) . catch ( err => unhandledError ( err , 'Collect Error' ) )
52+ // tell the orchestrator we received the event before doing any work (which
53+ // may be long-running or gated on the connection), so it can tell a busy
54+ // tester apart from a crashed one. See `sendEventToIframe` in orchestrator.ts.
55+ channel . postMessage ( {
56+ event : `ack:${ data . event } ` ,
57+ iframeId : data . iframeId ,
58+ messageId : data . messageId ,
59+ } )
60+
61+ await client . waitForConnection ( )
62+ debug ?.( 'event from orchestrator' , JSON . stringify ( e . data ) )
63+
64+ try {
65+ switch ( data . event ) {
66+ case 'execute' : {
67+ const { method, files, context, concurrencyId, workerId } = data
68+ const state = getWorkerState ( )
69+ const parsedContext = parse ( context )
70+
71+ state . ctx . concurrencyId = concurrencyId
72+ state . ctx . workerId = workerId
73+ state . ctx . providedContext = parsedContext
74+ state . providedContext = parsedContext
75+ state . metaEnv . VITEST_POOL_ID = String ( concurrencyId )
76+ state . metaEnv . VITEST_WORKER_ID = String ( workerId )
77+
78+ if ( method === 'collect' ) {
79+ await executeTests ( 'collect' , files ) . catch ( err => unhandledError ( err , 'Collect Error' ) )
80+ }
81+ else {
82+ await executeTests ( 'run' , files ) . catch ( err => unhandledError ( err , 'Run Error' ) )
83+ }
84+ break
6985 }
70- else {
71- await executeTests ( 'run' , files ) . catch ( err => unhandledError ( err , 'Run Error' ) )
86+ case 'cleanup' : {
87+ await cleanup ( ) . catch ( err => unhandledError ( err , 'Cleanup Error' ) )
88+ rootTesterSpan ?. span . end ( )
89+ await traces . finish ( )
90+ break
91+ }
92+ case 'prepare' : {
93+ await traces . waitInit ( )
94+ const tracesContext = traces . getContextFromCarrier ( data . otelCarrier )
95+ traces . recordInitSpan ( tracesContext )
96+ rootTesterSpan = traces . startContextSpan (
97+ `vitest.browser.tester.run` ,
98+ tracesContext ,
99+ )
100+ traces . bind ( rootTesterSpan . context )
101+ await prepare ( data ) . catch ( err => unhandledError ( err , 'Prepare Error' ) )
102+ break
103+ }
104+ case 'viewport:done' :
105+ case 'viewport:fail' :
106+ case 'viewport' : {
107+ break
108+ }
109+ default : {
110+ const error = new Error ( `Unknown event: ${ ( data as any ) . event } ` )
111+ unhandledError ( error , 'Unknown Event' )
72112 }
73- break
74- }
75- case 'cleanup' : {
76- await cleanup ( ) . catch ( err => unhandledError ( err , 'Cleanup Error' ) )
77- rootTesterSpan ?. span . end ( )
78- await traces . finish ( )
79- break
80- }
81- case 'prepare' : {
82- await traces . waitInit ( )
83- const tracesContext = traces . getContextFromCarrier ( data . otelCarrier )
84- traces . recordInitSpan ( tracesContext )
85- rootTesterSpan = traces . startContextSpan (
86- `vitest.browser.tester.run` ,
87- tracesContext ,
88- )
89- traces . bind ( rootTesterSpan . context )
90- await prepare ( data ) . catch ( err => unhandledError ( err , 'Prepare Error' ) )
91- break
92- }
93- case 'viewport:done' :
94- case 'viewport:fail' :
95- case 'viewport' : {
96- break
97- }
98- default : {
99- const error = new Error ( `Unknown event: ${ ( data as any ) . event } ` )
100- unhandledError ( error , 'Unknown Event' )
101113 }
102114 }
103-
104- channel . postMessage ( {
105- event : `response:${ data . event } ` ,
106- iframeId : getBrowserState ( ) . iframeId ! ,
107- } )
115+ catch ( error : any ) {
116+ // errors not handled by the cases above (e.g. tracing setup/teardown) must
117+ // not stop us from responding, otherwise the orchestrator would wait forever
118+ await unhandledError ( error , 'Tester Error' )
119+ }
120+ finally {
121+ // always let the orchestrator know the event was handled so its
122+ // `sendEventToIframe` promise resolves, even if the work above threw
123+ channel . postMessage ( {
124+ event : `response:${ data . event } ` ,
125+ iframeId : data . iframeId ,
126+ messageId : data . messageId ,
127+ } )
128+ }
108129} )
109130
110131const url = new URL ( location . href )
@@ -307,6 +328,6 @@ function unhandledError(e: Error, type: string) {
307328 stack : e . stack ,
308329 } , type ) . catch ( ( ) => { } )
309330}
310- function isEvent ( data : unknown ) : data is IframeChannelEvent {
331+ function isEvent ( data : unknown ) : data is IframeReceivedEvent {
311332 return typeof data === 'object' && ! ! data && 'event' in data
312333}
0 commit comments