Skip to content

Commit ad1ac35

Browse files
committed
Test rerun feature
1 parent 194d167 commit ad1ac35

13 files changed

Lines changed: 429 additions & 185 deletions

File tree

packages/app/src/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export class WebdriverIODevtoolsApplication extends Element {
7171
this.requestUpdate()
7272
}
7373

74-
#clearExecutionData({ detail }: { detail?: { uid?: string } }) {
75-
this.dataManager.clearExecutionData(detail?.uid)
74+
#clearExecutionData({ detail }: { detail?: { uid?: string; entryType?: 'suite' | 'test' } }) {
75+
this.dataManager.clearExecutionData(detail?.uid, detail?.entryType)
7676
}
7777

7878
#mainContent() {

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

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
109109
// Clear execution data before triggering rerun
110110
this.dispatchEvent(
111111
new CustomEvent('clear-execution-data', {
112-
detail: { uid: detail.uid },
112+
detail: { uid: detail.uid, entryType: detail.entryType },
113113
bubbles: true,
114114
composed: true
115115
})
@@ -188,7 +188,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
188188
// Clear execution data and mark all tests as running
189189
this.dispatchEvent(
190190
new CustomEvent('clear-execution-data', {
191-
detail: { uid: '*' },
191+
detail: { uid: '*', entryType: 'suite' },
192192
bubbles: true,
193193
composed: true
194194
})
@@ -334,18 +334,60 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
334334

335335
#isRunning(entry: TestStats | SuiteStats): boolean {
336336
if ('tests' in entry) {
337-
// Check if any immediate test is running
338-
if (entry.tests.some((t) => !t.end)) {
337+
// Fastest path: any explicitly running descendant
338+
if (
339+
entry.tests.some((t) => (t as any).state === 'running') ||
340+
entry.suites.some((s) => this.#isRunning(s))
341+
) {
339342
return true
340343
}
341-
// Check if any nested suite is running
342-
if (entry.suites.some((s) => this.#isRunning(s))) {
344+
345+
const hasPendingTests = entry.tests.some(
346+
(t) => (t as any).state === 'pending'
347+
)
348+
const hasPendingSuites = entry.suites.some((s) => this.#hasPending(s))
349+
const suiteState = (entry as any).state
350+
351+
// If the suite was explicitly marked 'running' (e.g. by markTestAsRunning)
352+
// and still has pending children, it's actively executing.
353+
if (suiteState === 'running' && (hasPendingTests || hasPendingSuites)) {
354+
return true
355+
}
356+
357+
// Mixed terminal + pending children = run is in progress regardless of
358+
// explicit suite state (handles Nightwatch Cucumber where the feature
359+
// suite state may be undefined in the JSON payload).
360+
const allDescendants = [...entry.tests, ...entry.suites]
361+
const hasSomeTerminal = allDescendants.some(
362+
(t) =>
363+
(t as any).state === 'passed' ||
364+
(t as any).state === 'failed' ||
365+
(t as any).state === 'skipped'
366+
)
367+
if ((hasPendingTests || hasPendingSuites) && hasSomeTerminal) {
343368
return true
344369
}
370+
345371
return false
346372
}
347-
// For individual tests, check if end is not set
348-
return !entry.end
373+
// For individual tests rely on explicit state only.
374+
return (entry as any).state === 'running'
375+
}
376+
377+
#hasPending(entry: TestStats | SuiteStats): boolean {
378+
if ('tests' in entry) {
379+
if ((entry as any).state === 'pending') {
380+
return true
381+
}
382+
if (entry.tests.some((t) => (t as any).state === 'pending')) {
383+
return true
384+
}
385+
if (entry.suites.some((s) => this.#hasPending(s))) {
386+
return true
387+
}
388+
return false
389+
}
390+
return (entry as any).state === 'pending'
349391
}
350392

351393
#hasFailed(entry: TestStats | SuiteStats): boolean {
@@ -364,7 +406,7 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
364406
return entry.state === 'failed'
365407
}
366408

367-
#computeEntryState(entry: TestStats | SuiteStats): TestState {
409+
#computeEntryState(entry: TestStats | SuiteStats): TestState | 'pending' {
368410
// For suites, check running state from children FIRST — this ensures that
369411
// a rerun (which clears end times) shows the spinner immediately, even if
370412
// the suite still has a cached 'passed'/'failed' state from the previous run.
@@ -374,7 +416,26 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
374416

375417
const state = (entry as any).state
376418

377-
// Check explicit state
419+
// For suites with no explicit terminal state, derive from children.
420+
// A suite with state=undefined or state=pending that has no terminal
421+
// children yet is still in-progress — don't show PASSED prematurely.
422+
if ('tests' in entry && (state == null || state === 'pending' || state === 'running')) {
423+
const allDescendants = [...entry.tests, ...entry.suites]
424+
if (allDescendants.length > 0) {
425+
const allTerminal = allDescendants.every(
426+
(t) =>
427+
(t as any).state === 'passed' ||
428+
(t as any).state === 'failed' ||
429+
(t as any).state === 'skipped'
430+
)
431+
if (!allTerminal) {
432+
// Still has non-terminal children — treat as running/loading
433+
return TestState.RUNNING
434+
}
435+
}
436+
}
437+
438+
// Check explicit terminal state
378439
const mappedState = STATE_MAP[state]
379440
if (mappedState) {
380441
return mappedState
@@ -388,8 +449,13 @@ export class DevtoolsSidebarExplorer extends CollapseableEntry {
388449
return TestState.PASSED
389450
}
390451

391-
// For individual tests, check if still running
392-
return !entry.end ? TestState.RUNNING : TestState.PASSED
452+
// For individual leaf tests: pending = spinner (run is in progress),
453+
// not circle (which implies "never run").
454+
if (state === 'pending') {
455+
return TestState.RUNNING
456+
}
457+
458+
return entry.end ? TestState.PASSED : 'pending'
393459
}
394460

395461
#getTestEntry(entry: TestStats | SuiteStats): TestEntry {

0 commit comments

Comments
 (0)