Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
65267ae
feat(vite-dev-server): migrate to async fs operations for better perf…
lucgonp Nov 6, 2025
8103f1c
fix: use promisify for better Node.js compatibility
lucgonp Nov 6, 2025
5383e8d
fix: implement lazy promise initialization and fix lint issues
lucgonp Nov 7, 2025
a388894
fix: add proper error handling to lazy promise initialization
lucgonp Nov 7, 2025
50d5920
feat: add intelligent timeout diagnostics system
lucgonp Nov 25, 2025
2b93731
fix: address Cursor Bot security and correctness issues
lucgonp Nov 25, 2025
3544244
fix(driver): preserve literal selectors in diagnostics
lucgonp Nov 26, 2025
5e45cf4
fix(vite-dev-server): avoid leading whitespace when injecting loader
lucgonp Nov 26, 2025
d705572
fix(driver): escape double quotes in diagnostics
lucgonp Nov 26, 2025
1d6787c
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Nov 27, 2025
5577760
docs(cli): add timeout diagnostics changelog entry
lucgonp Nov 28, 2025
48815f6
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Nov 30, 2025
bae73cd
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Dec 1, 2025
2e336ee
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Dec 2, 2025
40cfe95
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Dec 2, 2025
413b58e
chore: update timeout diagnostics documentation for clarity
lucgonp Dec 2, 2025
4706662
feat: enhance timeout suggestions for child commands in TimeoutDiagno…
lucgonp Dec 2, 2025
95ba223
fix: improve selector escaping in TimeoutDiagnostics for better handl…
lucgonp Dec 2, 2025
7ae0cd7
Merge branch 'develop' into feat/vite-with-timeout-backup
lucgonp Dec 3, 2025
88addf9
chore: update release date for version 15.7.1 in CHANGELOG.md
lucgonp Dec 3, 2025
99d2488
chore: update release date for version 15.7.1 in CHANGELOG.md to pending
lucgonp Dec 3, 2025
2e9247e
chore: update release date for version 15.7.1 in CHANGELOG.md
lucgonp Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 15.7.1

_Released 12/2/2025_
_Released 12/3/2025 (PENDING)_

**Features:**

