Saltar a contenido

Cipress Cheatsheet

< > > > > > > > > > "Clase de inscripción" Cypress es una herramienta de prueba frontal de próxima generación construida para la web moderna. Se dirige a los desarrolladores de puntos de dolor clave y los ingenieros de QA se enfrentan al probar aplicaciones modernas. Cypress le permite escribir todo tipo de pruebas: Pruebas de extremo a extremo, Pruebas de integración, Pruebas de unidad. ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Instalación básica

# Install Cypress via npm
npm install --save-dev cypress

# Install Cypress via yarn
yarn add --dev cypress

# Open Cypress for the first time
npx cypress open

# Run Cypress in headless mode
npx cypress run

Configuración de proyectos

# Initialize new project with Cypress
mkdir my-cypress-project
cd my-cypress-project
npm init -y
npm install --save-dev cypress

# Open Cypress (creates folder structure)
npx cypress open

Estructura de la carpeta

cypress/
├── e2e/                    # End-to-end test files
│   └── spec.cy.js
├── fixtures/               # Test data
│   └── example.json
├── support/               # Support files
│   ├── commands.js        # Custom commands
│   └── e2e.js            # Global configuration
└── downloads/             # Downloaded files during tests

Paquete.json Scripts

{
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run",
    "cypress:run:chrome": "cypress run --browser chrome",
    "cypress:run:firefox": "cypress run --browser firefox",
    "cypress:run:edge": "cypress run --browser edge",
    "cypress:run:headed": "cypress run --headed",
    "cypress:run:record": "cypress run --record --key=your-record-key"
  }
}

Comienzo

Primera prueba

// cypress/e2e/first-test.cy.js
describe('My First Test', () => {
  it('Visits the Kitchen Sink', () => {
    cy.visit('https://example.cypress.io')
    cy.contains('type').click()
    cy.url().should('include', '/commands/actions')
    cy.get('.action-email')
      .type('fake@email.com')
      .should('have.value', 'fake@email.com')
  })
})

Estructura de prueba básica

describe('Test Suite Name', () => {
  beforeEach(() => {
    // Runs before each test
    cy.visit('/login')
  })

  it('should do something', () => {
    // Test implementation
    cy.get('[data-cy=username]').type('user@example.com')
    cy.get('[data-cy=password]').type('password123')
    cy.get('[data-cy=submit]').click()
    cy.url().should('include', '/dashboard')
  })

  it('should do something else', () => {
    // Another test
  })
})

Test Hooks

describe('Hooks Example', () => {
  before(() => {
    // Runs once before all tests in the block
    cy.task('seedDatabase')
  })

  after(() => {
    // Runs once after all tests in the block
    cy.task('clearDatabase')
  })

  beforeEach(() => {
    // Runs before each test in the block
    cy.login('user@example.com', 'password123')
  })

  afterEach(() => {
    // Runs after each test in the block
    cy.clearCookies()
    cy.clearLocalStorage()
  })

  it('should test something', () => {
    // Test implementation
  })
})

Comandos básicos

// Visit a URL
cy.visit('https://example.com')
cy.visit('/relative-path')
cy.visit('/', { timeout: 30000 })

// Navigate browser history
cy.go('back')
cy.go('forward')
cy.go(-1) // Go back one page
cy.go(1)  // Go forward one page

// Reload page
cy.reload()
cy.reload(true) // Force reload

Element Interaction

// Click elements
cy.get('button').click()
cy.get('button').click({ force: true }) // Force click
cy.get('button').dblclick() // Double click
cy.get('button').rightclick() // Right click

// Type text
cy.get('input').type('Hello World')
cy.get('input').type('user@example.com{enter}')
cy.get('input').type('{selectall}New Text')
cy.get('input').clear().type('New Text')

// Select options
cy.get('select').select('Option 1')
cy.get('select').select(['Option 1', 'Option 2']) // Multiple
cy.get('select').select('value', { force: true })

// Check/uncheck
cy.get('input[type="checkbox"]').check()
cy.get('input[type="checkbox"]').uncheck()
cy.get('input[type="radio"]').check()

// File upload
cy.get('input[type="file"]').selectFile('path/to/file.pdf')
cy.get('input[type="file"]').selectFile(['file1.jpg', 'file2.jpg'])

Element Queries

// Get elements
cy.get('.class-name')
cy.get('#id')
cy.get('[data-cy="element"]')
cy.get('button').first()
cy.get('button').last()
cy.get('button').eq(2) // Third button (0-indexed)

// Find within elements
cy.get('.parent').find('.child')
cy.get('.parent').within(() => {
  cy.get('.child').click()
})

// Filter elements
cy.get('button').filter('.active')
cy.get('button').not('.disabled')

// Contains text
cy.contains('Submit')
cy.contains('button', 'Submit')
cy.get('button').contains('Submit')

Traversal

// Parent/child relationships
cy.get('.child').parent()
cy.get('.element').parents('.ancestor')
cy.get('.parent').children()
cy.get('.parent').children('.specific-child')

// Siblings
cy.get('.element').siblings()
cy.get('.element').next()
cy.get('.element').prev()
cy.get('.element').nextAll()
cy.get('.element').prevAll()

// Closest
cy.get('.element').closest('.container')

