Saltar al contenido principal

Primer Test

Vamos a crear nuestros primeros tests E2E para el proyecto Taller-Testing-Security.

Estructura de un Test

Cypress usa la sintaxis de Mocha/Chai. Un test tiene esta estructura:

describe('Nombre del grupo de tests', () => {

beforeEach(() => {
// Se ejecuta ANTES de cada test
});

it('debe hacer algo específico', () => {
// Test individual
});

it('debe hacer otra cosa', () => {
// Otro test
});
});
BloquePropósito
describe()Agrupa tests relacionados
beforeEach()Setup antes de cada test
it()Define un test individual
afterEach()Cleanup después de cada test

Test 1: Landing Page

Creamos nuestro primer test para verificar que la landing page carga correctamente.

Crear el archivo

// cypress/e2e/landing.cy.ts

describe('Landing Page', () => {

beforeEach(() => {
cy.visit('/');
});

it('debe cargar la página correctamente', () => {
// Verificar que estamos en la URL correcta
cy.url().should('eq', Cypress.config().baseUrl + '/');

// Verificar que existe un título
cy.get('h1').should('be.visible');
});

it('debe mostrar navegación', () => {
// Verificar links de navegación
cy.contains(/home|inicio/i).should('be.visible');
cy.contains(/dashboard/i).should('be.visible');
});
});

Ejecutar el test

# Modo interactivo
npm run test:e2e:open

# Modo headless
npm run test:e2e

Test 2: Formulario de Login

El login es una funcionalidad crítica. Vamos a testear varios escenarios.

Archivo de test

// cypress/e2e/auth/login.cy.ts

describe('Login Page', () => {

beforeEach(() => {
cy.visit('/login');
});

describe('Elementos del formulario', () => {

it('debe mostrar los campos del formulario', () => {
cy.get('input[name="email"]').should('be.visible');
cy.get('input[name="password"]').should('be.visible');
cy.get('input[type="submit"]').should('be.visible');
});
});

describe('Validación', () => {

it('debe mostrar error con campos vacíos', () => {
// Submit sin llenar
cy.get('input[type="submit"]').click();

// Debe mostrar mensaje de error
cy.contains(/username|password|email/i).should('be.visible');
});

it('debe mostrar error solo con email', () => {
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[type="submit"]').click();

// Error porque falta password
cy.contains(/password|contraseña/i).should('be.visible');
});
});

describe('Login con API mockeada', () => {

it('debe hacer login exitoso', () => {
// Mockear respuesta de la API
cy.mockLoginApi({ success: true });

// Llenar formulario
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[name="password"]').type('password123');
cy.get('input[type="submit"]').click();

// Esperar respuesta
cy.wait('@loginSuccess');

// Verificar redirección
cy.url().should('include', '/admin');
});

it('debe mostrar error con credenciales inválidas', () => {
// Mockear error
cy.mockLoginApi({ success: false });

cy.get('input[name="email"]').type('wrong@example.com');
cy.get('input[name="password"]').type('wrongpass');
cy.get('input[type="submit"]').click();

cy.wait('@loginError');

// Seguimos en login
cy.url().should('include', '/login');

// Muestra error
cy.contains(/invalid|error/i).should('be.visible');
});
});
});
Custom Command

cy.mockLoginApi() es un custom command que definimos en cypress/support/commands.ts. Simplifica el mockeo de la API de login.


Test 3: Dashboard

El dashboard carga datos de dos APIs: aboutme y projects.

Test de Dashboard

// cypress/e2e/dashboard/dashboard.cy.ts

describe('Dashboard Page', () => {

describe('Carga de datos', () => {

beforeEach(() => {
// Mockear las APIs del dashboard
cy.mockDashboardApi();
});

it('debe cargar y mostrar el perfil', () => {
cy.visit('/dashboard');

// Esperar a que las APIs respondan
cy.wait(['@getAboutMe', '@getProjects']);

// Verificar que se muestra la información
cy.contains('Test User').should('be.visible');
});

it('debe mostrar los proyectos', () => {
cy.visit('/dashboard');
cy.wait(['@getAboutMe', '@getProjects']);

// Verificar que hay al menos un proyecto
cy.contains('Test Project').should('be.visible');
});
});

describe('Estados de carga', () => {

it('debe mostrar loading mientras carga', () => {
// Mockear con delay
cy.mockDashboardApi({ delay: 1000 });

cy.visit('/dashboard');

// Verificar que aparece el loader
cy.contains(/loading|cargando/i).should('be.visible');

// Esperar a que termine
cy.wait(['@getAboutMe', '@getProjects']);

// Loader desaparece
cy.contains(/loading|cargando/i).should('not.exist');
});
});

describe('Manejo de errores', () => {

it('debe mostrar error cuando la API falla', () => {
// Mockear error
cy.mockDashboardApi({ error: true });

cy.visit('/dashboard');

// Verificar mensaje de error
cy.contains(/error/i).should('be.visible');
});
});
});