- Added intelligent timeout diagnostics that surface contextual suggestions when commands exceed their timeouts, helping users resolve failures faster. Addressed in [#33022](https://github.com/cypress-io/cypress/pull/33022).

**Performance:**

Expand Down
60 changes: 52 additions & 8 deletions npm/vite-dev-server/src/plugins/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import type { ModuleNode, PluginOption, ViteDevServer } from 'vite-7'
import type { Vite } from '../getVite.js'
import { parse, HTMLElement } from 'node-html-parser'
import fs from 'fs'
import { promisify } from 'util'

import type { ViteDevServerConfig } from '../devServer.js'
import path from 'path'
import { fileURLToPath } from 'url'

const readFile = promisify(fs.readFile)

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

Expand All @@ -33,6 +36,7 @@ export const Cypress = (
vite: Vite,
): PluginOption => {
let base = '/'
let loaderPromise: Promise<string> | null = null

const projectRoot = options.cypressConfig.projectRoot
const supportFilePath = options.cypressConfig.supportFile ? path.resolve(projectRoot, options.cypressConfig.supportFile) : false
Expand All @@ -42,9 +46,35 @@ export const Cypress = (
const indexHtmlFile = options.cypressConfig.indexHtmlFile

let specsPathsSet = getSpecsPathsSet(specs)
// TODO: use async fs methods here
// eslint-disable-next-line no-restricted-syntax
let loader = fs.readFileSync(INIT_FILEPATH, 'utf8')

// Load the init file asynchronously with proper error handling
const loadInitFile = async (): Promise<string> => {
try {
const content = await readFile(INIT_FILEPATH, 'utf8')

debug(`Successfully loaded init file from ${INIT_FILEPATH}`)

return content
} catch (error) {
debug(`Failed to load init file from ${INIT_FILEPATH}:`, error)

throw new Error(`Failed to load Cypress init file: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

// Get or create the loader promise (lazy initialization with proper error handling)
const getLoaderPromise = (): Promise<string> => {
if (!loaderPromise) {
loaderPromise = loadInitFile().catch((error) => {
// Reset the promise so it can be retried later
loaderPromise = null

throw error
})
}

return loaderPromise
}

devServerEvents.on('dev-server:specs:changed', ({ specs, options }: { specs: Spec[], options?: { neededForJustInTimeCompile: boolean }}) => {
if (options?.neededForJustInTimeCompile) {
Expand Down Expand Up @@ -79,7 +109,7 @@ export const Cypress = (

debug('resolved the indexHtmlPath as', indexHtmlPath, 'from', indexHtmlFile)

let indexHtmlContent = await fs.promises.readFile(indexHtmlPath, { encoding: 'utf8' })
let indexHtmlContent = await readFile(indexHtmlPath, 'utf8')

// Inject the script tags
indexHtmlContent = indexHtmlContent.replace(
Expand All @@ -92,12 +122,26 @@ export const Cypress = (
// find </body> last index
const endOfBody = indexHtmlContent.lastIndexOf('</body>')

// Get the loader content asynchronously
const loader = await getLoaderPromise()

// insert the script in the end of the body
const newHtml = `
${indexHtmlContent.substring(0, endOfBody)}
let newHtml: string

if (endOfBody === -1) {
// No closing body tag found, append at the end
debug('No closing body tag found, appending script at end of HTML')
newHtml = `${indexHtmlContent}
<script>${loader}</script>`
} else {
// Insert before closing body tag without extra leading whitespace
const beforeBody = indexHtmlContent.substring(0, endOfBody)
const afterBody = indexHtmlContent.substring(endOfBody)

newHtml = `${beforeBody}
<script>${loader}</script>
${indexHtmlContent.substring(endOfBody)}
`
${afterBody}`
}

return newHtml
},
Expand Down
198 changes: 198 additions & 0 deletions packages/driver/src/cypress/TIMEOUT_DIAGNOSTICS_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Timeout Diagnostics - Smart Error Messages

## 🎯 Objetivo

Melhorar dramaticamente a experiência do desenvolvedor ao lidar com erros de timeout no Cypress, fornecendo **sugestões contextuais e acionáveis** baseadas na análise do contexto do erro.

## 🚀 Motivação

Erros de timeout são extremamente comuns em testes Cypress, mas as mensagens tradicionais são genéricas:

```
cy.get() timed out waiting 4000ms
```

Com este sistema, os desenvolvedores recebem diagnósticos inteligentes:

```
cy.get() timed out waiting 4000ms

🔍 Diagnostic Suggestions:

1. The selector appears to target dynamic/loading content
a) Wait for the loading state to complete: cy.get('.loading-spinner').should('not.exist')
b) Consider using data-cy attributes instead of class names that indicate loading states
c) Use cy.intercept() to wait for the API request that populates this content
📚 Learn more: https://on.cypress.io/best-practices#Selecting-Elements

2. 8 network requests are still pending
a) Wait for specific API calls to complete using cy.intercept()
b) Consider increasing the timeout if the requests are expected to be slow
c) Check if some requests are failing or hanging in the Network tab
d) Example: cy.intercept("GET", "/api/data").as("getData"); cy.wait("@getData")
📚 Learn more: https://on.cypress.io/intercept
```

## ✨ Funcionalidades

### 1. **Detecção de Seletores Problemáticos**
- Identifica seletores que apontam para conteúdo dinâmico (loading, spinner, skeleton)
- Detecta seletores complexos e frágeis
- Alerta sobre IDs dinâmicos (ex: `#user-12345`)

### 2. **Análise de Problemas de Rede**
- Detecta múltiplas requisições pendentes
- Identifica timeouts longos que sugerem operações assíncronas
- Sugere uso de `cy.intercept()` para melhor controle

### 3. **Diagnóstico de Animações**
- Identifica quando animações estão causando delays
- Sugere configurações para desabilitar animações em testes

### 4. **Detecção de Mutações Excessivas do DOM**
- Identifica quando o DOM está mudando rapidamente
- Sugere esperar por estabilização antes de interagir

### 5. **Sugestões Específicas por Comando**
- Sugestões customizadas para cada tipo de comando (`get`, `click`, `type`, etc.)
- Links para documentação relevante

## 📦 Estrutura