Esperando

// Wait for element
cy.get('.loading').should('not.exist')
cy.get('.content').should('be.visible')

// Wait for specific time (not recommended)
cy.wait(1000)

// Wait for network requests
cy.intercept('GET', '/api/users').as('getUsers')
cy.wait('@getUsers')

// Wait for multiple requests
cy.wait(['@getUsers', '@getPosts'])

Selectores

CSS Selectors

// Basic selectors
cy.get('button')           // Tag
cy.get('.btn')            // Class
cy.get('#submit')         // ID
cy.get('[type="submit"]') // Attribute

// Combinators
cy.get('form button')     // Descendant
cy.get('form > button')   // Direct child
cy.get('label + input')   // Adjacent sibling
cy.get('h2 ~ p')         // General sibling

// Pseudo-selectors
cy.get('button:first')
cy.get('button:last')
cy.get('button:nth-child(2)')
cy.get('input:checked')
cy.get('input:disabled')
cy.get('a:contains("Click me")')

Atributos de datos (recomendados)

// HTML
// <button data-cy="submit-btn">Submit</button>
// <input data-testid="username" />
// <div data-test="user-profile">...</div>

// Cypress tests
cy.get('[data-cy="submit-btn"]')
cy.get('[data-testid="username"]')
cy.get('[data-test="user-profile"]')

// Custom command for data-cy
Cypress.Commands.add('dataCy', (value) => {
  return cy.get(`[data-cy=${value}]`)
})

// Usage
cy.dataCy('submit-btn').click()

XPath (con plugin)

# Install cypress-xpath plugin
npm install --save-dev cypress-xpath
// cypress/support/e2e.js
require('cypress-xpath')

// Usage in tests
cy.xpath('//button[contains(text(), "Submit")]')
cy.xpath('//input[@placeholder="Username"]')
cy.xpath('//div[@class="container"]//button[1]')

Selectores complejos

// Multiple attributes
cy.get('input[type="email"][required]')

// Partial attribute matching
cy.get('[class*="btn"]')      // Contains
cy.get('[class^="btn"]')      // Starts with
cy.get('[class$="primary"]')  // Ends with

// Multiple selectors
cy.get('button, input[type="submit"]')

// Chaining selectors
cy.get('.form-group')
  .find('input')
  .filter('[required]')
  .first()

Asserciones

En caso de sumas

// Existence
cy.get('button').should('exist')
cy.get('.loading').should('not.exist')

// Visibility
cy.get('button').should('be.visible')
cy.get('.modal').should('not.be.visible')

// Text content
cy.get('h1').should('contain', 'Welcome')
cy.get('h1').should('have.text', 'Welcome to our site')
cy.get('p').should('include.text', 'partial text')

// Attributes
cy.get('input').should('have.attr', 'placeholder', 'Enter email')
cy.get('button').should('have.class', 'btn-primary')
cy.get('input').should('have.value', 'test@example.com')

// CSS properties
cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)')
cy.get('div').should('have.css', 'display', 'none')

// Length
cy.get('li').should('have.length', 5)
cy.get('li').should('have.length.greaterThan', 3)
cy.get('li').should('have.length.lessThan', 10)

Supresiones esperadas

// Using expect (Chai)
cy.get('input').then(($input) => {
  expect($input).to.have.value('expected value')
  expect($input[0].validity.valid).to.be.true
})

// Multiple assertions
cy.get('button').then(($btn) => {
  expect($btn).to.have.class('btn')
  expect($btn).to.contain('Submit')
  expect($btn).to.be.visible
})

// Custom assertions
cy.get('.user-list').then(($list) => {
  const users = $list.find('.user-item')
  expect(users).to.have.length.above(0)
  expect(users.first()).to.contain('John Doe')
})

Aserciones URL

// URL should include
cy.url().should('include', '/dashboard')
cy.url().should('eq', 'https://example.com/dashboard')

// URL should match pattern
cy.url().should('match', /\/users\/\d+/)

// Location assertions
cy.location('pathname').should('eq', '/dashboard')
cy.location('search').should('eq', '?tab=profile')
cy.location('hash').should('eq', '#section1')

Aserciones aduaneras

// Add custom assertion
chai.Assertion.addMethod('beValidEmail', function() {
  const obj = this._obj
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

  this.assert(
    emailRegex.test(obj),
    'expected #{this} to be a valid email',
    'expected #{this} not to be a valid email',
    true
  )
})

// Usage
cy.get('input[type="email"]')
  .invoke('val')
  .should('beValidEmail')

Pruebas de red

Solicitudes de interceptación

// Basic intercept
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')

// Intercept with response modification
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')

// Intercept with dynamic response
cy.intercept('GET', '/api/users', (req) => {
  req.reply({
    statusCode: 200,
    body: {
      users: [
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Smith' }
      ]
    }
  })
}).as('getUsers')

// Intercept POST requests
cy.intercept('POST', '/api/users', {
  statusCode: 201,
  body: { id: 3, name: 'New User' }
}).as('createUser')

Solicitud de admisión

// Wait and assert request
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers').then((interception) => {
  expect(interception.response.statusCode).to.eq(200)
  expect(interception.response.body).to.have.property('users')
})

