TL;DR
- Cypress corre dentro del navegador, haciendo los tests rápidos y confiables sin WebDriver
- Instala con
npm install cypress --save-dev, luego ejecutanpx cypress open- Usa atributos
data-*para selectores — sobreviven cambios de UIIdeal para: Principiantes aprendiendo automatización, equipos testeando apps web JavaScript No es para ti si: Necesitas testing de apps móviles o soporte de Safari out of the box
Recuerdo mi primera semana intentando automatizar tests de navegador con Selenium. Archivos de configuración, descargas de drivers, mensajes de error oscuros sobre session IDs. Pasé más tiempo debuggeando mi setup de tests que escribiendo tests reales. Cuando descubrí Cypress, todo cambió. Tenía un test funcionando en 15 minutos.
Cypress se ha convertido en la opción preferida para testear aplicaciones web modernas. Corre directamente dentro del navegador, proporciona feedback instantáneo y requiere configuración mínima. Esta guía te lleva desde la instalación hasta correr tests en pipelines de CI/CD.
¿Por qué Cypress?
Antes de escribir código, entendamos qué hace diferente a Cypress de las herramientas tradicionales.
Cypress vs Herramientas Tradicionales:
| Característica | Cypress | Selenium/WebDriver |
|---|---|---|
| Arquitectura | Corre dentro del navegador | Proceso de driver externo |
| Tiempo de setup | Minutos | Horas (drivers, configs) |
| Debugging | Time-travel, snapshots | Screenshots, logs |
| Velocidad | Rápido (sin saltos de red) | Más lento (comandos HTTP) |
| Flakiness | Bajo (esperas automáticas) | Mayor (esperas manuales) |
Cypress sobresale testeando aplicaciones single-page construidas con React, Vue, Angular o frameworks similares. Proporciona recarga en tiempo real, esperas automáticas y una experiencia de debugging que se siente como usar DevTools del navegador.
Si estás construyendo una estrategia de testing comprehensiva, entender la pirámide de automatización de tests te ayuda a decidir dónde encaja Cypress junto a unit tests y API tests.
Instalando Cypress
Prerrequisitos
Necesitas Node.js instalado en tu máquina. Cualquier versión desde 18.x funciona bien.
# Verificar versión de Node.js
node --version
# Debería mostrar v18.x.x o superior
Pasos de Instalación
1. Inicializar un proyecto (si no tienes uno):
mkdir my-cypress-tests
cd my-cypress-tests
npm init -y
2. Instalar Cypress:
npm install cypress --save-dev
Esto descarga Cypress y su navegador Electron. La primera instalación toma un par de minutos.
3. Abrir Cypress:
npx cypress open
Esto lanza el Cypress Test Runner — una interfaz visual para correr tests.
Primera Experiencia de Lanzamiento
Cuando abres Cypress por primera vez, crea una estructura de carpetas:
cypress/
├── e2e/ # Tus archivos de test van aquí
├── fixtures/ # Datos de test (archivos JSON)
├── support/ # Comandos personalizados y setup
│ ├── commands.js # Comandos personalizados
│ └── e2e.js # Se ejecuta antes de cada archivo de test
└── downloads/ # Archivos descargados durante tests
Cypress también crea cypress.config.js en la raíz de tu proyecto — este es tu archivo de configuración principal.
Escribiendo Tu Primer Test
Escribamos un test que visita una página y verifica su contenido. Crea un archivo cypress/e2e/first-test.cy.js:
describe('Mi Primer Test', () => {
it('visita la página de ejemplo', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
cy.url().should('include', '/commands/actions')
cy.get('.action-email')
.type('test@example.com')
.should('have.value', 'test@example.com')
})
})
Desglose:
describe()agrupa tests relacionadosit()define un caso de test individualcy.visit()navega a una URLcy.contains()encuentra elemento por contenido de textocy.get()encuentra elemento por selector CSS.type()ingresa texto en un input.should()hace una aserción
Ejecuta este test haciendo clic en first-test.cy.js en el Test Runner.
Entendiendo la Estructura de Tests
Cada test de Cypress sigue un patrón:
describe('Nombre de Feature', () => {
beforeEach(() => {
// Se ejecuta antes de cada test
cy.visit('/login')
})
it('hace algo específico', () => {
// Arrange: configura condiciones del test
// Act: realiza la acción
// Assert: verifica el resultado
})
it('maneja otro escenario', () => {
// Otro caso de test
})
})
El hook beforeEach se ejecuta antes de cada test en el bloque describe. Úsalo para setup común como login o navegación a una página.
Selectores: Encontrando Elementos
Encontrar los elementos correctos es crucial para tests confiables. Cypress soporta varias estrategias de selectores.
Prioridad de Selectores (Mejor a Peor)
1. Atributos data (recomendado):
cy.get('[data-test="submit-button"]')
cy.get('[data-cy="login-form"]')
cy.get('[data-testid="user-email"]')
Los atributos data existen específicamente para testing. No cambian cuando cambian los estilos.
2. Selectores ID:
cy.get('#username')
Los IDs son estables pero no siempre están disponibles. No agregues IDs solo para testing — usa atributos data en su lugar.
3. Contenido de texto:
cy.contains('Enviar')
cy.contains('button', 'Enviar') // Más específico
Bueno para botones y enlaces. Se rompe si el texto cambia o se traduce.
4. Selectores CSS (evitar si es posible):
cy.get('.btn-primary')
cy.get('form input[type="email"]')
Las clases CSS cambian frecuentemente. Selectores complejos son frágiles.
Agregando Atributos de Test a Tu App
Trabaja con tu equipo de desarrollo para agregar atributos de test:
<!-- Antes -->
<button class="btn btn-primary">Registrarse</button>
<!-- Después -->
<button class="btn btn-primary" data-test="signup-button">Registrarse</button>
El atributo data-test sobrevive refactorizaciones, cambios de temas y actualizaciones de CSS.
Cypress Testing Library
Para mejores selectores basados en accesibilidad, instala Testing Library:
npm install @testing-library/cypress --save-dev
Agrega a cypress/support/commands.js:
import '@testing-library/cypress/add-commands'
Ahora puedes usar selectores accesibles:
cy.findByRole('button', { name: 'Enviar' })
cy.findByLabelText('Dirección de Email')
cy.findByPlaceholderText('Ingresa tu email')
Estos selectores coinciden con cómo los usuarios interactúan con tu app.
Aserciones y Expectativas
Cypress usa aserciones Chai. Los patrones más comunes:
Aserciones Should
// Visibilidad
cy.get('[data-test="header"]').should('be.visible')
cy.get('[data-test="modal"]').should('not.exist')
// Contenido
cy.get('[data-test="title"]').should('have.text', 'Bienvenido')
cy.get('[data-test="title"]').should('contain', 'Bienvenido')
// Atributos
cy.get('input').should('have.value', 'test@example.com')
cy.get('a').should('have.attr', 'href', '/dashboard')
// Estado
cy.get('button').should('be.disabled')
cy.get('input').should('be.enabled')
cy.get('checkbox').should('be.checked')
// Cantidad
cy.get('[data-test="list-item"]').should('have.length', 5)
cy.get('[data-test="list-item"]').should('have.length.gt', 3)
Encadenando Aserciones
cy.get('[data-test="user-card"]')
.should('be.visible')
.and('contain', 'Juan Pérez')
.and('have.class', 'active')
Expect para Verificaciones Complejas
cy.get('[data-test="product-list"]').then(($list) => {
const itemCount = $list.find('.product').length
expect(itemCount).to.be.greaterThan(0)
expect(itemCount).to.be.lessThan(100)
})
Interactuando con Elementos
Acciones Comunes
// Clicks
cy.get('button').click()
cy.get('button').dblclick()
cy.get('button').rightclick()
// Escribir
cy.get('input').type('Hola Mundo')
cy.get('input').type('Hola{enter}') // Presionar Enter
cy.get('input').clear().type('Nuevo texto')
// Seleccionar
cy.get('select').select('Opción 1')
cy.get('select').select(['Opción 1', 'Opción 2']) // Multi-select
// Checkboxes y Radios
cy.get('[type="checkbox"]').check()
cy.get('[type="checkbox"]').uncheck()
cy.get('[type="radio"]').check()
// Subir Archivos
cy.get('input[type="file"]').selectFile('cypress/fixtures/image.png')
// Scroll
cy.get('[data-test="footer"]').scrollIntoView()
Manejando Formularios
Un test de formulario típico:
describe('Formulario de Contacto', () => {
beforeEach(() => {
cy.visit('/contact')
})
it('envía exitosamente con datos válidos', () => {
cy.get('[data-test="name"]').type('María García')
cy.get('[data-test="email"]').type('maria@example.com')
cy.get('[data-test="message"]').type('¡Hola desde Cypress!')
cy.get('[data-test="submit"]').click()
cy.get('[data-test="success-message"]')
.should('be.visible')
.and('contain', 'Gracias')
})
it('muestra error para email inválido', () => {
cy.get('[data-test="email"]').type('email-invalido')
cy.get('[data-test="submit"]').click()
cy.get('[data-test="email-error"]')
.should('be.visible')
.and('contain', 'email válido')
})
})
Trabajando con APIs
Cypress puede interceptar y mockear llamadas API. Esto hace los tests más rápidos y confiables.
Interceptando Requests
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
cy.get('[data-test="user-list"]').should('be.visible')
El cy.wait('@getUsers') pausa hasta que la llamada API se complete. No más hacks con cy.wait(5000).
Mockeando Respuestas de API
cy.intercept('GET', '/api/products', {
statusCode: 200,
body: [
{ id: 1, name: 'Producto A', price: 29.99 },
{ id: 2, name: 'Producto B', price: 39.99 }
]
}).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts')
cy.get('[data-test="product"]').should('have.length', 2)
Mockear te permite testear casos edge sin cambios en el backend.
Testeando Estados de Error
cy.intercept('POST', '/api/checkout', {
statusCode: 500,
body: { error: 'Pago fallido' }
}).as('checkoutError')
cy.get('[data-test="checkout-button"]').click()
cy.wait('@checkoutError')
cy.get('[data-test="error-message"]')
.should('be.visible')
.and('contain', 'Pago fallido')
Para cobertura profunda de técnicas de network stubbing, revisa Cypress Deep Dive: Arquitectura, Debugging y Network Stubbing.
Configuración de Tests
cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
video: false,
screenshotOnRunFailure: true,
setupNodeEvents(on, config) {
// Event listeners de Node aquí
}
}
})
Configuraciones comunes:
| Configuración | Descripción | Default |
|---|---|---|
baseUrl | Se antepone a URLs de cy.visit() | ninguno |
viewportWidth/Height | Dimensiones del navegador | 1000 × 660 |
defaultCommandTimeout | Tiempo de reintento de comandos | 4000ms |
video | Grabar videos de ejecuciones | true |
retries | Auto-reintentar tests fallidos | 0 |
Variables de Entorno
// cypress.config.js
module.exports = defineConfig({
e2e: {
env: {
apiUrl: 'http://localhost:4000',
adminUser: 'admin@test.com'
}
}
})
Acceso en tests:
cy.visit(Cypress.env('apiUrl') + '/login')
cy.get('input').type(Cypress.env('adminUser'))
O pasar vía línea de comandos:
npx cypress run --env apiUrl=http://staging.example.com
Comandos Personalizados
Crea comandos reutilizables en cypress/support/commands.js:
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login')
cy.get('[data-test="email"]').type(email)
cy.get('[data-test="password"]').type(password)
cy.get('[data-test="submit"]').click()
cy.url().should('include', '/dashboard')
})
Cypress.Commands.add('logout', () => {
cy.get('[data-test="user-menu"]').click()
cy.get('[data-test="logout"]').click()
})
Uso en tests:
describe('Dashboard', () => {
beforeEach(() => {
cy.login('user@example.com', 'password123')
})
it('muestra perfil de usuario', () => {
cy.get('[data-test="profile"]').should('be.visible')
})
})
Login Programático
Para tests más rápidos, salta el login de UI:
Cypress.Commands.add('loginByApi', (email, password) => {
cy.request({
method: 'POST',
url: '/api/auth/login',
body: { email, password }
}).then((response) => {
window.localStorage.setItem('authToken', response.body.token)
})
})
Esto es mucho más rápido que llenar formularios repetidamente.
Organizando Tests
Estructura de Carpetas
cypress/
├── e2e/
│ ├── auth/
│ │ ├── login.cy.js
│ │ └── registration.cy.js
│ ├── products/
│ │ ├── listing.cy.js
│ │ └── checkout.cy.js
│ └── smoke/
│ └── critical-paths.cy.js
├── fixtures/
│ ├── users.json
│ └── products.json
└── support/
├── commands.js
└── e2e.js
Usando Fixtures
Almacena datos de test en cypress/fixtures/:
// cypress/fixtures/users.json
{
"admin": {
"email": "admin@example.com",
"password": "admin123"
},
"regular": {
"email": "user@example.com",
"password": "user123"
}
}
Cargar en tests:
cy.fixture('users').then((users) => {
cy.login(users.admin.email, users.admin.password)
})
// O usar directamente en intercepts
cy.intercept('GET', '/api/users', { fixture: 'users.json' })
Ejecutando Tests en CI/CD
Ejecución por Línea de Comandos
# Ejecutar todos los tests
npx cypress run
# Ejecutar archivo específico
npx cypress run --spec "cypress/e2e/auth/*.cy.js"
# Ejecutar en navegador específico
npx cypress run --browser chrome
# Ejecutar con variables de entorno
npx cypress run --env apiUrl=http://staging.example.com
Integración con GitHub Actions
Crea .github/workflows/cypress.yml:
name: Cypress Tests
on: [push, pull_request]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Start application
run: npm start &
- name: Wait for app
run: npx wait-on http://localhost:3000
- name: Run Cypress tests
uses: cypress-io/github-action@v6
with:
wait-on: 'http://localhost:3000'
browser: chrome
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
Para más patrones de CI/CD, revisa GitHub Actions para Automatización QA.
Ejecución Paralela
Para test suites grandes, ejecuta tests en paralelo:
jobs:
cypress:
runs-on: ubuntu-latest
strategy:
matrix:
containers: [1, 2, 3]
steps:
- uses: cypress-io/github-action@v6
with:
record: true
parallel: true
group: 'UI Tests'
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
Esto requiere Cypress Cloud (nivel gratuito disponible).
Debuggeando Tests Fallidos
Time Travel
Haz clic en cualquier comando en el Test Runner para ver el estado del DOM en ese momento. Esto es increíblemente útil para entender por qué un selector falló.
Screenshots y Videos
// Tomar screenshot manualmente
cy.screenshot('before-submit')
// Screenshots son automáticos al fallar
// Videos graban toda la ejecución del test
Configurar en cypress.config.js:
module.exports = defineConfig({
e2e: {
screenshotOnRunFailure: true,
video: true,
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots'
}
})
Comando Debug
cy.get('[data-test="menu"]').debug()
Esto pausa la ejecución y loguea el elemento a la consola.
Logging a Consola
cy.get('[data-test="items"]').then(($items) => {
console.log('Cantidad de items:', $items.length)
console.log('Primer item:', $items.first().text())
})
Abre DevTools del navegador para ver el output.
Mejores Prácticas
Hacer
- Usar atributos
data-testpara selectores - Esperar llamadas API en vez de timeouts arbitrarios
- Mantener tests independientes (cada uno puede correr solo)
- Usar
beforeEachpara setup común - Testear una cosa por caso de test
- Mockear servicios externos
No Hacer
- Usar
cy.wait(5000)— esperar eventos específicos - Compartir estado entre tests
- Testear detalles de implementación (clases CSS, estructura HTML)
- Sobre-mockear — algunos tests deben ir a APIs reales
- Escribir tests enormes — mantenerlos enfocados
Manejando Tests Flaky
// Malo - dependiente del tiempo
cy.wait(3000)
cy.get('[data-test="results"]').should('exist')
// Bueno - espera condición específica
cy.intercept('GET', '/api/search*').as('search')
cy.get('[data-test="search"]').type('query')
cy.wait('@search')
cy.get('[data-test="results"]').should('exist')
¿Qué Sigue?
Una vez que estés cómodo con lo básico, explora estos temas avanzados:
- Component testing: Testear componentes React/Vue en aislamiento
- Regresión visual: Capturar cambios inesperados de UI
- Monitoreo de performance: Rastrear tiempos de carga
- Testing de accesibilidad: Asegurar que tu app funciona para todos
Para patrones avanzados de Cypress incluyendo deep dives de arquitectura y dominio de network stubbing, lee Cypress Deep Dive. Si estás evaluando alternativas, Guía Completa de Playwright cubre el framework competidor de Microsoft.
FAQ
¿Cypress es gratis?
Sí, Cypress es open-source y completamente gratuito para desarrollo local y CI/CD. Cypress Cloud (anteriormente Dashboard) ofrece un nivel gratuito con grabaciones de test limitadas. Los niveles de pago proporcionan más ejecuciones paralelas, mayor retención de historial y analíticas avanzadas. La mayoría de los equipos empiezan gratis y actualizan según crecen los test suites.
¿Necesito saber JavaScript para usar Cypress?
Conocimientos básicos de JavaScript ayudan, pero no necesitas ser experto. La sintaxis de Cypress está diseñada para ser legible — cy.get('button').click() es comprensible incluso sin experiencia en JavaScript. Empieza con tests simples y aprende patrones de JavaScript (promises, arrow functions, destructuring) según los necesites. Muchos ingenieros QA aprenden JavaScript a través de Cypress.
¿Cypress puede testear cualquier sitio web?
Cypress testea aplicaciones web que corren en un navegador. Funciona con cualquier framework frontend: React, Vue, Angular, JavaScript vanilla, o páginas server-rendered. Sin embargo, Cypress no puede testear apps móviles nativas (iOS/Android), aplicaciones de escritorio, o apps Electron en builds de producción. Para testing móvil, necesitas herramientas como Appium. Cypress también tiene limitaciones con iframes cross-origin y múltiples pestañas de navegador.
¿Cuánto tiempo toma aprender Cypress?
Puedes escribir y ejecutar tu primer test dentro de una hora de instalación. Dominio básico — escribir tests confiables, usar selectores apropiadamente, manejar formularios — toma 2-4 semanas de práctica regular. Habilidades avanzadas como comandos personalizados, mocking de API e integración CI/CD toman más tiempo. La mayoría de los ingenieros se sienten confiados después de automatizar su primera feature real de principio a fin.
Ver También
- Cypress Deep Dive: Arquitectura y Network Stubbing - Técnicas avanzadas después de dominar lo básico
- Guía Completa de Playwright - Comparar con la alternativa de Microsoft
- Estrategia de Pirámide de Automatización - Dónde encaja Cypress en tu estrategia de testing
- GitHub Actions para Automatización QA - Patrones de integración CI/CD
- Testing Continuo en DevOps - Integrar tests automatizados en tu pipeline
