11import { createContext , ContextProvider } from '@lit/context'
22import type { ReactiveController , ReactiveControllerHost } from 'lit'
3- import type {
4- Metadata ,
5- CommandLog ,
6- TraceLog
7- } from '@wdio/devtools-service/types'
8- import type { SuiteStats , TestStats } from '@wdio/reporter'
3+ import type { Metadata , CommandLog , TraceLog } from '@wdio/devtools-service/types'
4+
5+ import {
6+ mutationContext ,
7+ logContext ,
8+ consoleLogContext ,
9+ networkRequestContext ,
10+ metadataContext ,
11+ commandContext ,
12+ sourceContext ,
13+ suiteContext
14+ } from './context.js'
15+ import type { TestStatsFragment , SuiteStatsFragment , SocketMessage } from './types.js'
916
1017const CACHE_ID = 'wdio-trace-cache'
1118
12- type TestStatsFragment = Omit < Partial < TestStats > , 'uid' > & { uid : string }
13-
14- type SuiteStatsFragment = Omit <
15- Partial < SuiteStats > ,
16- 'uid' | 'tests' | 'suites'
17- > & {
18- uid : string
19- tests ?: TestStatsFragment [ ]
20- suites ?: SuiteStatsFragment [ ]
21- }
22-
23- export const mutationContext = createContext < TraceMutation [ ] > (
24- Symbol ( 'mutationContext' )
25- )
26- export const logContext = createContext < string [ ] > ( Symbol ( 'logContext' ) )
27- export const consoleLogContext = createContext < ConsoleLogs [ ] > (
28- Symbol ( 'consoleLogContext' )
29- )
30- export const networkRequestContext = createContext < NetworkRequest [ ] > (
31- Symbol ( 'networkRequestContext' )
32- )
33- export const metadataContext = createContext < Metadata > (
34- Symbol ( 'metadataContext' )
35- )
36- export const commandContext = createContext < CommandLog [ ] > (
37- Symbol ( 'commandContext' )
38- )
39- export const sourceContext = createContext < Record < string , string > > (
40- Symbol ( 'sourceContext' )
41- )
42- export const suiteContext = createContext < Record < string , any > [ ] > (
43- Symbol ( 'suiteContext' )
44- )
45-
4619const hasConnection = createContext < boolean > ( Symbol ( 'hasConnection' ) )
47- export const isTestRunningContext = createContext < boolean > (
48- Symbol ( 'isTestRunning' )
49- )
50-
51- interface SocketMessage <
52- T extends
53- | keyof TraceLog
54- | 'testStopped'
55- | 'clearExecutionData'
56- | 'replaceCommand' =
57- | keyof TraceLog
58- | 'testStopped'
59- | 'clearExecutionData'
60- | 'replaceCommand'
61- > {
62- scope : T
63- data : T extends keyof TraceLog
64- ? TraceLog [ T ]
65- : T extends 'clearExecutionData'
66- ? { uid ?: string }
67- : T extends 'replaceCommand'
68- ? { oldTimestamp : number ; command : CommandLog }
69- : unknown
70- }
7120
7221export class DataManagerController implements ReactiveController {
7322 #ws?: WebSocket
@@ -129,15 +78,13 @@ export class DataManagerController implements ReactiveController {
12978 return this . metadataContextProvider . value ?. type
13079 }
13180
132- // Public method to clear execution data when rerun is triggered
13381 clearExecutionData ( uid ?: string ) {
13482 this . #resetExecutionData( )
13583 if ( uid ) {
13684 this . #markTestAsRunning( uid )
13785 }
13886 }
13987
140- // Private method to mark a test/suite as running immediately for UI feedback
14188 #markTestAsRunning( uid : string ) {
14289 const suites = this . suitesContextProvider . value || [ ]
14390
@@ -191,12 +138,11 @@ export class DataManagerController implements ReactiveController {
191138
192139 // Recursive helper to mark tests/suites as running
193140 const markAsRunning = ( s : SuiteStatsFragment ) : SuiteStatsFragment => {
194- // If this is the target suite/test, mark it as running
195141 if ( s . uid === uid ) {
196142 return {
197143 ...s ,
198144 start : new Date ( ) ,
199- end : undefined , // Clear end to mark as running
145+ end : undefined ,
200146 tests :
201147 ( s . tests ?. map ( ( test ) => ( {
202148 ...test ,
@@ -207,7 +153,6 @@ export class DataManagerController implements ReactiveController {
207153 }
208154 }
209155
210- // Check if any child test matches
211156 const updatedTests = ( s . tests ?. map ( ( test ) => {
212157 if ( test . uid === uid ) {
213158 return {
@@ -219,7 +164,6 @@ export class DataManagerController implements ReactiveController {
219164 return test
220165 } ) ?? [ ] ) as TestStatsFragment [ ]
221166
222- // Recursively check nested suites
223167 const updatedNestedSuites = s . suites ?. map ( markAsRunning )
224168
225169 return {
@@ -277,22 +221,19 @@ export class DataManagerController implements ReactiveController {
277221 return
278222 }
279223
280- // Handle test stopped event
281224 if ( scope === 'testStopped' ) {
282225 this . #handleTestStopped( )
283226 this . #host. requestUpdate ( )
284227 return
285228 }
286229
287- // Handle clear execution data event (when tests change)
288230 if ( scope === 'clearExecutionData' ) {
289231 const clearData = data as { uid ?: string }
290232 this . clearExecutionData ( clearData . uid )
291233 this . #host. requestUpdate ( )
292234 return
293235 }
294236
295- // Handle in-place command replacement (retry deduplication)
296237 if ( scope === 'replaceCommand' ) {
297238 const { oldTimestamp, command } = data as {
298239 oldTimestamp : number
@@ -303,17 +244,17 @@ export class DataManagerController implements ReactiveController {
303244 return
304245 }
305246
306- // Check for new run BEFORE processing suites data
307247 if ( scope === 'suites' ) {
308248 const shouldReset = this . #shouldResetForNewRun( data )
309249 if ( shouldReset ) {
310250 this . #resetExecutionData( )
311251 }
312252 }
313253
314- // Route data to appropriate handler
315254 if ( scope === 'mutations' ) {
316255 this . #handleMutationsUpdate( data as TraceMutation [ ] )
256+ } else if ( scope === 'logs' ) {
257+ this . #handleLogsUpdate( data as string [ ] )
317258 } else if ( scope === 'commands' ) {
318259 this . #handleCommandsUpdate( data as CommandLog [ ] )
319260 } else if ( scope === 'metadata' ) {
@@ -326,8 +267,6 @@ export class DataManagerController implements ReactiveController {
326267 this . #handleSourcesUpdate( data as Record < string , string > )
327268 } else if ( scope === 'suites' ) {
328269 this . #handleSuitesUpdate( data )
329- } else {
330- this . #handleGenericUpdate( scope , data )
331270 }
332271
333272 this . #host. requestUpdate ( )
@@ -376,7 +315,7 @@ export class DataManagerController implements ReactiveController {
376315 ec as Record < string , SuiteStatsFragment >
377316 ) ) {
378317 if ( uid === Object . keys ( chunk ) [ 0 ] ) {
379- existingEnd = ( existing as any ) ?. end
318+ existingEnd = existing ?. end
380319 break outer
381320 }
382321 }
@@ -432,7 +371,7 @@ export class DataManagerController implements ReactiveController {
432371 return {
433372 ...test ,
434373 end : new Date ( ) ,
435- state : 'failed' as 'failed' ,
374+ state : 'failed' ,
436375 error : {
437376 message : 'Test execution stopped' ,
438377 name : 'TestStoppedError'
@@ -445,8 +384,24 @@ export class DataManagerController implements ReactiveController {
445384 // Recursively update nested suites (for Cucumber scenarios)
446385 const updatedNestedSuites = s . suites ?. map ( updateSuite )
447386
387+ // Derive the suite's own state from its updated children so that
388+ // STATE_MAP['running'] no longer produces a spinner after stop.
389+ const allTests = [ ...( updatedTests || [ ] ) , ...( updatedNestedSuites || [ ] ) ]
390+ const hasFailed = allTests . some ( ( t ) => t ?. state === 'failed' )
391+ const hasRunning = allTests . some ( ( t ) => ! t ?. end )
392+ const derivedState : SuiteStatsFragment [ 'state' ] = hasRunning
393+ ? s . state
394+ : hasFailed
395+ ? 'failed'
396+ : s . state === 'running'
397+ ? 'failed'
398+ : s . state
399+
448400 return {
449401 ...s ,
402+ state : derivedState ,
403+ ...( ! hasRunning && ! s . end ? { end : new Date ( ) } : { } ) ,
404+
450405 tests : updatedTests || [ ] ,
451406 suites : updatedNestedSuites || [ ]
452407 }
@@ -566,27 +521,11 @@ export class DataManagerController implements ReactiveController {
566521 return date instanceof Date ? date . getTime ( ) : date
567522 }
568523
569- #handleGenericUpdate( scope : keyof TraceLog , data : any ) {
570- const providerMap = {
571- mutations : this . mutationsContextProvider ,
572- logs : this . logsContextProvider ,
573- consoleLogs : this . consoleLogsContextProvider ,
574- metadata : this . metadataContextProvider ,
575- commands : this . commandsContextProvider ,
576- sources : this . sourcesContextProvider ,
577- suites : this . suitesContextProvider
578- } as const
579-
580- const provider = providerMap [ scope as keyof typeof providerMap ]
581- if ( provider ) {
582- provider . setValue ( data )
583- }
524+ #handleLogsUpdate( data : string [ ] ) {
525+ this . logsContextProvider . setValue ( data )
584526 }
585527
586528 #mergeSuite( existing : SuiteStatsFragment , incoming : SuiteStatsFragment ) {
587- // Note: Rerun detection and clearing is now handled in #handleSuitesUpdate
588- // before any merges happen, so data is cleared proactively
589-
590529 // First merge tests and suites properly
591530 const mergedTests = this . #mergeTests( existing . tests , incoming . tests )
592531 const mergedSuites = this . #mergeChildSuites(
@@ -676,7 +615,4 @@ export class DataManagerController implements ReactiveController {
676615 }
677616}
678617
679- /**
680- * re-export types used for context
681- */
682- export { type Metadata , type CommandLog , type TraceLog , type TraceMutation }
618+
0 commit comments