// Assert request was made
cy.intercept('POST', '/api/users').as('createUser')
cy.get('[data-cy="create-user"]').click()
cy.wait('@createUser').its('request.body').should('deep.include', {
  name: 'John Doe',
  email: 'john@example.com'
})

// Assert request headers
cy.wait('@getUsers').its('request.headers').should('have.property', 'authorization')

Condiciones de red

// Simulate slow network
cy.intercept('GET', '/api/users', (req) => {
  req.reply((res) => {
    res.delay(2000) // 2 second delay
    res.send({ fixture: 'users.json' })
  })
}).as('getUsers')

// Simulate network failure
cy.intercept('GET', '/api/users', { forceNetworkError: true }).as('getUsersError')

// Simulate server error
cy.intercept('GET', '/api/users', {
  statusCode: 500,
  body: { error: 'Internal Server Error' }
}).as('getUsersError')

Múltiples interceptaciones

// Multiple endpoints
cy.intercept('GET', '/api/users').as('getUsers')
cy.intercept('GET', '/api/posts').as('getPosts')
cy.intercept('GET', '/api/comments').as('getComments')

cy.visit('/dashboard')

// Wait for all requests
cy.wait(['@getUsers', '@getPosts', '@getComments'])

// Or wait individually
cy.wait('@getUsers')
cy.wait('@getPosts')
cy.wait('@getComments')

Operaciones de archivo

Cargo de archivos

// Upload single file
cy.get('input[type="file"]').selectFile('cypress/fixtures/example.pdf')

// Upload multiple files
cy.get('input[type="file"]').selectFile([
  'cypress/fixtures/file1.jpg',
  'cypress/fixtures/file2.jpg'
])

// Upload with drag and drop
cy.get('.dropzone').selectFile('cypress/fixtures/example.pdf', {
  action: 'drag-drop'
})

// Upload from fixtures
cy.fixture('example.pdf', 'base64').then(fileContent => {
  cy.get('input[type="file"]').selectFile({
    contents: Cypress.Buffer.from(fileContent, 'base64'),
    fileName: 'example.pdf',
    mimeType: 'application/pdf'
  })
})

Archivo Descargar

// Download file and verify
cy.get('[data-cy="download-btn"]').click()
cy.readFile('cypress/downloads/report.pdf').should('exist')

// Verify downloaded file content
cy.get('[data-cy="download-csv"]').click()
cy.readFile('cypress/downloads/data.csv')
  .should('contain', 'Name,Email,Age')
  .and('contain', 'John Doe,john@example.com,30')

// Download with custom filename
cy.get('[data-cy="download-btn"]').click()
cy.task('downloadFile', {
  url: 'https://example.com/file.pdf',
  directory: 'cypress/downloads',
  filename: 'custom-name.pdf'
})

Trabajando con Arreglos

// Load fixture data
cy.fixture('users.json').then((users) => {
  cy.intercept('GET', '/api/users', { body: users }).as('getUsers')
})

// Use fixture in test
cy.fixture('user-data.json').as('userData')
cy.get('@userData').then((data) => {
  cy.get('[data-cy="name"]').type(data.name)
  cy.get('[data-cy="email"]').type(data.email)
})

// Fixture with alias
cy.fixture('config.json').as('config')
cy.get('@config').its('apiUrl').should('eq', 'https://api.example.com')

Archivos de lectura / escritura

// Read file
cy.readFile('cypress/fixtures/data.json').then((data) => {
  expect(data).to.have.property('users')
})

// Write file
cy.writeFile('cypress/fixtures/output.json', {
  timestamp: Date.now(),
  testResults: 'passed'
})

// Append to file
cy.writeFile('cypress/fixtures/log.txt', 'New log entry\n', { flag: 'a+' })

Comandos Personalizados

Comandos Personales Básicos

// cypress/support/commands.js

// Simple command
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login')
  cy.get('[data-cy="email"]').type(email)
  cy.get('[data-cy="password"]').type(password)
  cy.get('[data-cy="submit"]').click()
})

// Command with options
Cypress.Commands.add('login', (email, password, options = {}) => {
  const { rememberMe = false } = options

  cy.visit('/login')
  cy.get('[data-cy="email"]').type(email)
  cy.get('[data-cy="password"]').type(password)

  if (rememberMe) {
    cy.get('[data-cy="remember-me"]').check()
  }

  cy.get('[data-cy="submit"]').click()
})

// Usage
cy.login('user@example.com', 'password123')
cy.login('user@example.com', 'password123', { rememberMe: true })

Comandos Aduaneros avanzados

// Command that returns value
Cypress.Commands.add('getLocalStorage', (key) => {
  return cy.window().then((win) => {
    return win.localStorage.getItem(key)
  })
})

// Command with chaining
Cypress.Commands.add('dataCy', (value) => {
  return cy.get(`[data-cy=${value}]`)
})

// Usage with chaining
cy.dataCy('submit-btn').click()
cy.dataCy('username').type('john@example.com')

// Command with retry logic
Cypress.Commands.add('waitForElement', (selector, timeout = 10000) => {
  cy.get(selector, { timeout }).should('be.visible')
})

// API command
Cypress.Commands.add('apiLogin', (email, password) => {
  cy.request({
    method: 'POST',
    url: '/api/auth/login',
    body: { email, password }
  }).then((response) => {
    window.localStorage.setItem('authToken', response.body.token)
  })
})

