@@ -76,6 +76,9 @@ export default class DevToolsHookService implements Services.ServiceInstance {
7676 */
7777 #commandStack: string [ ] = [ ]
7878
79+ // This is used to capture the last command signature to avoid duplicate captures
80+ #lastCommandSig: string | null = null ;
81+
7982 /**
8083 * allows to define the type of data being captured to hint the
8184 * devtools app which data to expect
@@ -128,13 +131,22 @@ export default class DevToolsHookService implements Services.ServiceInstance {
128131 }
129132 }
130133
134+ /**
135+ * beforeScenario is triggered at the beginning of every worker session, therefore
136+ * we can use it to reset the command stack and last command signature
137+ */
138+ beforeScenario ( ) {
139+ this . #lastCommandSig = null ;
140+ this . #commandStack = [ ]
141+ }
142+
131143 async beforeCommand ( command : string , args : string [ ] ) {
132144 if ( ! this . #browser) { return }
133145
134146 // Always inject the script to support iframe detection etc.
135147 await this . #sessionCapturer. injectScript ( getBrowserObject ( this . #browser) )
136148
137- /**
149+ /**
138150 * propagate url change to devtools app
139151 */
140152 if ( command === 'url' ) {
@@ -150,12 +162,24 @@ export default class DevToolsHookService implements Services.ServiceInstance {
150162 ! frame . getFileName ( ) ?. includes ( 'node_modules' )
151163 )
152164
153- if ( source && this . #commandStack. length === 0 ) {
154- this . #commandStack. push ( command )
165+ // If the command is a selector command, we don't want to capture it
166+ // as it is not a top-level user command.
167+ const isSelectorCommand = command . startsWith ( '$' ) || command . includes ( 'LocateNodes' )
168+
169+ if ( source && this . #commandStack. length === 0 && ! isSelectorCommand ) {
170+ const cmdSig = JSON . stringify ( {
171+ command,
172+ args,
173+ src : source . getFileName ( ) + ':' + source . getLineNumber ( )
174+ } ) ;
175+
176+ if ( this . #lastCommandSig !== cmdSig ) {
177+ this . #commandStack. push ( command ) ;
178+ this . #lastCommandSig = cmdSig ;
179+ }
155180 }
156181 }
157182
158-
159183 afterCommand ( command : keyof WebDriverCommands , args : any [ ] , result : any , error ?: Error ) {
160184 /* THE FIX: Ensure that the command is captured only if it matches the last command in the stack.
161185 * This prevents capturing commands that are not top-level user commands.
@@ -196,5 +220,5 @@ export default class DevToolsHookService implements Services.ServiceInstance {
196220 const traceFilePath = path . join ( outputDir , `wdio-trace-${ this . #browser. sessionId } .json` )
197221 await fs . writeFile ( traceFilePath , JSON . stringify ( traceLog ) )
198222 log . info ( `DevTools trace saved to ${ traceFilePath } ` )
199- }
223+ }
200224}
0 commit comments