@@ -76,7 +76,7 @@ export class TimeoutDiagnostics {
7676
7777 // Check for dynamic content indicators
7878 if ( this . COMMON_PATTERNS . dynamicContent . test ( selector ) ) {
79- const escapedSelector = selector . replace ( / ' / g , '\\\'' )
79+ const escapedSelector = this . escapeSelector ( selector )
8080
8181 suggestions . push ( {
8282 reason : 'The selector appears to target dynamic/loading content that may not be ready yet' ,
@@ -92,8 +92,8 @@ export class TimeoutDiagnostics {
9292
9393 // Check for potentially incorrect selectors
9494 if ( selector . includes ( ' ' ) && ! selector . includes ( '[' ) && command === 'get' ) {
95- const escapedFirst = selector . split ( ' ' ) [ 0 ] . replace ( / ' / g , '\\\'' )
96- const escapedRest = selector . split ( ' ' ) . slice ( 1 ) . join ( ' ' ) . replace ( / ' / g , '\\\'' )
95+ const escapedFirst = this . escapeSelector ( selector . split ( ' ' ) [ 0 ] )
96+ const escapedRest = this . escapeSelector ( selector . split ( ' ' ) . slice ( 1 ) . join ( ' ' ) )
9797
9898 suggestions . push ( {
9999 reason : 'Complex selector detected - might be fragile or incorrect' ,
@@ -107,18 +107,21 @@ export class TimeoutDiagnostics {
107107 }
108108
109109 // Check for ID selectors that might be dynamic
110- if ( selector . startsWith ( '#' ) && / \d { 3 , } / . test ( selector ) ) {
111- const prefix = selector . split ( / \d / ) [ 0 ]
112- const escapedPrefix = prefix . replace ( / ' / g, '\\\'' )
113-
114- suggestions . push ( {
115- reason : 'Selector uses an ID with numbers - might be dynamically generated' ,
116- suggestions : [
117- 'Dynamic IDs change between sessions and will cause flaky tests' ,
118- 'Use a data-cy attribute or a more stable selector instead' ,
119- `If the ID is dynamic, use a partial match: cy.get('[id^="${ escapedPrefix } "]').first()` ,
120- ] ,
121- } )
110+ if ( selector . startsWith ( '#' ) ) {
111+ const prefixMatch = selector . match ( / ^ # ( [ ^ \d ] + ) \d { 3 , } / )
112+
113+ if ( prefixMatch ) {
114+ const escapedPrefix = this . escapeSelector ( prefixMatch [ 1 ] )
115+
116+ suggestions . push ( {
117+ reason : 'Selector uses an ID with numbers - might be dynamically generated' ,
118+ suggestions : [
119+ 'Dynamic IDs change between sessions and will cause flaky tests' ,
120+ 'Use a data-cy attribute or a more stable selector instead' ,
121+ `If the ID is dynamic, use a partial match: cy.get('[id^="${ escapedPrefix } "]').first()` ,
122+ ] ,
123+ } )
124+ }
122125 }
123126
124127 return suggestions
@@ -187,37 +190,17 @@ export class TimeoutDiagnostics {
187190
188191 private static getGeneralSuggestions ( context : TimeoutContext ) : DiagnosticSuggestion {
189192 const { command, timeout, selector } = context
190- const escapedSelector = selector ?. replace ( / ' / g, '\\\'' )
191- const actionCommands = new Set ( [
192- 'click' , 'type' , 'dblclick' , 'rightclick' , 'check' , 'uncheck' ,
193- 'select' , 'focus' , 'blur' , 'submit' , 'trigger' ,
194- ] )
195-
196- const timeoutSuggestion = ( ( ) => {
197- if ( escapedSelector && actionCommands . has ( command ) ) {
198- return `Increase timeout if needed: cy.get('${ escapedSelector } ').${ command } ({ timeout: ${ timeout * 2 } })`
199- }
200-
201- const args : string [ ] = [ ]
202-
203- if ( escapedSelector ) {
204- args . push ( `'${ escapedSelector } '` )
205- }
206-
207- args . push ( `{ timeout: ${ timeout * 2 } }` )
208-
209- return `Increase timeout if needed: cy.${ command } (${ args . join ( ', ' ) } )`
210- } ) ( )
193+ const escapedSelector = selector ? this . escapeSelector ( selector ) : undefined
211194
212195 const generalSuggestions = [
213- timeoutSuggestion ,
196+ `Increase timeout if needed: cy. ${ command } ( ${ escapedSelector ? `' ${ escapedSelector } ', ` : '' } { timeout: ${ timeout * 2 } })` ,
214197 'Verify the element/condition you\'re waiting for actually appears' ,
215198 'Check the browser console and Network tab for errors' ,
216199 'Use .debug() before the failing command to inspect the state: cy.debug()' ,
217200 ]
218201
219202 // Add command-specific suggestions
220- if ( [ 'get' , 'contains' ] . includes ( command ) && escapedSelector ) {
203+ if ( command === 'get' && escapedSelector ) {
221204 generalSuggestions . unshift (
222205 `Verify selector in DevTools: document.querySelector('${ escapedSelector } ')` ,
223206 'Ensure the element is not hidden by CSS (display: none, visibility: hidden)' ,
@@ -238,6 +221,14 @@ export class TimeoutDiagnostics {
238221 }
239222 }
240223
224+ private static escapeSelector ( selector : string ) : string {
225+ return selector
226+ . replace ( / \\ / g, '\\\\' )
227+ . replace ( / ` / g, '\\`' )
228+ . replace ( / \$ \{ / g, '\\${' )
229+ . replace ( / ' / g, '\\\'' )
230+ }
231+
241232 /**
242233 * Format diagnostic suggestions into a readable message
243234 */
0 commit comments