Sobreescribir Comandos

// Overwrite existing command
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
  const domain = Cypress.config('baseUrl')

  if (domain) {
    const combinedUrl = `${domain}${url}`
    return originalFn(combinedUrl, options)
  }

  return originalFn(url, options)
})

// Overwrite type command to clear first
Cypress.Commands.overwrite('type', (originalFn, element, text, options) => {
  if (options && options.clearFirst !== false) {
    element.clear()
  }

  return originalFn(element, text, options)
})

Soporte TipoScript

// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string, options?: { rememberMe?: boolean }): Chainable<void>
      dataCy(value: string): Chainable<JQuery<HTMLElement>>
      apiLogin(email: string, password: string): Chainable<void>
    }
  }
}

Cypress.Commands.add('login', (email: string, password: string, options = {}) => {
  // Implementation
})

Configuración

Configuración Cypress

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    // Base URL for cy.visit() and cy.request()
    baseUrl: 'http://localhost:3000',

    // Viewport settings
    viewportWidth: 1280,
    viewportHeight: 720,

    // Timeouts
    defaultCommandTimeout: 10000,
    requestTimeout: 10000,
    responseTimeout: 30000,
    pageLoadTimeout: 30000,

    // Test files
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    excludeSpecPattern: ['**/examples/*', '**/temp/*'],

    // Support file
    supportFile: 'cypress/support/e2e.js',

    // Fixtures
    fixturesFolder: 'cypress/fixtures',

    // Screenshots and videos
    screenshotsFolder: 'cypress/screenshots',
    videosFolder: 'cypress/videos',
    video: true,
    screenshotOnRunFailure: true,

    // Browser settings
    chromeWebSecurity: false,

    // Setup function
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },

  // Component testing
  component: {
    devServer: {
      framework: 'react',
      bundler: 'webpack',
    },
  },
})

Medio ambiente

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    env: {
      apiUrl: 'https://api.example.com',
      username: 'testuser',
      password: 'testpass'
    }
  }
})

// cypress.env.json
{
  "apiUrl": "https://api.example.com",
  "username": "testuser",
  "password": "testpass"
}

// Usage in tests
cy.visit(Cypress.env('apiUrl'))
const username = Cypress.env('username')

Múltiples entornos

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
|  |  |  | const environment = config.env.environment |  | 'development' |  |  |  |

      const environments = {
        development: {
          baseUrl: 'http://localhost:3000',
          apiUrl: 'http://localhost:3001'
        },
        staging: {
          baseUrl: 'https://staging.example.com',
          apiUrl: 'https://api-staging.example.com'
        },
        production: {
          baseUrl: 'https://example.com',
          apiUrl: 'https://api.example.com'
        }
      }

      config.baseUrl = environments[environment].baseUrl
      config.env.apiUrl = environments[environment].apiUrl

      return config
    }
  }
})

// Run with specific environment
// npx cypress run --env environment=staging

Configuración del navegador

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Browser launch options
    setupNodeEvents(on, config) {
      on('before:browser:launch', (browser = {}, launchOptions) => {
        if (browser.name === 'chrome') {
          launchOptions.args.push('--disable-dev-shm-usage')
          launchOptions.args.push('--no-sandbox')
          launchOptions.args.push('--disable-gpu')
        }

        if (browser.name === 'firefox') {
          launchOptions.preferences['media.navigator.permission.disabled'] = true
        }

        return launchOptions
      })
    }
  }
})

Page Objects

Objeto de página básica

// cypress/support/pages/LoginPage.js
class LoginPage {
  visit() {
    cy.visit('/login')
    return this
  }

  fillEmail(email) {
    cy.get('[data-cy="email"]').type(email)
    return this
  }

  fillPassword(password) {
    cy.get('[data-cy="password"]').type(password)
    return this
  }

  submit() {
    cy.get('[data-cy="submit"]').click()
    return this
  }

  login(email, password) {
    this.fillEmail(email)
    this.fillPassword(password)
    this.submit()
    return this
  }

  // Getters for elements
  get emailInput() {
    return cy.get('[data-cy="email"]')
  }

  get passwordInput() {
    return cy.get('[data-cy="password"]')
  }

  get submitButton() {
    return cy.get('[data-cy="submit"]')
  }

  get errorMessage() {
    return cy.get('[data-cy="error-message"]')
  }
}

export default LoginPage

Usando objetos de página

// cypress/e2e/login.cy.js
import LoginPage from '../support/pages/LoginPage'

describe('Login Tests', () => {
  const loginPage = new LoginPage()

  beforeEach(() => {
    loginPage.visit()
  })

  it('should login successfully', () => {
    loginPage
      .login('user@example.com', 'password123')

    cy.url().should('include', '/dashboard')
  })

  it('should show error for invalid credentials', () => {
    loginPage
      .login('invalid@email.com', 'wrongpassword')

    loginPage.errorMessage
      .should('be.visible')
      .and('contain', 'Invalid credentials')
  })
})

Objeto de página avanzada

