@@ -10,7 +10,7 @@ import { ensureDeviceReady } from '../device-ready.ts';
1010import { resolveIosAppStateFromSnapshots } from '../app-state.ts' ;
1111import { stopIosRunnerSession } from '../../platforms/ios/runner-client.ts' ;
1212import { attachRefs , type RawSnapshotNode , type SnapshotState } from '../../utils/snapshot.ts' ;
13- import { pruneGroupNodes } from '../snapshot-processing.ts' ;
13+ import { extractNodeText , normalizeType , pruneGroupNodes } from '../snapshot-processing.ts' ;
1414import {
1515 buildSelectorChainForNode ,
1616 resolveSelectorChain ,
@@ -517,6 +517,10 @@ async function healReplayAction(params: {
517517 const session = sessionStore . get ( sessionName ) ;
518518 if ( ! session ) return null ;
519519 const requiresRect = action . command === 'click' || action . command === 'fill' ;
520+ const allowDisambiguation =
521+ action . command === 'click' ||
522+ action . command === 'fill' ||
523+ ( action . command === 'get' && action . positionals ?. [ 0 ] === 'text' ) ;
520524 const snapshot = await captureSnapshotForReplay ( session , action , logPath , requiresRect , dispatch , sessionStore ) ;
521525 const selectorCandidates = collectReplaySelectorCandidates ( action ) ;
522526 for ( const candidate of selectorCandidates ) {
@@ -526,7 +530,7 @@ async function healReplayAction(params: {
526530 platform : session . device . platform ,
527531 requireRect : requiresRect ,
528532 requireUnique : true ,
529- disambiguateAmbiguous : requiresRect ,
533+ disambiguateAmbiguous : allowDisambiguation ,
530534 } ) ;
531535 if ( ! resolved ) continue ;
532536 const selectorChain = buildSelectorChainForNode ( resolved . node , session . device . platform , {
@@ -580,6 +584,10 @@ async function healReplayAction(params: {
580584 } ;
581585 }
582586 }
587+ const numericDriftHeal = healNumericGetTextDrift ( action , snapshot , session ) ;
588+ if ( numericDriftHeal ) {
589+ return numericDriftHeal ;
590+ }
583591 return null ;
584592}
585593
@@ -697,6 +705,56 @@ function parseSelectorWaitPositionals(positionals: string[]): {
697705 } ;
698706}
699707
708+ function healNumericGetTextDrift (
709+ action : SessionAction ,
710+ snapshot : SnapshotState ,
711+ session : SessionState ,
712+ ) : SessionAction | null {
713+ if ( action . command !== 'get' ) return null ;
714+ if ( action . positionals ?. [ 0 ] !== 'text' ) return null ;
715+ const selectorExpression = action . positionals ?. [ 1 ] ;
716+ if ( ! selectorExpression ) return null ;
717+ const chain = tryParseSelectorChain ( selectorExpression ) ;
718+ if ( ! chain ) return null ;
719+
720+ const roleFilters = new Set < string > ( ) ;
721+ let hasNumericTerm = false ;
722+ for ( const selector of chain . selectors ) {
723+ for ( const term of selector . terms ) {
724+ if ( term . key === 'role' && typeof term . value === 'string' ) {
725+ roleFilters . add ( normalizeType ( term . value ) ) ;
726+ }
727+ if (
728+ ( term . key === 'text' || term . key === 'label' || term . key === 'value' ) &&
729+ typeof term . value === 'string' &&
730+ / ^ \d + $ / . test ( term . value . trim ( ) )
731+ ) {
732+ hasNumericTerm = true ;
733+ }
734+ }
735+ }
736+ if ( ! hasNumericTerm ) return null ;
737+
738+ const numericNodes = snapshot . nodes . filter ( ( node ) => {
739+ const text = extractNodeText ( node ) . trim ( ) ;
740+ if ( ! / ^ \d + $ / . test ( text ) ) return false ;
741+ if ( roleFilters . size === 0 ) return true ;
742+ return roleFilters . has ( normalizeType ( node . type ?? '' ) ) ;
743+ } ) ;
744+ if ( numericNodes . length === 0 ) return null ;
745+ const numericValues = uniqueStrings ( numericNodes . map ( ( node ) => extractNodeText ( node ) . trim ( ) ) ) ;
746+ if ( numericValues . length !== 1 ) return null ;
747+
748+ const targetNode = numericNodes [ 0 ] ;
749+ if ( ! targetNode ) return null ;
750+ const selectorChain = buildSelectorChainForNode ( targetNode , session . device . platform , { action : 'get' } ) ;
751+ if ( selectorChain . length === 0 ) return null ;
752+ return {
753+ ...action ,
754+ positionals : [ 'text' , selectorChain . join ( ' || ' ) ] ,
755+ } ;
756+ }
757+
700758function parseReplayScript ( script : string ) : SessionAction [ ] {
701759 const actions : SessionAction [ ] = [ ] ;
702760 const lines = script . split ( / \r ? \n / ) ;
0 commit comments