Skip to content

Commit 1bcece7

Browse files
fix(driver): escape selector template literals
1 parent 5a0954b commit 1bcece7

File tree

1 file changed

+26
-22
lines changed

1 file changed

+26
-22
lines changed

packages/driver/src/cypress/timeout_diagnostics.ts

Lines changed: 26 additions & 22 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 = 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

Comments
 (0)