// cypress/support/pages/DashboardPage.js
class DashboardPage {
  constructor() {
    this.selectors = {
      userMenu: '[data-cy="user-menu"]',
      userProfile: '[data-cy="user-profile"]',
      notifications: '[data-cy="notifications"]',
      searchInput: '[data-cy="search"]',
      sidebar: '[data-cy="sidebar"]',
      mainContent: '[data-cy="main-content"]'
    }
  }

  visit() {
    cy.visit('/dashboard')
    this.waitForLoad()
    return this
  }

  waitForLoad() {
    cy.get(this.selectors.mainContent).should('be.visible')
    cy.get('.loading-spinner').should('not.exist')
    return this
  }

  openUserMenu() {
    cy.get(this.selectors.userMenu).click()
    return this
  }

  search(query) {
    cy.get(this.selectors.searchInput)
      .clear()
      .type(`${query}{enter}`)
    return this
  }

  // Navigation methods
  navigateToProfile() {
    this.openUserMenu()
    cy.get(this.selectors.userProfile).click()
    return this
  }

  // Assertion methods
  shouldShowWelcomeMessage(username) {
    cy.contains(`Welcome, ${username}`).should('be.visible')
    return this
  }

  shouldHaveNotificationCount(count) {
    cy.get(this.selectors.notifications)
      .find('.badge')
      .should('contain', count)
    return this
  }
}

export default DashboardPage

Page Object with API Integration

// cypress/support/pages/UsersPage.js
class UsersPage {
  visit() {
    cy.intercept('GET', '/api/users').as('getUsers')
    cy.visit('/users')
    cy.wait('@getUsers')
    return this
  }

  createUser(userData) {
    cy.intercept('POST', '/api/users').as('createUser')

    cy.get('[data-cy="create-user-btn"]').click()
    cy.get('[data-cy="name"]').type(userData.name)
    cy.get('[data-cy="email"]').type(userData.email)
    cy.get('[data-cy="save"]').click()

    cy.wait('@createUser')
    return this
  }

  deleteUser(userId) {
    cy.intercept('DELETE', `/api/users/${userId}`).as('deleteUser')

    cy.get(`[data-cy="user-${userId}"]`)
      .find('[data-cy="delete-btn"]')
      .click()

    cy.get('[data-cy="confirm-delete"]').click()
    cy.wait('@deleteUser')
    return this
  }

  shouldShowUser(userData) {
    cy.get('[data-cy="users-table"]')
      .should('contain', userData.name)
      .and('contain', userData.email)
    return this
  }
}

export default UsersPage

API Testing

Pruebas básicas de API

describe('API Tests', () => {
  it('should get users', () => {
    cy.request('GET', '/api/users')
      .then((response) => {
        expect(response.status).to.eq(200)
        expect(response.body).to.have.property('users')
        expect(response.body.users).to.be.an('array')
      })
  })

  it('should create user', () => {
    const newUser = {
      name: 'John Doe',
      email: 'john@example.com'
    }

    cy.request('POST', '/api/users', newUser)
      .then((response) => {
        expect(response.status).to.eq(201)
        expect(response.body).to.have.property('id')
        expect(response.body.name).to.eq(newUser.name)
        expect(response.body.email).to.eq(newUser.email)
      })
  })
})

Pruebas de API con autenticación

describe('Authenticated API Tests', () => {
  let authToken

  before(() => {
    // Login and get token
    cy.request({
      method: 'POST',
      url: '/api/auth/login',
      body: {
        email: 'admin@example.com',
        password: 'password123'
      }
    }).then((response) => {
      authToken = response.body.token
    })
  })

  it('should get protected resource', () => {
    cy.request({
      method: 'GET',
      url: '/api/admin/users',
      headers: {
        'Authorization': `Bearer ${authToken}`
      }
    }).then((response) => {
      expect(response.status).to.eq(200)
      expect(response.body).to.have.property('users')
    })
  })

  it('should fail without token', () => {
    cy.request({
      method: 'GET',
      url: '/api/admin/users',
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(401)
    })
  })
})
```_

### Ayudadores de prueba de API
```javascript
// cypress/support/api-helpers.js
Cypress.Commands.add('apiLogin', (email, password) => {
  return cy.request({
    method: 'POST',
    url: '/api/auth/login',
    body: { email, password }
  }).then((response) => {
    window.localStorage.setItem('authToken', response.body.token)
    return response.body.token
  })
})

Cypress.Commands.add('apiRequest', (method, url, body = null) => {
  const token = window.localStorage.getItem('authToken')

  return cy.request({
    method,
    url,
    body,
    headers: token ? { 'Authorization': `Bearer ${token}` } : {}
  })
})

// Usage
cy.apiLogin('admin@example.com', 'password123')
cy.apiRequest('GET', '/api/users')
cy.apiRequest('POST', '/api/users', { name: 'John', email: 'john@example.com' })

Validación de Schema

// Install ajv for JSON schema validation
// npm install --save-dev ajv

// cypress/support/schema-validation.js
import Ajv from 'ajv'

const ajv = new Ajv()

Cypress.Commands.add('validateSchema', (data, schema) => {
  const validate = ajv.compile(schema)
  const valid = validate(data)

  if (!valid) {
    throw new Error(`Schema validation failed: ${JSON.stringify(validate.errors)}`)
  }
})

