Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 48 additions & 5 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,25 @@ 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 = `
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
newHtml = `
${indexHtmlContent.substring(0, endOfBody)}
<script>${loader}</script>
${indexHtmlContent.substring(endOfBody)}
`
}

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unrelated timeout diagnostics code accidentally committed

The PR is titled "Feat/vite async fs operations" but includes a complete timeout diagnostics feature for the driver package. The README is in Portuguese and describes this as a future feature that needs integration. The documentation explicitly states this is not yet integrated and requires modifications to error_utils.ts and other components. This appears to be work-in-progress code accidentally included in a PR about Vite async operations.

Additional Locations (2)

Fix in Cursor Fix in Web

Loading
Loading