Skip to content

Commit 749c4cf

Browse files
committed
Test rerun feature enhancement
1 parent 3ad7aed commit 749c4cf

11 files changed

Lines changed: 96 additions & 123 deletions

File tree

packages/app/src/components/browser/snapshot.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ import type { CommandLog } from '@wdio/devtools-service/types'
99

1010
import {
1111
mutationContext,
12-
type TraceMutation,
1312
metadataContext,
14-
type Metadata,
1513
commandContext
16-
} from '../../controller/DataManager.js'
14+
} from '../../controller/context.js'
15+
import type { Metadata } from '@wdio/devtools-service/types'
1716

1817
import '~icons/mdi/world.js'
1918
import '../placeholder.js'

packages/app/src/components/sidebar/explorer.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import type { Metadata } from '@wdio/devtools-service/types'
77
import { repeat } from 'lit/directives/repeat.js'
88
import {
99
suiteContext,
10-
metadataContext,
11-
isTestRunningContext
12-
} from '../../controller/DataManager.js'
10+
metadataContext
11+
} from '../../controller/context.js'
1312
import type {
1413
TestEntry,
1514
RunCapabilities,
@@ -73,9 +72,6 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
7372
@consume({ context: metadataContext, subscribe: true })
7473
metadata: Metadata | undefined = undefined
7574

76-
@consume({ context: isTestRunningContext, subscribe: true })
77-
isTestRunning = false
78-
7975
updated(changedProperties: Map<string | number | symbol, unknown>) {
8076
super.updated(changedProperties)
8177
}
@@ -491,10 +487,6 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
491487
)
492488
: html`<div class="text-sm px-4 py-2">
493489
<p class="text-disabledForeground">No tests to display</p>
494-
<p class="text-xs text-disabledForeground mt-2">
495-
Debug: suites=${this.suites?.length || 0},
496-
rootSuites=${uniqueSuites.length}, filtered=${suites.length}
497-
</p>
498490
</div>`}
499491
</wdio-test-suite>
500492
`

packages/app/src/components/workbench.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { DragController, Direction } from '../utils/DragController.js'
77
import {
88
consoleLogContext,
99
networkRequestContext
10-
} from '../controller/DataManager.js'
10+
} from '../controller/context.js'
1111

1212
import '~icons/mdi/arrow-collapse-down.js'
1313
import '~icons/mdi/arrow-collapse-up.js'

packages/app/src/components/workbench/actions.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { html, css } from 'lit'
33
import { customElement } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
55

6+
import type { CommandLog } from '@wdio/devtools-service/types'
67
import {
78
mutationContext,
8-
type TraceMutation,
9-
commandContext,
10-
type CommandLog
11-
} from '../../controller/DataManager.js'
9+
commandContext
10+
} from '../../controller/context.js'
1211

1312
import '~icons/mdi/pencil.js'
1413
import '~icons/mdi/family-tree.js'

packages/app/src/components/workbench/console.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { html, css, nothing } from 'lit'
33
import { customElement } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
55

6-
import { consoleLogContext } from '../../controller/DataManager.js'
6+
import { consoleLogContext } from '../../controller/context.js'
77

88
const LOG_ICONS: Record<ConsoleLogs['type'], string> = {
99
log: '📄',

packages/app/src/components/workbench/metadata.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { html, css } from 'lit'
33
import { customElement } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
55

6-
import { metadataContext, type Metadata } from '../../controller/DataManager.js'
6+
import type { Metadata } from '@wdio/devtools-service/types'
7+
import { metadataContext } from '../../controller/context.js'
78

89
import './list.js'
910
import '../placeholder.js'

packages/app/src/components/workbench/network.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Element } from '@core/element'
22
import { html, css, nothing } from 'lit'
33
import { customElement, state } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
5-
import { networkRequestContext } from '../../controller/DataManager.js'
5+
import { networkRequestContext } from '../../controller/context.js'
66
import { RESOURCE_TYPES } from '../../utils/network-constants.js'
77
import {
88
formatBytes,

packages/app/src/components/workbench/source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { EditorViewConfig } from '@codemirror/view'
88
import { javascript } from '@codemirror/lang-javascript'
99
import { oneDark } from '@codemirror/theme-one-dark'
1010

11-
import { sourceContext } from '../../controller/DataManager.js'
11+
import { sourceContext } from '../../controller/context.js'
1212

1313
import '../placeholder.js'
1414

packages/app/src/controller/DataManager.ts

Lines changed: 37 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,22 @@
11
import { createContext, ContextProvider } from '@lit/context'
22
import 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

1017
const 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-
4619
const 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

7221
export 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+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createContext } from '@lit/context'
2+
import type { Metadata, CommandLog } from '@wdio/devtools-service/types'
3+
4+
export const mutationContext = createContext<TraceMutation[]>(Symbol('mutationContext'))
5+
export const logContext = createContext<string[]>(Symbol('logContext'))
6+
export const consoleLogContext = createContext<ConsoleLogs[]>(Symbol('consoleLogContext'))
7+
export const networkRequestContext = createContext<NetworkRequest[]>(Symbol('networkRequestContext'))
8+
export const metadataContext = createContext<Metadata>(Symbol('metadataContext'))
9+
export const commandContext = createContext<CommandLog[]>(Symbol('commandContext'))
10+
export const sourceContext = createContext<Record<string, string>>(Symbol('sourceContext'))
11+
export const suiteContext = createContext<Record<string, any>[]>(Symbol('suiteContext'))

0 commit comments

Comments
 (0)