// Usage
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'number' },
    name: { type: 'string' },
    email: { type: 'string', format: 'email' }
  },
  required: ['id', 'name', 'email']
}

cy.request('GET', '/api/users/1')
  .then((response) => {
    cy.validateSchema(response.body, userSchema)
  })

Pruebas visuales

Pruebas de pantalla

describe('Visual Tests', () => {
  it('should match homepage screenshot', () => {
    cy.visit('/')
    cy.screenshot('homepage')
  })

  it('should match element screenshot', () => {
    cy.visit('/dashboard')
    cy.get('[data-cy="user-profile"]').screenshot('user-profile')
  })

  it('should match full page screenshot', () => {
    cy.visit('/products')
    cy.screenshot('products-page', { capture: 'fullPage' })
  })
})

Regreso Visual con Percy

# Install Percy
npm install --save-dev @percy/cypress
// cypress/support/e2e.js
import '@percy/cypress'

// Usage in tests
describe('Visual Regression Tests', () => {
  it('should match visual snapshot', () => {
    cy.visit('/')
    cy.percySnapshot('Homepage')
  })

  it('should match responsive snapshots', () => {
    cy.visit('/dashboard')
    cy.percySnapshot('Dashboard - Desktop', {
      widths: [1280]
    })
    cy.percySnapshot('Dashboard - Mobile', {
      widths: [375]
    })
  })
})
```_

### Comandos Visuales Personalizados
```javascript
// cypress/support/commands.js
Cypress.Commands.add('compareSnapshot', (name, options = {}) => {
  const { threshold = 0.1, clip } = options

  cy.screenshot(name, { clip })

  // Custom comparison logic would go here
  // This is a simplified example
  cy.task('compareImages', {
    actualImage: `cypress/screenshots/${name}.png`,
    expectedImage: `cypress/snapshots/${name}.png`,
    threshold
  })
})

// Usage
cy.visit('/login')
cy.compareSnapshot('login-page', { threshold: 0.05 })

Pruebas responsivas

describe('Responsive Tests', () => {
  const viewports = [
    { width: 320, height: 568 },   // iPhone SE
    { width: 375, height: 667 },   // iPhone 8
    { width: 768, height: 1024 },  // iPad
    { width: 1024, height: 768 },  // iPad Landscape
    { width: 1280, height: 720 },  // Desktop
  ]

  viewports.forEach((viewport) => {
    it(`should display correctly at ${viewport.width}x${viewport.height}`, () => {
      cy.viewport(viewport.width, viewport.height)
      cy.visit('/')
      cy.screenshot(`homepage-${viewport.width}x${viewport.height}`)

      // Test responsive behavior
      if (viewport.width < 768) {
        cy.get('[data-cy="mobile-menu"]').should('be.visible')
        cy.get('[data-cy="desktop-menu"]').should('not.be.visible')
      } else {
        cy.get('[data-cy="mobile-menu"]').should('not.be.visible')
        cy.get('[data-cy="desktop-menu"]').should('be.visible')
      }
    })
  })
})

CI/CD Integration

GitHub Actions

# .github/workflows/cypress.yml
name: Cypress Tests

on: [push, pull_request]

jobs:
  cypress-run:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        browser: [chrome, firefox, edge]

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Start application
        run: npm start &

      - name: Wait for application
        run: npx wait-on http://localhost:3000

      - name: Run Cypress tests
        uses: cypress-io/github-action@v5
        with:
          browser: ${{ matrix.browser }}
          record: true
          parallel: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload screenshots
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: cypress-screenshots-${{ matrix.browser }}
          path: cypress/screenshots

      - name: Upload videos
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: cypress-videos-${{ matrix.browser }}
          path: cypress/videos

Docker Integration

# Dockerfile.cypress
FROM cypress/included:12.17.0

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# Run tests
CMD ["npx", "cypress", "run"]
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=test

  cypress:
    build:
      context: .
      dockerfile: Dockerfile.cypress
    depends_on:
      - app
    environment:
      - CYPRESS_baseUrl=http://app:3000
    volumes:
      - ./cypress/videos:/app/cypress/videos
      - ./cypress/screenshots:/app/cypress/screenshots

Pruebas paralelas

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Enable parallel testing
    setupNodeEvents(on, config) {
      // Split tests across multiple machines
      if (config.env.SPLIT_TESTS) {
|  |  |  | const totalMachines = config.env.TOTAL_MACHINES |  | 1 |  |  |  |
|  |  |  | const currentMachine = config.env.CURRENT_MACHINE |  | 0 |  |  |  |

        // Custom logic to split test files
        config.specPattern = getSpecsForMachine(currentMachine, totalMachines)
      }

      return config
    }
  }
})

function getSpecsForMachine(current, total) {
  // Implementation to split specs across machines
  const allSpecs = [
    'cypress/e2e/auth/*.cy.js',
    'cypress/e2e/dashboard/*.cy.js',
    'cypress/e2e/users/*.cy.js'
  ]

  return allSpecs.filter((_, index) => index % total === current)
}

Test Reporting

