Skip to content

Commit 454b5db

Browse files
committed
fix(driver): correct action command timeout hints
1 parent 1bbdaf1 commit 454b5db

File tree

1 file changed

+40
-24
lines changed

1 file changed

+40
-24
lines changed

packages/driver/src/cypress/timeout_diagnostics.ts

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
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-
108
interface TimeoutContext {
119
command: string
1210
selector?: string
@@ -32,7 +30,7 @@ export class TimeoutDiagnostics {
3230
dynamicContent: /loading|spinner|skeleton|placeholder/i,
3331
asyncLoad: /fetch|api|graphql|ajax/i,
3432
animation: /fade|slide|animate|transition/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

Comments
 (0)