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 = selector . replace ( / ' / g, '\\\'' )
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 = selector . split ( ' ' ) [ 0 ] . replace ( / ' / g, '\\\'' )
96+ const escapedRest = selector . split ( ' ' ) . slice ( 1 ) . join ( ' ' ) . replace ( / ' / g, '\\\'' )
9997
10098 suggestions . push ( {
10199 reason : 'Complex selector detected - might be fragile or incorrect' ,
@@ -110,8 +108,8 @@ export class TimeoutDiagnostics {
110108
111109 // Check for ID selectors that might be dynamic
112110 if ( selector . startsWith ( '#' ) && / \d { 3 , } / . test ( selector ) ) {
113- const prefix = selector . split ( / \d / ) [ 0 ] ;
114- const escapedPrefix = prefix . replace ( / ' / g, "\\'" ) ;
111+ const prefix = selector . split ( / \d / ) [ 0 ]
112+ const escapedPrefix = prefix . replace ( / ' / g, '\\\'' )
115113
116114 suggestions . push ( {
117115 reason : 'Selector uses an ID with numbers - might be dynamically generated' ,
@@ -126,7 +124,7 @@ export class TimeoutDiagnostics {
126124 return suggestions
127125 }
128126
129- private static analyzeNetworkIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
127+ private static analyzeNetworkIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
130128 const suggestions : DiagnosticSuggestion [ ] = [ ]
131129 const { networkRequests = 0 , timeout } = context
132130
@@ -161,7 +159,7 @@ export class TimeoutDiagnostics {
161159 return suggestions
162160 }
163161
164- private static analyzeAnimationIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
162+ private static analyzeAnimationIssues ( context : TimeoutContext ) : DiagnosticSuggestion [ ] {
165163 return [ {
166164 reason : 'Animations are still running when the command timed out' ,
167165 suggestions : [
@@ -174,7 +172,7 @@ export class TimeoutDiagnostics {
174172 } ]
175173 }
176174
177- private static analyzeDOMMutationIssues ( context : TimeoutContext ) : DiagnosticSuggestion {
175+ private static analyzeDOMMutationIssues ( context : TimeoutContext ) : DiagnosticSuggestion {
178176 return {
179177 reason : `The DOM is changing rapidly (${ context . domMutations } mutations detected)` ,
180178 suggestions : [
@@ -187,21 +185,39 @@ export class TimeoutDiagnostics {
187185 }
188186 }
189187
190- private static getGeneralSuggestions ( context : TimeoutContext ) : DiagnosticSuggestion {
188+ private static getGeneralSuggestions ( context : TimeoutContext ) : DiagnosticSuggestion {
191189 const { command, timeout, selector } = context
192- const escapedSelector = selector ?. replace ( / ' / g, "\\'" )
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+ } ) ( )
193211
194212 const generalSuggestions = [
195- `Increase timeout if needed: cy. ${ command } ( ${ escapedSelector ? `' ${ escapedSelector } ', ` : '' } { timeout: ${ timeout * 2 } })` ,
213+ timeoutSuggestion ,
196214 'Verify the element/condition you\'re waiting for actually appears' ,
197215 'Check the browser console and Network tab for errors' ,
198216 'Use .debug() before the failing command to inspect the state: cy.debug()' ,
199217 ]
200218
201219 // Add command-specific suggestions
202- if ( [ 'get' , 'contains' ] . includes ( command ) && selector ) {
203- const escapedSelector = selector . replace ( / ' / g, "\\'" ) ;
204-
220+ if ( [ 'get' , 'contains' ] . includes ( command ) && escapedSelector ) {
205221 generalSuggestions . unshift (
206222 `Verify selector in DevTools: document.querySelector('${ escapedSelector } ')` ,
207223 'Ensure the element is not hidden by CSS (display: none, visibility: hidden)' ,
@@ -225,14 +241,14 @@ export class TimeoutDiagnostics {
225241 /**
226242 * Format diagnostic suggestions into a readable message
227243 */
228- static formatSuggestions ( suggestions : DiagnosticSuggestion [ ] ) : string {
244+ static formatSuggestions ( suggestions : DiagnosticSuggestion [ ] ) : string {
229245 if ( suggestions . length === 0 ) return ''
230246
231247 let output = '\n\n🔍 Diagnostic Suggestions:\n'
232248
233249 suggestions . forEach ( ( suggestion , index ) => {
234250 output += `\n${ index + 1 } . ${ suggestion . reason } \n`
235-
251+
236252 suggestion . suggestions . forEach ( ( tip , tipIndex ) => {
237253 output += ` ${ String . fromCharCode ( 97 + tipIndex ) } ) ${ tip } \n`
238254 } )
@@ -248,10 +264,10 @@ export class TimeoutDiagnostics {
248264 /**
249265 * Enhanced error message with diagnostics
250266 */
251- static enhanceTimeoutError ( originalMessage : string , context : TimeoutContext ) : string {
267+ static enhanceTimeoutError ( originalMessage : string , context : TimeoutContext ) : string {
252268 const suggestions = this . analyze ( context )
253269 const diagnostics = this . formatSuggestions ( suggestions )
254-
270+
255271 return originalMessage + diagnostics
256272 }
257273}
0 commit comments