# Install mochawesome reporter
npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
// cypress.config.js
module.exports = defineConfig({
  e2e: {
    reporter: 'mochawesome',
    reporterOptions: {
      reportDir: 'cypress/reports',
      overwrite: false,
      html: false,
      json: true
    }
  }
})
{
  "scripts": {
    "test:report": "cypress run && npm run merge:reports && npm run generate:report",
    "merge:reports": "mochawesome-merge cypress/reports/*.json > cypress/reports/merged-report.json",
    "generate:report": "marge cypress/reports/merged-report.json --reportDir cypress/reports --inline"
  }
}

Buenas prácticas

Organización de los ensayos

// Good: Descriptive test names
describe('User Authentication', () => {
  it('should allow user to login with valid credentials', () => {
    // Test implementation
  })

  it('should display error message for invalid credentials', () => {
    // Test implementation
  })

  it('should redirect to dashboard after successful login', () => {
    // Test implementation
  })
})

// Good: Group related tests
describe('Shopping Cart', () => {
  describe('Adding Items', () => {
    it('should add item to cart')
    it('should update cart count')
    it('should show item in cart sidebar')
  })

  describe('Removing Items', () => {
    it('should remove item from cart')
    it('should update cart total')
    it('should show empty cart message when no items')
  })
})

Selector Mejores Prácticas

// ❌ Bad: Fragile selectors
cy.get('.btn.btn-primary.submit-button')
cy.get('div > form > div:nth-child(2) > input')
cy.get('button:contains("Submit")')

// ✅ Good: Stable selectors
cy.get('[data-cy="submit-button"]')
cy.get('[data-testid="email-input"]')
cy.get('[aria-label="Submit form"]')

// ✅ Good: Custom commands for common patterns
Cypress.Commands.add('getByTestId', (testId) => {
  return cy.get(`[data-testid="${testId}"]`)
})

cy.getByTestId('email-input').type('user@example.com')

Esperando mejores prácticas

// ❌ Bad: Arbitrary waits
cy.wait(3000)
cy.get('button').click()

// ✅ Good: Wait for specific conditions
cy.get('[data-cy="loading"]').should('not.exist')
cy.get('[data-cy="submit-button"]').should('be.enabled').click()

// ✅ Good: Wait for network requests
cy.intercept('POST', '/api/users').as('createUser')
cy.get('[data-cy="submit"]').click()
cy.wait('@createUser')

Gestión de datos de prueba

// ✅ Good: Use fixtures for test data
cy.fixture('users.json').then((users) => {
  const testUser = users.validUser
  cy.login(testUser.email, testUser.password)
})

// ✅ Good: Generate dynamic test data
const generateUser = () => ({
  name: `Test User ${Date.now()}`,
  email: `test${Date.now()}@example.com`,
  password: 'password123'
})

// ✅ Good: Clean up test data
afterEach(() => {
  cy.task('cleanupTestData')
})

Manejo de errores

// ✅ Good: Handle expected errors
cy.get('[data-cy="submit"]').click()
cy.get('[data-cy="error-message"]')
  .should('be.visible')
  .and('contain', 'Email is required')

// ✅ Good: Retry flaky operations
Cypress.Commands.add('retryableClick', (selector, maxRetries = 3) => {
  let attempts = 0

  const clickWithRetry = () => {
    attempts++

    return cy.get(selector).click().then(() => {
      // Verify click was successful
      return cy.get(selector).should('have.class', 'clicked')
    }).catch((error) => {
      if (attempts < maxRetries) {
        cy.wait(1000)
        return clickWithRetry()
      }
      throw error
    })
  }

  return clickWithRetry()
})

Debugging

Debug Commands

// Pause test execution
cy.pause()

// Debug specific element
cy.get('[data-cy="button"]').debug()

// Log values
cy.get('[data-cy="input"]').then(($el) => {
  console.log('Element value:', $el.val())
})

// Take screenshot at specific point
cy.screenshot('debug-point-1')

// Log network requests
cy.intercept('**', (req) => {
  console.log('Request:', req.method, req.url)
})

Browser DevTools

// Open DevTools programmatically
cy.window().then((win) => {
  win.debugger
})

// Inspect element in DevTools
cy.get('[data-cy="element"]').then(($el) => {
  debugger // Will pause in DevTools
  console.log($el[0]) // Inspect element
})

// Access application state
cy.window().its('store').then((store) => {
  console.log('Redux state:', store.getState())
})

Comandos de depuración personalizados

// cypress/support/commands.js
Cypress.Commands.add('logElement', (selector) => {
  cy.get(selector).then(($el) => {
    console.log('Element:', $el[0])
    console.log('Text:', $el.text())
    console.log('Value:', $el.val())
    console.log('Classes:', $el.attr('class'))
  })
})

Cypress.Commands.add('debugState', () => {
  cy.window().then((win) => {
    if (win.store) {
      console.log('Redux State:', win.store.getState())
    }
    if (win.localStorage) {
      console.log('LocalStorage:', win.localStorage)
    }
    if (win.sessionStorage) {
      console.log('SessionStorage:', win.sessionStorage)
    }
  })
})

// Usage
cy.logElement('[data-cy="user-profile"]')
cy.debugState()

Test Debugging Strategies

// Strategy 1: Isolate failing test
it.only('should debug this specific test', () => {
  // Test implementation
})

