@@ -19,6 +19,7 @@ import {
1919} from './context.js'
2020import { CACHE_ID } from './constants.js'
2121import { getTimestamp } from '../utils/helpers.js'
22+ import { rerunState } from './rerunState.js'
2223import type {
2324 TestStatsFragment ,
2425 SuiteStatsFragment ,
@@ -87,16 +88,30 @@ export class DataManagerController implements ReactiveController {
8788 }
8889
8990 clearExecutionData ( uid ?: string , entryType ?: 'suite' | 'test' ) {
90- this . #resetExecutionData( )
91+ // If we are already tracking a feature-level rerun and this clear is for
92+ // a child scenario (not the top-level rerun trigger itself), skip resetting
93+ // execution data so previously-completed scenarios' data is preserved.
94+ const isChildOfActiveRerun = ! ! ( uid && rerunState . activeRerunSuiteUid && uid !== rerunState . activeRerunSuiteUid )
95+
96+ if ( ! isChildOfActiveRerun ) {
97+ this . #resetExecutionData( )
98+ }
9199
92100 // When the backend sends clearExecutionData with no uid (e.g. a full Nightwatch
93101 // rerun), immediately mark all suites as running so the spinner shows instead
94102 // of the previous run's terminal state (passed/failed).
95103 if ( ! uid ) {
104+ rerunState . activeRerunSuiteUid = undefined
96105 this . #markTestAsRunning( '*' , 'suite' )
97106 return
98107 }
99108
109+ // Track the top-level rerun suite uid so we can identify child-scenario
110+ // clears (from the Nightwatch backend) and skip their data wipes.
111+ if ( ! isChildOfActiveRerun && entryType === 'suite' && uid !== '*' ) {
112+ rerunState . activeRerunSuiteUid = uid
113+ }
114+
100115 // Track explicit single-test reruns so merge logic can keep sibling tests
101116 // stable while the backend emits suite-level "pending" snapshots.
102117 if ( entryType === 'test' && uid !== '*' ) {
@@ -221,8 +236,12 @@ export class DataManagerController implements ReactiveController {
221236 ...( matched
222237 ? {
223238 state : 'running' as const ,
224- start : runStart ,
225- end : undefined
239+ // Don't reset the parent's start/end when it is already
240+ // running — subsequent child-scenario marks would otherwise
241+ // reset the feature's original run timestamp.
242+ ...( s . state !== 'running'
243+ ? { start : runStart , end : undefined }
244+ : { } )
226245 }
227246 : { } ) ,
228247 tests : updatedTests || [ ] ,
@@ -329,6 +348,33 @@ export class DataManagerController implements ReactiveController {
329348 }
330349
331350 #shouldResetForNewRun( data : unknown ) : boolean {
351+ // During a UI-triggered rerun, suppress auto-detection so sibling-scenario
352+ // updates don't wipe accumulated execution data.
353+ // Still update #lastSeenRunTimestamp so that once activeRerunSuiteUid is
354+ // cleared the final suite update isn't mistakenly treated as a new run.
355+ if ( rerunState . activeRerunSuiteUid ) {
356+ const payloads = Array . isArray ( data )
357+ ? ( data as Record < string , SuiteStatsFragment > [ ] )
358+ : ( [ data ] as Record < string , SuiteStatsFragment > [ ] )
359+ for ( const chunk of payloads ) {
360+ if ( ! chunk ) {
361+ continue
362+ }
363+ for ( const suite of Object . values ( chunk ) ) {
364+ if ( ! suite ?. start ) {
365+ continue
366+ }
367+ const t = getTimestamp (
368+ suite . start as Date | number | string | undefined
369+ )
370+ if ( t > this . #lastSeenRunTimestamp) {
371+ this . #lastSeenRunTimestamp = t
372+ }
373+ }
374+ }
375+ return false
376+ }
377+
332378 const payloads = Array . isArray ( data )
333379 ? ( data as Record < string , SuiteStatsFragment > [ ] )
334380 : ( [ data ] as Record < string , SuiteStatsFragment > [ ] )
@@ -399,6 +445,7 @@ export class DataManagerController implements ReactiveController {
399445
400446 #handleTestStopped( ) {
401447 this . #activeRerunTestUid = undefined
448+ rerunState . activeRerunSuiteUid = undefined
402449
403450 // Mark all running tests as failed when test execution is stopped
404451 const suites = this . suitesContextProvider . value || [ ]
@@ -563,6 +610,15 @@ export class DataManagerController implements ReactiveController {
563610 this . suitesContextProvider . setValue (
564611 Array . from ( suiteMap . entries ( ) ) . map ( ( [ uid , suite ] ) => ( { [ uid ] : suite } ) )
565612 )
613+
614+ // Once the active rerun suite reaches a terminal state, clear the tracking
615+ // flag so subsequent CLI-triggered runs can be detected normally.
616+ if ( rerunState . activeRerunSuiteUid ) {
617+ const activeSuite = suiteMap . get ( rerunState . activeRerunSuiteUid )
618+ if ( activeSuite ?. end ) {
619+ rerunState . activeRerunSuiteUid = undefined
620+ }
621+ }
566622 }
567623
568624 #handleLogsUpdate( data : string [ ] ) {
@@ -649,8 +705,14 @@ export class DataManagerController implements ReactiveController {
649705 // #mergeChildSuites preserves stale child suites from the previous run,
650706 // but they must not keep their terminal states — mark them 'pending' so
651707 // they render as a spinner instead of a stale checkmark/cross.
708+ // Exception: when only a specific child scenario is being rerun
709+ // (activeRerunSuiteUid differs from the incoming feature suite's uid),
710+ // sibling scenarios must keep their existing terminal states.
711+ const isChildRerun =
712+ ! ! rerunState . activeRerunSuiteUid &&
713+ rerunState . activeRerunSuiteUid !== incoming . uid
652714 const finalSuites =
653- incoming . state === 'pending' && mergedSuites
715+ incoming . state === 'pending' && mergedSuites && ! isChildRerun
654716 ? mergedSuites . map ( ( s ) =>
655717 s . state === 'passed' || s . state === 'failed'
656718 ? { ...s , state : 'pending' as const , end : undefined }
0 commit comments