Test 4: Flujo Completo de Usuario

Este test simula el journey completo de un usuario.

Test de User Journey

// cypress/e2e/flows/user-journey.cy.ts

describe('Flujo de Usuario', () => {

describe('Usuario anónimo', () => {

it('debe navegar de landing a dashboard', () => {
// Mockear APIs
cy.intercept('GET', '**/v1/aboutme/', { fixture: 'aboutme.json' }).as('getAboutMe');
cy.intercept('GET', '**/v1/projects/', { fixture: 'projects.json' }).as('getProjects');

// Empezar en landing
cy.visit('/');

// Click en Dashboard
cy.contains(/dashboard/i).click();

// Verificar navegación
cy.url().should('include', '/dashboard');

// Esperar datos
cy.wait(['@getAboutMe', '@getProjects']);

// Verificar contenido
cy.contains('Lucas Fernandez').should('be.visible');
});
});

describe('Usuario autenticado', () => {

beforeEach(() => {
cy.mockLoginApi({ success: true });
cy.mockDashboardApi();
});

it('debe hacer login y acceder a admin', () => {
// Ir a login
cy.visit('/login');

// Hacer login
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[name="password"]').type('password123');
cy.get('input[type="submit"]').click();

// Esperar login
cy.wait('@loginSuccess');

// Verificar que estamos en admin
cy.url().should('include', '/admin');
});
});
});

Usar Fixtures

Los fixtures son datos mock en formato JSON que puedes reutilizar.

Archivo de fixture

// cypress/fixtures/users.json
{
"validUser": {
"email": "test@example.com",
"password": "test123"
},
"invalidUser": {
"email": "wrong@example.com",
"password": "wrongpass"
}
}

Usar en tests

it('debe usar datos de fixture', () => {
cy.mockLoginApi({ success: true });

// Cargar fixture
cy.fixture('users').then((users) => {
cy.get('input[name="email"]').type(users.validUser.email);
cy.get('input[name="password"]').type(users.validUser.password);
cy.get('input[type="submit"]').click();
});

cy.wait('@loginSuccess');
});

Debugging

Pausar ejecución

it('debug test', () => {
cy.visit('/');
cy.pause(); // Pausa aquí - puedes continuar paso a paso
cy.get('h1').should('be.visible');
});

Inspeccionar elemento

it('debug element', () => {
cy.get('[data-testid="user"]').debug(); // Abre DevTools
});

Logs personalizados

it('with logs', () => {
cy.log('🚀 Iniciando test');
cy.visit('/');
cy.log('✅ Página cargada');
});

Screenshots

it('take screenshots', () => {
cy.visit('/');
cy.screenshot('homepage-inicial');

cy.get('button').click();
cy.screenshot('despues-de-click');
});

Errores Comunes

Element not found

// ❌ El elemento no existe o no ha cargado
cy.get('[data-testid="user"]').click();

// ✅ Aumentar timeout
cy.get('[data-testid="user"]', { timeout: 10000 }).click();

// ✅ Esperar a que algo desaparezca primero
cy.get('[data-testid="loading"]').should('not.exist');
cy.get('[data-testid="user"]').click();

Element not visible

// ❌ Elemento existe pero está oculto
cy.get('button').click();

// ✅ Forzar click (usar con cuidado)
cy.get('button').click({ force: true });

No uses waits fijos

// ❌ Wait arbitrario
cy.wait(5000);

// ✅ Wait inteligente - esperar a que elemento exista
cy.get('[data-testid="result"]', { timeout: 10000 }).should('exist');

// ✅ Wait a request
cy.wait('@apiRequest');

Resumen de Comandos Básicos

ComandoDescripción
cy.visit('/')Navegar a URL
cy.get('selector')Obtener elemento
cy.contains('texto')Buscar por texto
cy.click()Click en elemento
cy.type('texto')Escribir texto
cy.should('be.visible')Verificar estado
cy.wait('@alias')Esperar request
cy.url()Obtener URL actual

Próximos Pasos

Ahora que sabes escribir tests básicos:

  1. Selectores - Estrategias para encontrar elementos
  2. Formularios - Testing de inputs y validaciones
  3. Intercepción - Mockear APIs