// Strategy 2: Add detailed logging
it('should complete user flow', () => {
  cy.log('Step 1: Visit login page')
  cy.visit('/login')

  cy.log('Step 2: Fill credentials')
  cy.get('[data-cy="email"]').type('user@example.com')
  cy.get('[data-cy="password"]').type('password123')

  cy.log('Step 3: Submit form')
  cy.get('[data-cy="submit"]').click()

  cy.log('Step 4: Verify redirect')
  cy.url().should('include', '/dashboard')
})

// Strategy 3: Break down complex tests
it('should login user', () => {
  cy.login('user@example.com', 'password123')
})

it('should navigate to profile', () => {
  cy.login('user@example.com', 'password123')
  cy.get('[data-cy="profile-link"]').click()
  cy.url().should('include', '/profile')
})
```_

## Ejecución

### Prueba de rendimiento
```javascript
// Optimize test setup
describe('Performance Optimized Tests', () => {
  // Use beforeEach for common setup
  beforeEach(() => {
    cy.login('user@example.com', 'password123')
  })

  // Avoid unnecessary visits
  it('should test multiple features on same page', () => {
    cy.visit('/dashboard')

    // Test multiple things on the same page
    cy.get('[data-cy="user-name"]').should('contain', 'John Doe')
    cy.get('[data-cy="notification-count"]').should('contain', '3')
    cy.get('[data-cy="recent-activity"]').should('be.visible')
  })
})

// Use API for setup when possible
Cypress.Commands.add('seedData', () => {
  return cy.request('POST', '/api/test/seed', {
    users: 10,
    posts: 50,
    comments: 100
  })
})

// Usage
beforeEach(() => {
  cy.seedData()
})

Ejecución paralela

# Run tests in parallel (requires Cypress Dashboard)
npx cypress run --record --parallel

# Split tests manually
npx cypress run --spec "cypress/e2e/auth/**/*"
npx cypress run --spec "cypress/e2e/dashboard/**/*"
npx cypress run --spec "cypress/e2e/users/**/*"

Optimización de memoria

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Optimize memory usage
    numTestsKeptInMemory: 5,

    // Disable video for faster execution
    video: false,

    // Reduce screenshot quality
    screenshotOnRunFailure: true,

    setupNodeEvents(on, config) {
      // Clear browser cache between tests
      on('before:browser:launch', (browser, launchOptions) => {
        if (browser.name === 'chrome') {
          launchOptions.args.push('--disable-dev-shm-usage')
          launchOptions.args.push('--memory-pressure-off')
        }
        return launchOptions
      })
    }
  }
})

Solución de problemas

Cuestiones comunes

// Issue: Element not found
// Solution: Wait for element or check selector
cy.get('[data-cy="button"]', { timeout: 10000 }).should('exist')

// Issue: Element not clickable
// Solution: Ensure element is visible and enabled
cy.get('[data-cy="button"]')
  .should('be.visible')
  .and('not.be.disabled')
  .click()

// Issue: Flaky tests
// Solution: Add proper waits and assertions
cy.intercept('GET', '/api/data').as('getData')
cy.visit('/page')
cy.wait('@getData')
cy.get('[data-cy="content"]').should('be.visible')

// Issue: Cross-origin errors
// Solution: Disable web security (not recommended for production)
// cypress.config.js
{
  chromeWebSecurity: false
}

Configuración de depuración

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Enable debug mode
    setupNodeEvents(on, config) {
      // Log all events
      on('task', {
        log(message) {
          console.log(message)
          return null
        }
      })

      // Debug failed tests
      on('after:spec', (spec, results) => {
        if (results && results.stats.failures) {
          console.log('Failed spec:', spec.relative)
          console.log('Failures:', results.stats.failures)
        }
      })
    }
  }
})

// Usage in tests
cy.task('log', 'Debug message here')

Recuperación de errores

// Retry failed commands
Cypress.Commands.add('retryCommand', (command, maxRetries = 3) => {
  let attempts = 0

  const executeCommand = () => {
    attempts++

    return command().catch((error) => {
      if (attempts < maxRetries) {
        cy.wait(1000)
        return executeCommand()
      }
      throw error
    })
  }

  return executeCommand()
})

// Usage
cy.retryCommand(() => cy.get('[data-cy="flaky-element"]').click())

-...

Resumen

Cypress es un poderoso marco de pruebas de extremo a extremo que proporciona:

  • Experiencia de desarrolladores: API intuitiva con vista previa del navegador en tiempo real
  • Esperanza Automática: La espera inteligente de elementos y solicitudes de red
  • Time Travel: Pruebas de depuración con instantáneas en cada paso
  • Control de red: Interceptar y modificar las solicitudes de red
  • Real Browser Testing: Las pruebas se ejecutan en navegadores reales para obtener resultados precisos
  • Rich Ecosystem: Extensivo ecosistema de plugins e integraciones
  • CI/CD List: Apoyo integrado para la integración continua
  • ** Pruebas visuales**: Capacidades de prueba de regresión visual y de pantalla

Cypress destaca en la prueba de aplicaciones web modernas con su enfoque en la experiencia del desarrollador, herramientas de depuración integral y ejecución de pruebas confiable. Su arquitectura única y conjunto de características lo convierten en una excelente opción para los equipos que buscan implementar sólidos flujos de trabajo de pruebas de extremo a extremo.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título