TL;DR

  • Cypress corre dentro del navegador, haciendo los tests rápidos y confiables sin WebDriver
  • Instala con npm install cypress --save-dev, luego ejecuta npx cypress open
  • Usa atributos data-* para selectores — sobreviven cambios de UI

Ideal 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ísticaCypressSelenium/WebDriver
ArquitecturaCorre dentro del navegadorProceso de driver externo
Tiempo de setupMinutosHoras (drivers, configs)
DebuggingTime-travel, snapshotsScreenshots, logs
VelocidadRápido (sin saltos de red)Más lento (comandos HTTP)
FlakinessBajo (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 relacionados
  • it() define un caso de test individual
  • cy.visit() navega a una URL
  • cy.contains() encuentra elemento por contenido de texto
  • cy.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ónDescripciónDefault
baseUrlSe antepone a URLs de cy.visit()ninguno
viewportWidth/HeightDimensiones del navegador1000 × 660
defaultCommandTimeoutTiempo de reintento de comandos4000ms
videoGrabar videos de ejecucionestrue
retriesAuto-reintentar tests fallidos0

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-test para selectores
  • Esperar llamadas API en vez de timeouts arbitrarios
  • Mantener tests independientes (cada uno puede correr solo)
  • Usar beforeEach para 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

Recursos Oficiales