11/**
22 * Timeout Diagnostics - Smart suggestions for timeout errors
3- *
3+ *
44 * This module provides contextual diagnostics and actionable suggestions
55 * when commands timeout, helping developers quickly identify and fix issues.
66 */
77
8- import _ from 'lodash'
9-
108interface TimeoutContext {
119 command : string
1210 selector ?: string
@@ -32,7 +30,7 @@ export class TimeoutDiagnostics {
3230 dynamicContent : / l o a d i n g | s p i n n e r | s k e l e t o n | p l a c e h o l d e r / i,
3331 asyncLoad : / f e t c h | a p i | g r a p h q l | a j a x / i,
3432 animation : / f a d e | s l i d e | a n i m a t e | t r a n s i t i o n / i,
35-
33+
3634 // Network patterns
3735 slowNetwork : 3000 , // threshold in ms
3836 manyRequests : 5 ,
@@ -41,7 +39,7 @@ export class TimeoutDiagnostics {
4139 /**
4240 * Generate diagnostic suggestions based on timeout context
4341 */
44- static analyze ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
42+ static analyze ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
4543 const suggestions : DiagnosticSuggestion [ ] = [ ]
4644
4745 // Check for common selector issues
@@ -72,13 +70,13 @@ export class TimeoutDiagnostics {
7270 return suggestions
7371 }
7472
75- private static analyzeSelectorIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
73+ private static analyzeSelectorIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
7674 const suggestions : DiagnosticSuggestion [ ] = [ ]
7775 const { selector = '' , command } = context
7876
7977 // Check for dynamic content indicators
8078 if ( this . COMMON_PATTERNS . dynamicContent . test ( selector ) ) {
81- const escapedSelector = selector . replace ( / ' / g , "\\'" ) ;
79+ const escapedSelector = this . escapeSelector ( selector )
8280
8381 suggestions . push ( {
8482 reason : 'The selector appears to target dynamic/loading content that may not be ready yet' ,
@@ -94,8 +92,8 @@ export class TimeoutDiagnostics {
9492
9593 // Check for potentially incorrect selectors
9694 if ( selector . includes ( ' ' ) && ! selector . includes ( '[' ) && command === 'get' ) {
97- const escapedFirst = selector . split ( ' ' ) [ 0 ] . replace ( / ' / g , "\\'" ) ;
98- 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 ( ' ' ) )
9997
10098 suggestions . push ( {
10199 reason : 'Complex selector detected - might be fragile or incorrect' ,
@@ -113,7 +111,7 @@ export class TimeoutDiagnostics {
113111 const prefixMatch = selector . match ( / ^ ( # [ ^ \d ] + ) \d { 3 , } / )
114112
115113 if ( prefixMatch ) {
116- const escapedPrefix = prefixMatch [ 1 ] . replace ( / ' / g , "\\'" )
114+ const escapedPrefix = this . escapeSelector ( prefixMatch [ 1 ] )
117115
118116 suggestions . push ( {
119117 reason : 'Selector uses an ID with numbers - might be dynamically generated' ,
@@ -129,7 +127,7 @@ export class TimeoutDiagnostics {
129127 return suggestions
130128 }
131129
132- private static analyzeNetworkIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
130+ private static analyzeNetworkIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
133131 const suggestions : DiagnosticSuggestion [ ] = [ ]
134132 const { networkRequests = 0 , timeout } = context
135133
@@ -164,7 +162,7 @@ export class TimeoutDiagnostics {
164162 return suggestions
165163 }
166164
167- private static analyzeAnimationIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
165+ private static analyzeAnimationIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
168166 return [ {
169167 reason : 'Animations are still running when the command timed out' ,
170168 suggestions : [
@@ -177,7 +175,7 @@ export class TimeoutDiagnostics {
177175 } ]
178176 }
179177
180- private static analyzeDOMMutationIssues ( context : TimeoutContext ) : DiagnosticSuggestion {
178+ private static analyzeDOMMutationIssues ( context : TimeoutContext ) : DiagnosticSuggestion {
181179 return {
182180 reason : `The DOM is changing rapidly (${ context . domMutations } mutations detected)` ,
183181 suggestions : [
@@ -190,9 +188,9 @@ export class TimeoutDiagnostics {
190188 }
191189 }
192190
193- private static getGeneralSuggestions ( context : TimeoutContext ) : DiagnosticSuggestion {
191+ private static getGeneralSuggestions ( context : TimeoutContext ) : DiagnosticSuggestion {
194192 const { command, timeout, selector } = context
195- const escapedSelector = selector ?. replace ( / ' / g , "\\'" )
193+ const escapedSelector = selector ? this . escapeSelector ( selector ) : undefined
196194
197195 const generalSuggestions = [
198196 `Increase timeout if needed: cy.${ command } (${ escapedSelector ? `'${ escapedSelector } ', ` : '' } { timeout: ${ timeout * 2 } })` ,
@@ -202,9 +200,7 @@ export class TimeoutDiagnostics {
202200 ]
203201
204202 // Add command-specific suggestions
205- if ( [ 'get' , 'contains' ] . includes ( command ) && selector ) {
206- const escapedSelector = selector . replace ( / ' / g, "\\'" ) ;
207-
203+ if ( [ 'get' , 'contains' ] . includes ( command ) && escapedSelector ) {
208204 generalSuggestions . unshift (
209205 `Verify selector in DevTools: document.querySelector('${ escapedSelector } ')` ,
210206 'Ensure the element is not hidden by CSS (display: none, visibility: hidden)' ,
@@ -225,17 +221,25 @@ export class TimeoutDiagnostics {
225221 }
226222 }
227223
224+ private static escapeSelector ( selector : string ) : string {
225+ return selector
226+ . replace ( / \\ / g, '\\\\' )
227+ . replace ( / ` / g, '\`' )
228+ . replace ( / \$ \{ / g, '\${' )
229+ . replace ( / ' / g, '\\\'' )
230+ }
231+
228232 /**
229233 * Format diagnostic suggestions into a readable message
230234 */
231- static formatSuggestions ( suggestions : DiagnosticSuggestion [ ] ) : string {
235+ static formatSuggestions ( suggestions : DiagnosticSuggestion [ ] ) : string {
232236 if ( suggestions . length === 0 ) return ''
233237
234238 let output = '\n\n🔍 Diagnostic Suggestions:\n'
235239
236240 suggestions . forEach ( ( suggestion , index ) => {
237241 output += `\n${ index + 1 } . ${ suggestion . reason } \n`
238-
242+
239243 suggestion . suggestions . forEach ( ( tip , tipIndex ) => {
240244 output += ` ${ String . fromCharCode ( 97 + tipIndex ) } ) ${ tip } \n`
241245 } )
@@ -251,10 +255,10 @@ export class TimeoutDiagnostics {
251255 /**
252256 * Enhanced error message with diagnostics
253257 */
254- static enhanceTimeoutError ( originalMessage : string , context : TimeoutContext ) : string {
258+ static enhanceTimeoutError ( originalMessage : string , context : TimeoutContext ) : string {
255259 const suggestions = this . analyze ( context )
256260 const diagnostics = this . formatSuggestions ( suggestions )
257-
261+
258262 return originalMessage + diagnostics
259263 }
260264}
0 commit comments