```
packages/driver/src/cypress/
└── timeout_diagnostics.ts # Lógica principal

packages/driver/test/unit/cypress/
└── timeout_diagnostics.spec.ts # Testes unitários
```

## 🔧 API

```typescript
import { TimeoutDiagnostics } from './timeout_diagnostics'

// Analisar contexto e obter sugestões
const suggestions = TimeoutDiagnostics.analyze({
command: 'get',
selector: '.loading-spinner',
timeout: 4000,
networkRequests: 5,
animationsRunning: true,
})

// Formatar sugestões para exibição
const formatted = TimeoutDiagnostics.formatSuggestions(suggestions)

// Enriquecer mensagem de erro existente
const enhanced = TimeoutDiagnostics.enhanceTimeoutError(
'cy.get() timed out',
context
)
```

## 🎨 Exemplos de Uso

### Exemplo 1: Conteúdo Dinâmico
```typescript
// Teste que falha
cy.get('.loading-spinner').click()

// Erro melhorado sugere:
// "Wait for the loading state to complete:
// cy.get('.loading-spinner').should('not.exist')"
```

### Exemplo 2: Problemas de Rede
```typescript
// Teste aguardando resposta API
cy.get('.user-data').should('be.visible')

// Erro melhorado sugere:
// "Use cy.intercept() to wait for the specific request:
// cy.intercept('GET', '/api/users').as('getUsers')
// cy.wait('@getUsers')"
```

### Exemplo 3: Animações
```typescript
// Elemento animando quando clica
cy.get('.modal-button').click()

// Erro melhorado sugere:
// "Disable animations: .click({ waitForAnimations: false })
// Or globally: Cypress.config('animationDistanceThreshold', 0)"
```

## 🔮 Integração Futura

Para integrar completamente no Cypress, seria necessário:

1. **Modificar `error_utils.ts`** para capturar contexto adicional durante timeouts
2. **Coletar métricas** de rede, DOM e animações durante execução do comando
3. **Integrar na pipeline de erro** existente do Cypress
4. **Adicionar configuração** para habilitar/desabilitar diagnósticos

```typescript
// Exemplo de integração em error_utils.ts
import TimeoutDiagnostics from './timeout_diagnostics'

const createTimeoutError = (cmd, ms) => {
const context = {
command: cmd.get('name'),
selector: cmd.get('selector'),
timeout: ms,
networkRequests: getNetworkMonitor().pendingCount(),
animationsRunning: hasRunningAnimations(),
domMutations: getDOMMutationCount(),
}

const baseMessage = `cy.${cmd.get('name')}() timed out waiting ${ms}ms`
return TimeoutDiagnostics.enhanceTimeoutError(baseMessage, context)
}
```

## 📊 Benefícios

1. **Reduz tempo de debugging**: Desenvolvedores identificam problemas mais rapidamente
2. **Educação inline**: Ensina melhores práticas durante o desenvolvimento
3. **Menos frustração**: Erros mais claros = desenvolvedores mais felizes
4. **Reduz issues no GitHub**: Menos perguntas sobre "por que meu teste timeout?"
5. **Melhora adoção**: Desenvolvedores iniciantes aprendem mais rápido

## 🧪 Testes

Execute os testes unitários:

```bash
cd packages/driver
yarn test timeout_diagnostics.spec.ts
```

Cobertura inclui:
- ✅ Detecção de todos os tipos de problemas
- ✅ Formatação de mensagens
- ✅ Casos extremos e edge cases
- ✅ Combinação de múltiplos diagnósticos

## 🚀 Próximos Passos

1. ✅ Criar módulo de diagnósticos com testes
2. ⏳ Integrar com sistema de erros existente
3. ⏳ Adicionar coleta de métricas de contexto
4. ⏳ Criar configuração para habilitar/desabilitar
5. ⏳ Adicionar mais padrões e sugestões baseado em feedback
6. ⏳ Documentação para usuários finais

## 🤝 Contribuindo

Este é um sistema extensível. Para adicionar novos diagnósticos:

1. Adicione padrão em `COMMON_PATTERNS`
2. Crie método `analyze*Issues()`
3. Adicione testes correspondentes
4. Documente o novo diagnóstico

## 📝 Licença

MIT - Consistente com o projeto Cypress
Loading