Que Es Cypress?
Cypress es un framework moderno de testing end-to-end construido especificamente para la web. A diferencia de Selenium, que se comunica con los navegadores a traves de un driver externo, Cypress se ejecuta directamente dentro del navegador. Esta diferencia arquitectonica es fundamental — significa que Cypress tiene acceso nativo a todo lo que sucede en la aplicacion: elementos del DOM, peticiones de red, timers, almacenamiento local e incluso los objetos JavaScript de la aplicacion.
Cuando ejecutas un test de Cypress, el framework carga tu aplicacion en un iframe y ejecuta los comandos de test junto a ella en la misma instancia del navegador. No hay salto de red entre el test runner y el navegador, no hay serializacion de comandos, y no hay espera de respuestas por HTTP. Los comandos se ejecutan a la velocidad del navegador mismo.
Cypress fue creado en 2014 y se ha convertido en una de las herramientas de testing mas populares en el ecosistema JavaScript. Esta disenado para hacer el testing lo mas rapido y confiable posible, con funcionalidades incorporadas que abordan los puntos de dolor mas comunes del testing de UI: inestabilidad, ejecucion lenta y depuracion dificil.
Arquitectura de Cypress
Comprender la arquitectura ayuda a explicar por que Cypress se comporta de manera diferente a otras herramientas.
Ejecucion Dentro del Navegador
Arquitectura Tradicional de Selenium:
Codigo de Test → HTTP → WebDriver → Navegador
Arquitectura de Cypress:
Codigo de Test → [se ejecuta dentro del mismo proceso del navegador] → Aplicacion
El test runner (proceso Node.js) inicia el navegador e inyecta el codigo de test de Cypress directamente en el. El codigo de test y el codigo de la aplicacion se ejecutan en el mismo event loop. Esto significa que Cypress puede:
- Acceder y manipular directamente el DOM
- Interceptar y modificar peticiones de red antes de que salgan del navegador
- Controlar el tiempo (avanzar timers, simular fechas)
- Acceder al estado de la aplicacion e incluso llamar funciones de la aplicacion directamente
La Cola de Comandos
Los comandos de Cypress no se ejecutan inmediatamente. Cuando escribes:
cy.visit('/login')
cy.get('#email').type('user@example.com')
cy.get('#password').type('secret123')
cy.get('button[type="submit"]').click()
Estos comandos se agregan a una cola y se ejecutan secuencialmente. Cada comando espera a que el anterior se complete antes de comenzar. Esto elimina la necesidad de esperas explicitas o sintaxis async/await.
Espera Automatica y Reintentos
Una de las funcionalidades mas importantes de Cypress es su mecanismo de reintento automatico. Cuando escribes:
cy.get('.success-message').should('contain', 'Welcome')
Cypress:
- Intenta encontrar el elemento
.success-message - Si no lo encuentra, reintenta despues de un corto intervalo
- Sigue reintentando hasta encontrar el elemento O que expire el timeout (4 segundos por defecto)
- Una vez encontrado, verifica si contiene “Welcome”
- Si el texto no coincide, reintenta toda la cadena
Esta logica de reintento incorporada elimina la necesidad de llamadas manuales a waitForElement que plagan los tests de Selenium.
Primeros Pasos
Instalacion
# Crear un nuevo proyecto
mkdir my-cypress-project && cd my-cypress-project
npm init -y
# Instalar Cypress
npm install cypress --save-dev
# Abrir Cypress (genera configuracion y estructura de carpetas por defecto)
npx cypress open
Estructura del Proyecto
Despues del primer lanzamiento, Cypress crea:
cypress/
e2e/ # Archivos de test (.cy.js)
fixtures/ # Datos de prueba (archivos JSON)
support/
commands.js # Comandos personalizados
e2e.js # Setup/teardown global
cypress.config.js # Configuracion
Configuracion
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
video: true,
screenshotOnRunFailure: true,
retries: {
runMode: 2, // Reintentos en CI
openMode: 0 // Sin reintentos en modo interactivo
}
}
})
Escribiendo Tu Primer Test
// cypress/e2e/login.cy.js
describe('Pagina de Login', () => {
beforeEach(() => {
cy.visit('/login')
})
it('deberia hacer login con credenciales validas', () => {
cy.get('[data-testid="email"]').type('admin@example.com')
cy.get('[data-testid="password"]').type('correctPassword')
cy.get('[data-testid="submit"]').click()
cy.url().should('include', '/dashboard')
cy.get('.welcome-header').should('contain', 'Welcome, Admin')
})
it('deberia mostrar error con credenciales invalidas', () => {
cy.get('[data-testid="email"]').type('admin@example.com')
cy.get('[data-testid="password"]').type('wrongPassword')
cy.get('[data-testid="submit"]').click()
cy.get('.error-message')
.should('be.visible')
.and('contain', 'Invalid email or password')
})
})
Comandos Principales
Consulta de Elementos
cy.get('.class-name') // Selector CSS
cy.get('[data-testid="submit"]') // Atributo data (recomendado)
cy.contains('Submit Order') // Buscar por contenido de texto
cy.get('form').find('input') // Encadenar selectores
cy.get('li').first() // Primer elemento que coincide
cy.get('li').eq(2) // Tercer elemento (indexado desde cero)
Interaccion con Elementos
cy.get('input').type('Hello World')
cy.get('input').clear().type('Nuevo texto')
cy.get('button').click()
cy.get('button').dblclick()
cy.get('button').rightclick()
cy.get('select').select('Opcion 2')
cy.get('input[type="checkbox"]').check()
cy.get('input[type="checkbox"]').uncheck()
cy.get('.item').trigger('mouseover')
Aserciones
Cypress usa aserciones de Chai con sintaxis .should():
cy.get('.title').should('have.text', 'Dashboard')
cy.get('.list').should('have.length', 5)
cy.get('.button').should('be.visible')
cy.get('.button').should('be.disabled')
cy.get('.input').should('have.value', 'Hello')
cy.get('.error').should('not.exist')
cy.url().should('include', '/products')
cy.get('.price').should('contain', '$29.99')
Intercepcion de Red con cy.intercept()
Una de las funcionalidades mas poderosas de Cypress es la capacidad de interceptar y controlar peticiones de red.
Sustitucion de Respuestas de API
it('deberia mostrar productos desde la API', () => {
cy.intercept('GET', '/api/products', {
statusCode: 200,
body: [
{ id: 1, name: 'Producto A', price: 29.99 },
{ id: 2, name: 'Producto B', price: 49.99 }
]
}).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts')
cy.get('.product-card').should('have.length', 2)
cy.get('.product-card').first().should('contain', 'Producto A')
})
Espera de Peticiones Reales
it('deberia enviar formulario y esperar respuesta del servidor', () => {
cy.intercept('POST', '/api/orders').as('createOrder')
cy.get('[data-testid="place-order"]').click()
cy.wait('@createOrder').then((interception) => {
expect(interception.response.statusCode).to.equal(201)
expect(interception.request.body).to.have.property('items')
})
})
Simulacion de Errores
it('deberia manejar errores del servidor correctamente', () => {
cy.intercept('GET', '/api/products', {
statusCode: 500,
body: { error: 'Internal Server Error' }
}).as('serverError')
cy.visit('/products')
cy.wait('@serverError')
cy.get('.error-state')
.should('be.visible')
.and('contain', 'Algo salio mal')
cy.get('.retry-button').should('be.visible')
})
Comandos Personalizados
Los comandos personalizados extienden la API de Cypress con funciones reutilizables.
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
cy.session([email, password], () => {
cy.visit('/login')
cy.get('[data-testid="email"]').type(email)
cy.get('[data-testid="password"]').type(password)
cy.get('[data-testid="submit"]').click()
cy.url().should('include', '/dashboard')
})
})
Cypress.Commands.add('createProduct', (product) => {
cy.request({
method: 'POST',
url: '/api/products',
body: product,
headers: { Authorization: `Bearer ${Cypress.env('API_TOKEN')}` }
})
})
Uso en tests:
describe('Gestion de Productos', () => {
beforeEach(() => {
cy.login('admin@example.com', 'password123')
})
it('deberia mostrar producto recien creado', () => {
cy.createProduct({ name: 'Nuevo Widget', price: 19.99 })
cy.visit('/products')
cy.contains('Nuevo Widget').should('be.visible')
})
})
Fixtures y Datos de Prueba
Los fixtures almacenan datos de prueba estaticos en archivos JSON.
// cypress/fixtures/user.json
{
"admin": {
"email": "admin@example.com",
"password": "admin123",
"role": "administrator"
},
"regular": {
"email": "user@example.com",
"password": "user123",
"role": "user"
}
}
// Usando fixtures en tests
it('deberia hacer login como admin', () => {
cy.fixture('user').then((users) => {
cy.get('#email').type(users.admin.email)
cy.get('#password').type(users.admin.password)
cy.get('#submit').click()
cy.contains('Administrator Dashboard').should('be.visible')
})
})
Variables de Entorno y Multiples Entornos
// cypress.config.js
module.exports = defineConfig({
e2e: {
env: {
apiUrl: 'http://localhost:8080',
coverage: false
}
}
})
# Sobrescribir via linea de comandos
npx cypress run --env apiUrl=https://staging.example.com
# O via cypress.env.json (no se sube al repositorio)
// Acceso en tests
cy.request(`${Cypress.env('apiUrl')}/api/health`)
Tecnicas de Depuracion
Viaje en el Tiempo
Cypress registra snapshots en cada paso. En el runner interactivo, al pasar el cursor sobre un comando se muestra el estado exacto del DOM en ese momento. Esta funcionalidad de “viaje en el tiempo” hace la depuracion sencilla — puedes ver exactamente como se veia la pagina cuando se ejecuto un comando.
cy.pause() y cy.debug()
it('ejemplo de depuracion', () => {
cy.visit('/checkout')
cy.get('.cart-items').should('have.length', 3)
cy.pause() // Pausa la ejecucion del test para inspeccion manual
cy.get('.checkout-button').click()
cy.debug() // Abre el debugger de DevTools en este punto
})
Ejercicios
Ejercicio 1: Suite de Tests de E-Commerce
Escribe una suite de tests de Cypress para una funcionalidad de busqueda de productos:
- Visita la pagina de productos
- Escribe una consulta de busqueda en el input de busqueda
- Intercepta la llamada a la API de busqueda y verifica los parametros de la peticion
- Verifica que la lista de productos filtrada muestra el numero correcto de resultados
- Verifica que cada producto visible contiene el termino de busqueda
Ejercicio 2: Biblioteca de Comandos Personalizados
Crea un conjunto de comandos personalizados para flujos comunes de autenticacion:
cy.login(email, password)— login via UI con cache de sesioncy.apiLogin(email, password)— login via API para setup mas rapidocy.logout()— limpiar sesion y verificar redireccion a la pagina de login- Escribe tests que usen estos comandos y verifica que funcionan correctamente
Ejercicio 3: Tests de Manejo de Errores
Usando cy.intercept(), escribe tests que verifiquen que la aplicacion maneja estos escenarios:
- 500 Internal Server Error — muestra pagina de error con boton de reintento
- 401 Unauthorized — redirige a la pagina de login
- Timeout de red — muestra mensaje de timeout
- Respuesta lenta (3+ segundos) — muestra spinner de carga antes de que aparezcan los datos