Elysia Tools

Explora herramientas, ejemplos, explicaciones visuales y guías temáticas en un solo espacio claro.

Categorías populares

Temas

Idioma
ZHDERUPTENFRES

Elysia Tools

Navegación

Pruebas

Framework de Pruebas E2E Cypress

Ejemplos comprensivos de pruebas E2E Cypress incluyendo configuración de pruebas, modelos de objetos de página, pruebas API, pruebas de regresión visual y patrones E2E avanzados para aplicaciones web modernas

Ejemplos

Entradas de esta colección

CódigojavascriptSimple

Configuración de Proyecto Cypress

Configuración completa de proyecto Cypress con archivos de configuración, estructura de pruebas, comandos personalizados y mejores prácticas de pruebas E2E

Dificultad

3/10

Tiempo estimado

20 min

Etiquetas

cypress, setup, configuration, e2e testing

Requisitos previos

JavaScript, Web development basics, npm/yarn

javascript
// Cypress Project Setup and Configuration

// 1. cypress.config.js - Main configuration
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  // Project configuration
  projectId: 'your-project-id',

  // Browser configuration
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    excludeSpecPattern: ['**/node_modules/**', '**/dist/**'],

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

    // Video settings
    video: true,
    videoCompression: 32,

    // Screenshot settings
    screenshotOnRunFailure: true,

    // Default command timeout
    defaultCommandTimeout: 10000,

    // Request timeout
    requestTimeout: 10000,

    // Response timeout
    responseTimeout: 10000,

    // Environment variables
    env: {
      username: 'testuser',
      password: 'testpass',
      apiUrl: 'http://localhost:4000/api'
    },

    // Page load timeout
    pageLoadTimeout: 30000,

    // retries
    retries: {
      runMode: 2,
      openMode: 0
    },

    // Browser settings
    chromeWebSecurity: false,

    // Experimental features
    experimentalStudio: true,
    experimentalWebKitSupport: true
  },

  // Component testing configuration
  component: {
    devServer: {
      framework: 'create-react-app',
      bundler: 'webpack'
    }
  }
});

// 2. package.json dependencies
const packageJson = {
  "devDependencies": {
    "cypress": "^13.6.0",
    "@cypress/xhr": "^2.5.1",
    "cypress-mochawesome-reporter": "^3.6.0",
    "cypress-visual-regression": "^2.0.0",
    "cypress-real-events": "^1.10.4",
    "cypress-plugin-tab": "^1.0.5",
    "cypress-localstorage-commands": "^2.2.2",
    "cypress-wait-until": "^1.7.2"
  },
  "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run",
    "cy:run:headed": "cypress run --headed",
    "cy:run:chrome": "cypress run --browser chrome",
    "cy:run:record": "cypress run --record",
    "cy:run:ci": "cypress run --record --browser chrome --headless",
    "cy:report": "cypress run --reporter cypress-mochawesome-reporter"
  }
};

// 3. cypress/support/e2e.js - Global setup and commands
// Import commands file using ES2015 syntax:
import './commands';

// Alternatively you can use CommonJS syntax:
// require('./commands')

// Global beforeEach hook
beforeEach(() => {
  // Clear local storage before each test
  cy.clearLocalStorage();

  // Clear cookies before each test
  cy.clearCookies();
});

// Global afterEach hook
afterEach(() => {
  // Take screenshot on failure
  cy.screenshot({ capture: 'runner' });
});

// Global error handling
Cypress.on('uncaught:exception', (err, runnable) => {
  // Return false to prevent Cypress from failing the test
  if (err.message.includes('ResizeObserver loop limit exceeded')) {
    return false;
  }

  // Let Cypress handle other uncaught exceptions as test failures
  return undefined;
});

// 4. cypress/support/commands.js - Custom commands
// Custom login command
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login');
  cy.get('[data-cy=username]').type(username);
  cy.get('[data-cy=password]').type(password);
  cy.get('[data-cy=login-button]').click();
  cy.url().should('not.include', '/login');
});

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

// Custom command to wait for API response
Cypress.Commands.add('waitForApi', (alias, timeout = 10000) => {
  cy.wait(`@${alias}`, { timeout });
});

// Custom command to check element visibility
Cypress.Commands.add('shouldBeVisible', { prevSubject: true }, (subject, options = {}) => {
  cy.wrap(subject).should('be.visible');
  return cy.wrap(subject);
});

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

// Custom command for file upload
Cypress.Commands.add('uploadFile', (selector, fileName, fileType = '') => {
  cy.get(selector).then(subject => {
    cy.fixture(fileName).then(fileContent => {
      const blob = new Blob([fileContent], { type: fileType });
      const file = new File([blob], fileName, { type: fileType });

      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);
      subject[0].files = dataTransfer.files;
    });
  });
});

// 5. Basic test structure
// cypress/e2e/login.cy.js
describe('Authentication', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should display login form', () => {
    cy.getDataCy('username').should('be.visible');
    cy.getDataCy('password').should('be.visible');
    cy.getDataCy('login-button').should('be.visible');
    cy.get('h1').should('contain', 'Login');
  });

  it('should show error for invalid credentials', () => {
    cy.getDataCy('username').type('invaliduser');
    cy.getDataCy('password').type('invalidpass');
    cy.getDataCy('login-button').click();

    cy.getDataCy('error-message').should('be.visible');
    cy.getDataCy('error-message').should('contain', 'Invalid credentials');
    cy.url().should('include', '/login');
  });

  it('should login successfully with valid credentials', () => {
    cy.getDataCy('username').type(Cypress.env('username'));
    cy.getDataCy('password').type(Cypress.env('password'));
    cy.getDataCy('login-button').click();

    cy.url().should('not.include', '/login');
    cy.getDataCy('user-menu').should('be.visible');
  });

  it('should handle empty form submission', () => {
    cy.getDataCy('login-button').click();

    cy.getDataCy('username').should('have.class', 'error');
    cy.getDataCy('password').should('have.class', 'error');
    cy.getDataCy('error-message').should('contain', 'Please fill in all fields');
  });

  it('should toggle password visibility', () => {
    cy.getDataCy('password').type('password123');
    cy.getDataCy('password').should('have.attr', 'type', 'password');

    cy.getDataCy('toggle-password').click();
    cy.getDataCy('password').should('have.attr', 'type', 'text');

    cy.getDataCy('toggle-password').click();
    cy.getDataCy('password').should('have.attr', 'type', 'password');
  });
});

// 6. Page Object Model example
// cypress/e2e/page-objects/LoginPage.js
class LoginPage {
  visit() {
    cy.visit('/login');
  }

  getUsernameField() {
    return cy.getDataCy('username');
  }

  getPasswordField() {
    return cy.getDataCy('password');
  }

  getLoginButton() {
    return cy.getDataCy('login-button');
  }

  getErrorMessage() {
    return cy.getDataCy('error-message');
  }

  login(username, password) {
    this.getUsernameField().type(username);
    this.getPasswordField().type(password);
    this.getLoginButton().click();
  }

  verifyLoginPage() {
    cy.url().should('include', '/login');
    this.getUsernameField().should('be.visible');
    this.getPasswordField().should('be.visible');
    this.getLoginButton().should('be.visible');
  }

  verifyLoginError() {
    this.getErrorMessage().should('be.visible');
    cy.url().should('include', '/login');
  }
}

export default LoginPage;

// cypress/e2e/authentication-with-pom.cy.js
import LoginPage from '../page-objects/LoginPage';

describe('Authentication with Page Object Model', () => {
  const loginPage = new LoginPage();

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

  it('should login successfully using POM', () => {
    loginPage.login(Cypress.env('username'), Cypress.env('password'));

    cy.url().should('not.include', '/login');
    cy.getDataCy('user-menu').should('be.visible');
  });

  it('should show error using POM', () => {
    loginPage.login('invalid', 'credentials');

    loginPage.verifyLoginError();
    loginPage.getErrorMessage().should('contain', 'Invalid credentials');
  });
});

// 7. Test organization with fixtures
// cypress/fixtures/users.json
{
  "validUser": {
    "username": "testuser",
    "password": "testpass123",
    "email": "testuser@example.com",
    "firstName": "Test",
    "lastName": "User"
  },
  "invalidUser": {
    "username": "invaliduser",
    "password": "wrongpass"
  },
  "adminUser": {
    "username": "admin",
    "password": "adminpass",
    "role": "administrator"
  }
}

// cypress/fixtures/products.json
{
  "products": [
    {
      "id": 1,
      "name": "Laptop",
      "price": 999.99,
      "category": "Electronics",
      "inStock": true
    },
    {
      "id": 2,
      "name": "Book",
      "price": 19.99,
      "category": "Books",
      "inStock": true
    }
  ]
}

// cypress/e2e/using-fixtures.cy.js
describe('Using Fixtures in Tests', () => {
  let userData;

  beforeEach(() => {
    cy.fixture('users').then((users) => {
      userData = users;
    });
  });

  it('should login with valid user from fixtures', () => {
    cy.visit('/login');

    cy.getDataCy('username').type(userData.validUser.username);
    cy.getDataCy('password').type(userData.validUser.password);
    cy.getDataCy('login-button').click();

    cy.url().should('not.include', '/login');
  });

  it('should fail login with invalid user from fixtures', () => {
    cy.visit('/login');

    cy.getDataCy('username').type(userData.invalidUser.username);
    cy.getDataCy('password').type(userData.invalidUser.password);
    cy.getDataCy('login-button').click();

    cy.getDataCy('error-message').should('be.visible');
  });
});

// 8. Environment-specific configuration
// cypress.config.env.js
module.exports = (on, config) => {
  // Get environment from command line or use default
  const environment = config.env.environment || 'development';

  // Load environment-specific configuration
  const environmentConfig = require(`./cypress/environments/${environment}.json`);

  // Merge environment config with default config
  Object.assign(config, environmentConfig);

  // Return final config
  return config;
};

// cypress/environments/development.json
{
  "baseUrl": "http://localhost:3000",
  "env": {
    "apiUrl": "http://localhost:4000/api",
    "username": "devuser",
    "password": "devpass"
  }
}

// cypress/environments/staging.json
{
  "baseUrl": "https://staging.example.com",
  "env": {
    "apiUrl": "https://api-staging.example.com/api",
    "username": "staginguser",
    "password": "stagingpass"
  }
}

// cypress/environments/production.json
{
  "baseUrl": "https://example.com",
  "env": {
    "apiUrl": "https://api.example.com/api",
    "username": "produser",
    "password": "prodpass"
  }
}
CódigojavascriptIntermedio

Interacciones de Usuario Avanzadas y Aserciones

Ejemplos complejos de interacciones de usuario incluyendo drag and drop, carga de archivos, atajos de teclado, efectos hover y estrategias avanzadas de aserción

Dificultad

6/10

Tiempo estimado

30 min

Etiquetas

cypress, interactions, advanced testing, accessibility

Requisitos previos

JavaScript advanced, DOM manipulation, Web accessibility

javascript
// Advanced User Interactions with Cypress

// 1. Complex Form Interactions
// cypress/e2e/advanced-forms.cy.js
describe('Advanced Form Interactions', () => {
  beforeEach(() => {
    cy.visit('/forms/advanced');
  });

  it('should handle multi-step form with validation', () => {
    // Step 1: Personal Information
    cy.getDataCy('step-1').should('be.visible');
    cy.getDataCy('first-name').type('John').should('have.value', 'John');
    cy.getDataCy('last-name').type('Doe').should('have.value', 'Doe');
    cy.getDataCy('email').type('john.doe@example.com');

    // Email validation
    cy.getDataCy('email').blur();
    cy.getDataCy('email-error').should('not.exist');

    // Invalid email test
    cy.getDataCy('email').clear().type('invalid-email');
    cy.getDataCy('email').blur();
    cy.getDataCy('email-error').should('be.visible')
      .and('contain', 'Please enter a valid email');

    // Correct email
    cy.getDataCy('email').clear().type('john.doe@example.com');
    cy.getDataCy('next-step').click();

    // Step 2: Address Information
    cy.getDataCy('step-2').should('be.visible');
    cy.getDataCy('address').type('123 Main St');
    cy.getDataCy('city').type('New York');

    // Dynamic dropdown
    cy.getDataCy('state').click();
    cy.getDataCy('state-option').contains('New York').click();
    cy.getDataCy('state').should('contain', 'New York');

    // Step 3: Review and Submit
    cy.getDataCy('next-step').click();
    cy.getDataCy('step-3').should('be.visible');

    // Verify all information is displayed correctly
    cy.getDataCy('review-first-name').should('contain', 'John');
    cy.getDataCy('review-last-name').should('contain', 'Doe');
    cy.getDataCy('review-email').should('contain', 'john.doe@example.com');
    cy.getDataCy('review-address').should('contain', '123 Main St');

    // Submit form
    cy.getDataCy('submit-form').click();
    cy.getDataCy('success-message').should('be.visible')
      .and('contain', 'Form submitted successfully');
  });

  it('should handle conditional form fields', () => {
    cy.getDataCy('has-experience').check();
    cy.getDataCy('experience-section').should('be.visible');

    cy.getDataCy('years-experience').select('5-10');
    cy.getDataCy('previous-companies').type(['Company A', 'Company B']);

    // Dynamic field addition
    cy.getDataCy('add-company').click();
    cy.getDataCy('company-input').should('have.length', 3);

    cy.getDataCy('has-experience').uncheck();
    cy.getDataCy('experience-section').should('not.exist');
  });
});

// 2. Drag and Drop Functionality
// cypress/e2e/drag-drop.cy.js
describe('Drag and Drop Interactions', () => {
  beforeEach(() => {
    cy.visit('/drag-drop');
  });

  it('should drag items between containers', () => {
    const dataTransfer = new DataTransfer();

    // Get source and target elements
    cy.getDataCy('source-item-1')
      .trigger('dragstart', { dataTransfer })
      .trigger('dragleave', { dataTransfer });

    cy.getDataCy('target-container')
      .trigger('dragover', { dataTransfer })
      .trigger('drop', { dataTransfer })
      .trigger('dragend', { dataTransfer });

    cy.getDataCy('target-container').within(() => {
      cy.getDataCy('source-item-1').should('exist');
    });

    cy.getDataCy('source-container').within(() => {
      cy.getDataCy('source-item-1').should('not.exist');
    });
  });

  it('should handle sortable lists', () => {
    // Using cypress-real-events plugin for better drag support
    cy.getDataCy('sortable-item-1').realMouseDown();
    cy.getDataCy('sortable-item-3').realMouseMove({ clientX: 100, clientY: 200 });
    cy.getDataCy('sortable-item-3').realMouseUp();

    // Verify order has changed
    cy.getDataCy('sortable-list').within(() => {
      cy.getDataCy('sortable-item').eq(0).should('contain', 'Item 2');
      cy.getDataCy('sortable-item').eq(1).should('contain', 'Item 3');
      cy.getDataCy('sortable-item').eq(2).should('contain', 'Item 1');
    });
  });

  it('should handle file upload via drag and drop', () => {
    // Intercept file upload request
    cy.intercept('POST', '/api/upload', {
      statusCode: 200,
      body: { success: true, filename: 'test-file.pdf' }
    }).as('fileUpload');

    // Create a fake file
    cy.fixture('test-file.pdf').then(fileContent => {
      cy.get('[data-cy=drop-zone]').selectFile({
        contents: Cypress.Buffer.from(fileContent),
        fileName: 'test-file.pdf',
        mimeType: 'application/pdf'
      }, {
        force: true,
        dragEnter: true,
        dragLeave: true,
        dragOver: true,
        drop: true
      });
    });

    cy.wait('@fileUpload');
    cy.getDataCy('upload-success').should('be.visible');
    cy.getDataCy('uploaded-file').should('contain', 'test-file.pdf');
  });
});

// 3. Keyboard and Accessibility Testing
// cypress/e2e/keyboard-accessibility.cy.js
describe('Keyboard Navigation and Accessibility', () => {
  beforeEach(() => {
    cy.visit('/accessibility');
  });

  it('should be fully keyboard navigable', () => {
    // Tab navigation
    cy.get('body').tab();
    cy.focused().should('have.attr', 'data-cy', 'skip-link');

    // Continue tabbing through interactive elements
    cy.focused().tab();
    cy.focused().should('have.attr', 'data-cy', 'main-navigation');

    cy.focused().tab();
    cy.focused().should('have.attr', 'data-cy', 'search-input');

    // Test form navigation
    cy.get('[data-cy=search-input]').type('test query');
    cy.focused().tab();
    cy.focused().should('have.attr', 'data-cy', 'search-button');

    // Enter to submit
    cy.focused().type('{enter}');
    cy.getDataCy('search-results').should('be.visible');
  });

  it('should handle keyboard shortcuts', () {
    // Test custom keyboard shortcuts
    cy.get('body').type('{ctrl}k'); // Open search modal
    cy.getDataCy('search-modal').should('be.visible');
    cy.focused().should('have.attr', 'data-cy', 'search-input');

    // Escape to close modal
    cy.get('body').type('{esc}');
    cy.getDataCy('search-modal').should('not.exist');

    // Test slash for quick search
    cy.get('body').type('/');
    cy.focused().should('have.attr', 'data-cy', 'search-input');
  });

  it('should have proper ARIA labels and roles', () => {
    // Check ARIA attributes
    cy.getDataCy('main-navigation').should('have.attr', 'role', 'navigation');
    cy.getDataCy('search-button').should('have.attr', 'aria-label', 'Search');
    cy.getDataCy('menu-button').should('have.attr', 'aria-expanded', 'false');

    // Test ARIA live regions
    cy.getDataCy('status-message').should('have.attr', 'aria-live', 'polite');

    // Trigger status update
    cy.getDataCy('update-status').click();
    cy.getDataCy('status-message').should('contain', 'Status updated');
  });

  it('should support screen readers', () => {
    // Check for proper semantic HTML
    cy.get('h1').should('exist');
    cy.get('main').should('exist');
    cy.get('nav').should('exist');

    // Test alt text for images
    cy.getDataCy('product-image').should('have.attr', 'alt').and('not.be.empty');

    // Test form labels
    cy.getDataCy('form-input').should('have.attr', 'aria-labelledby');
    cy.getDataCy('input-label').should('exist');
  });
});

// 4. Advanced Assertions and Chaining
// cypress/e2e/advanced-assertions.cy.js
describe('Advanced Assertions and Chaining', () => {
  beforeEach(() => {
    cy.visit('/advanced-assertions');
  });

  it('should use complex assertions', () => {
    // Multiple assertions on single element
    cy.getDataCy('user-card')
      .should('be.visible')
      .and('have.class', 'active')
      .and('have.css', 'background-color', 'rgb(59, 130, 246)')
      .and('contain.text', 'John Doe');

    // Assertions on multiple elements
    cy.getDataCy('product-item')
      .should('have.length', 5)
      .each(($el, index) => {
        cy.wrap($el).should('have.attr', 'data-product-id');
        if (index < 3) {
          cy.wrap($el).should('have.class', 'featured');
        }
      });

    // Custom assertions with callbacks
    cy.getDataCy('price-display').should(($el) => {
      const text = $el.text();
      const price = parseFloat(text.replace('$', ''));
      expect(price).to.be.greaterThan(0);
      expect(price).to.be.lessThan(1000);
    });

    // Chain assertions with different subjects
    cy.getDataCy('product-container')
      .find('[data-cy=product-item]')
      .first()
      .find('[data-cy=product-name]')
      .should('not.be.empty')
      .parents('[data-cy=product-item]')
      .find('[data-cy=product-price]')
      .should('match', /\$\d+\.\d{2}/);
  });

  it('should handle timing and等待', () => {
    // Wait for element with custom timeout
    cy.getDataCy('slow-loading-content', { timeout: 10000 }).should('be.visible');

    // Wait for text to change
    cy.getDataCy('dynamic-text').should('not.contain', 'Loading...');
    cy.getDataCy('dynamic-text').should('contain', 'Content loaded');

    // Wait for API call
    cy.intercept('GET', '/api/data').as('getData');
    cy.getDataCy('load-data-button').click();
    cy.wait('@getData', { timeout: 15000 }).its('response.statusCode').should('eq', 200);

    // Wait for number of elements
    cy.getDataCy('list-item').should('have.length.greaterThan', 0);

    // Wait for element to become visible
    cy.getDataCy('hidden-element').scrollIntoView().should('be.visible');
  });

  it('should use assertions on window and document', () => {
    // Window assertions
    cy.window().should('have.property', 'localStorage');
    cy.window().its('localStorage').should('have.property', 'setItem');

    // Document assertions
    cy.document().its('title').should('include', 'Advanced Testing');
    cy.document().should('have.property', 'readyState', 'complete');

    // Location assertions
    cy.location().should((location) => {
      expect(location.pathname).to.include('/advanced-assertions');
      expect(location.search).to.be.empty;
    });

    // URL assertions
    cy.url().should('include', '/advanced-assertions');
    cy.url().should('not.include', 'error');
  });
});

// 5. Real User Events Simulation
// cypress/e2e/real-events.cy.js
describe('Real User Events Simulation', () => {
  beforeEach(() => {
    cy.visit('/real-events');
  });

  it('should simulate real mouse movements', () => {
    // Using cypress-real-events for more realistic interactions
    cy.getDataCy('interactive-button').realHover();
    cy.getDataCy('tooltip').should('be.visible');

    cy.getDataCy('interactive-button').realMouseMove(10, 10);
    cy.getDataCy('interactive-button').realMouseMove(20, 5);
    cy.getDataCy('interactive-button').realClick();

    cy.getDataCy('click-feedback').should('be.visible');
  });

  it('should simulate touch events on mobile', () => {
    cy.viewport('iphone-x');

    cy.getDataCy('touch-button').realTouch();
    cy.getDataCy('touch-feedback').should('be.visible');

    // Swipe gesture
    cy.getDataCy('swipe-container')
      .realTouchStart(0, 100)
      .realTouchMove(100, 100)
      .realTouchEnd(100, 100);

    cy.getDataCy('swipe-result').should('contain', 'Swiped right');
  });

  it('should simulate complex form input', () => {
    // Realistic typing with delays
    cy.getDataCy('name-input').realType('John Doe', { delay: 100 });
    cy.getDataCy('email-input').realType('john@example.com', { delay: 50 });

    // Select dropdown with real click
    cy.getDataCy('country-dropdown').realClick();
    cy.get('[data-value="US"]').realClick();
    cy.getDataCy('country-dropdown').should('contain', 'United States');

    // Real file selection
    cy.getDataCy('file-input').realClick();

    // Tab navigation
    cy.get('body').realPress('Tab');
    cy.get('body').realPress('Tab');
    cy.focused().should('have.attr', 'data-cy', 'email-input');
  });

  it('should simulate keyboard shortcuts and modifiers', () => {
    // Ctrl+C copy
    cy.getDataCy('text-to-copy').selectText();
    cy.get('body').realPress(['Control', 'c']);
    cy.getDataCy('copy-feedback').should('contain', 'Copied');

    // Ctrl+V paste
    cy.getDataCy('paste-target').focus();
    cy.get('body').realPress(['Control', 'v']);

    // Shift+Click for multi-select
    cy.getDataCy('selectable-item-1').realClick();
    cy.getDataCy('selectable-item-3').realClick({ shiftKey: true });

    cy.getDataCy('selected-count').should('contain', '3');
  });
});
CódigojavascriptIntermedio

Pruebas API e Intercepción de Red

Ejemplos comprensivos de pruebas API incluyendo intercepción de request/response, stubbing, autenticación y pruebas de rendimiento con Cypress

Dificultad

7/10

Tiempo estimado

35 min

Etiquetas

cypress, api testing, network, performance testing

Requisitos previos

JavaScript advanced, REST APIs, HTTP protocols, GraphQL basics

javascript
// API Testing and Network Interception with Cypress

// 1. Basic API Testing
// cypress/e2e/api/basic-api.cy.js
describe('Basic API Testing', () => {
  const baseUrl = Cypress.env('apiUrl');

  beforeEach(() => {
    // Reset any network stubs
    cy.intercept('GET', '**/api/**').as('apiCalls');
  });

  it('should test GET requests', () => {
    cy.request('GET', `${baseUrl}/users`).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.be.an('array');
      expect(response.body).to.have.length.greaterThan(0);

      // Validate user object structure
      const user = response.body[0];
      expect(user).to.have.property('id');
      expect(user).to.have.property('name');
      expect(user).to.have.property('email');
    });
  });

  it('should test POST requests', () => {
    const newUser = {
      name: 'John Doe',
      email: 'john.doe@example.com',
      role: 'user'
    };

    cy.request('POST', `${baseUrl}/users`, newUser).then((response) => {
      expect(response.status).to.eq(201);
      expect(response.body).to.include(newUser);
      expect(response.body).to.have.property('id');
    });
  });

  it('should test PUT requests', () => {
    const updatedUser = {
      name: 'Jane Doe',
      email: 'jane.doe@example.com'
    };

    cy.request('PUT', `${baseUrl}/users/1`, updatedUser).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.include(updatedUser);
    });
  });

  it('should test DELETE requests', () => {
    cy.request('DELETE', `${baseUrl}/users/1`).then((response) => {
      expect(response.status).to.eq(204);

      // Verify deletion
      cy.request({
        method: 'GET',
        url: `${baseUrl}/users/1`,
        failOnStatusCode: false
      }).then((response) => {
        expect(response.status).to.eq(404);
      });
    });
  });

  it('should handle API errors', () => {
    cy.request({
      method: 'GET',
      url: `${baseUrl}/users/999`,
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(404);
      expect(response.body).to.have.property('error');
    });
  });
});

// 2. Network Interception and Stubbing
// cypress/e2e/api/network-interception.cy.js
describe('Network Interception and Stubbing', () => {
  beforeEach(() => {
    cy.visit('/dashboard');
  });

  it('should intercept and stub API responses', () => {
    // Intercept GET users request
    cy.intercept('GET', '/api/users', {
      statusCode: 200,
      body: [
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
      ]
    }).as('getUsers');

    // Intercept POST user request
    cy.intercept('POST', '/api/users', {
      statusCode: 201,
      body: { id: 3, name: 'New User', email: 'new@example.com' }
    }).as('createUser');

    // Trigger API calls
    cy.getDataCy('load-users').click();
    cy.wait('@getUsers');

    // Verify UI is updated with stubbed data
    cy.getDataCy('user-list').should('contain', 'John Doe');
    cy.getDataCy('user-list').should('contain', 'Jane Smith');

    // Create new user
    cy.getDataCy('add-user').click();
    cy.wait('@createUser');

    // Verify new user appears in list
    cy.getDataCy('user-list').should('contain', 'New User');
  });

  it('should simulate API failures', () => {
    // Intercept with error response
    cy.intercept('GET', '/api/users', {
      statusCode: 500,
      body: { error: 'Internal server error' }
    }).as('getUsersError');

    cy.getDataCy('load-users').click();
    cy.wait('@getUsersError');

    // Verify error handling
    cy.getDataCy('error-message').should('be.visible');
    cy.getDataCy('error-message').should('contain', 'Failed to load users');
  });

  it('should use dynamic response based on request', () => {
    cy.intercept('POST', '/api/auth/login', (req) => {
      const { username, password } = req.body;

      if (username === 'admin' && password === 'password') {
        req.reply({
          statusCode: 200,
          body: {
            token: 'fake-jwt-token',
            user: { id: 1, username: 'admin', role: 'admin' }
          }
        });
      } else {
        req.reply({
          statusCode: 401,
          body: { error: 'Invalid credentials' }
        });
      }
    }).as('login');

    // Test successful login
    cy.getDataCy('username').type('admin');
    cy.getDataCy('password').type('password');
    cy.getDataCy('login-button').click();
    cy.wait('@login');

    cy.url().should('not.include', '/login');
    cy.getDataCy('user-info').should('contain', 'admin');
  });

  it('should handle network delays', () => {
    cy.intercept('GET', '/api/slow-data', {
      statusCode: 200,
      body: { data: 'slow response data' },
      delay: 2000 // 2 second delay
    }).as('slowData');

    cy.getDataCy('load-slow-data').click();
    cy.getDataCy('loading-indicator').should('be.visible');

    cy.wait('@slowData');
    cy.getDataCy('loading-indicator').should('not.exist');
    cy.getDataCy('data-display').should('contain', 'slow response data');
  });

  it('should intercept file uploads', () => {
    cy.intercept('POST', '/api/upload', (req) => {
      expect(req.headers['content-type']).to.include('multipart/form-data');
      req.reply({
        statusCode: 200,
        body: {
          success: true,
          filename: 'test-file.pdf',
          size: 1024
        }
      });
    }).as('fileUpload');

    cy.fixture('test-file.pdf').then(fileContent => {
      cy.getDataCy('file-input').selectFile({
        contents: Cypress.Buffer.from(fileContent),
        fileName: 'test-file.pdf',
        mimeType: 'application/pdf'
      });
    });

    cy.getDataCy('upload-button').click();
    cy.wait('@fileUpload');

    cy.getDataCy('upload-success').should('be.visible');
    cy.getDataCy('uploaded-filename').should('contain', 'test-file.pdf');
  });
});

// 3. API Authentication Testing
// cypress/e2e/api/api-auth.cy.js
describe('API Authentication Testing', () => {
  let authToken;

  beforeEach(() => {
    // Get auth token before each test
    cy.request({
      method: 'POST',
      url: '/api/auth/login',
      body: {
        username: Cypress.env('apiUsername'),
        password: Cypress.env('apiPassword')
      }
    }).then((response) => {
      authToken = response.body.token;
    });
  });

  it('should test authenticated endpoints', () => {
    cy.request({
      method: 'GET',
      url: '/api/users/profile',
      headers: {
        'Authorization': `Bearer ${authToken}`
      }
    }).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.have.property('user');
    });
  });

  it('should reject requests without auth token', () => {
    cy.request({
      method: 'GET',
      url: '/api/users/profile',
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(401);
      expect(response.body).to.have.property('error');
    });
  });

  it('should reject requests with invalid token', () => {
    cy.request({
      method: 'GET',
      url: '/api/users/profile',
      headers: {
        'Authorization': 'Bearer invalid-token'
      },
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(401);
    });
  });

  it('should handle token refresh', () => {
    // Initial request with expired token
    cy.request({
      method: 'GET',
      url: '/api/users/profile',
      headers: {
        'Authorization': 'Bearer expired-token'
      },
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(401);
    });

    // Token refresh flow
    cy.request({
      method: 'POST',
      url: '/api/auth/refresh',
      body: {
        refreshToken: 'valid-refresh-token'
      }
    }).then((response) => {
      expect(response.status).to.eq(200);
      const newToken = response.body.token;

      // Retry with new token
      return cy.request({
        method: 'GET',
        url: '/api/users/profile',
        headers: {
          'Authorization': `Bearer ${newToken}`
        }
      });
    }).then((response) => {
      expect(response.status).to.eq(200);
    });
  });
});

// 4. API Performance Testing
// cypress/e2e/api/api-performance.cy.js
describe('API Performance Testing', () => {
  it('should measure response times', () => {
    const startTime = Date.now();

    cy.request('GET', '/api/users').then((response) => {
      const endTime = Date.now();
      const responseTime = endTime - startTime;

      expect(response.status).to.eq(200);
      expect(responseTime).to.be.lessThan(1000); // Should respond within 1 second

      cy.log(`Response time: ${responseTime}ms`);
    });
  });

  it('should test API under load', () => {
    const requests = [];
    const numRequests = 10;

    // Make multiple concurrent requests
    for (let i = 0; i < numRequests; i++) {
      requests.push(
        cy.request({
          method: 'GET',
          url: '/api/data',
          headers: { 'X-Request-ID': i }
        })
      );
    }

    // Wait for all requests to complete
    Promise.all(requests).then((responses) => {
      responses.forEach((response, index) => {
        expect(response.status).to.eq(200);
        expect(response.body).to.have.property('requestId', index);
      });

      cy.log(`Successfully completed ${numRequests} concurrent requests`);
    });
  });

  it('should handle API rate limiting', () => {
    const requests = [];
    const numRequests = 20;

    // Make requests rapidly to test rate limiting
    for (let i = 0; i < numRequests; i++) {
      requests.push(
        cy.request({
          method: 'GET',
          url: '/api/rate-limited',
          failOnStatusCode: false
        })
      );
    }

    Promise.all(requests).then((responses) => {
      const successCount = responses.filter(r => r.status === 200).length;
      const rateLimitCount = responses.filter(r => r.status === 429).length;

      expect(successCount).to.be.greaterThan(0);
      expect(rateLimitCount).to.be.greaterThan(0);

      cy.log(`Success: ${successCount}, Rate limited: ${rateLimitCount}`);
    });
  });
});

// 5. GraphQL API Testing
// cypress/e2e/api/graphql-api.cy.js
describe('GraphQL API Testing', () => {
  const graphqlUrl = '/graphql';

  it('should test GraphQL queries', () => {
    const query = {
      query: `
        query GetUsers {
          users {
            id
            name
            email
            posts {
              id
              title
            }
          }
        }
      `
    };

    cy.request({
      method: 'POST',
      url: graphqlUrl,
      body: query,
      headers: {
        'Content-Type': 'application/json'
      }
    }).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.have.property('data');
      expect(response.body.data).to.have.property('users');
      expect(response.body.data.users).to.be.an('array');

      const users = response.body.data.users;
      if (users.length > 0) {
        expect(users[0]).to.have.property('id');
        expect(users[0]).to.have.property('name');
        expect(users[0]).to.have.property('posts');
      }
    });
  });

  it('should test GraphQL mutations', () => {
    const mutation = {
      query: `
        mutation CreateUser($input: CreateUserInput!) {
          createUser(input: $input) {
            id
            name
            email
          }
        }
      `,
      variables: {
        input: {
          name: 'Test User',
          email: 'test@example.com'
        }
      }
    };

    cy.request({
      method: 'POST',
      url: graphqlUrl,
      body: mutation,
      headers: {
        'Content-Type': 'application/json'
      }
    }).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.have.property('data');
      expect(response.body.data).to.have.property('createUser');

      const createdUser = response.body.data.createUser;
      expect(createdUser.name).to.eq('Test User');
      expect(createdUser.email).to.eq('test@example.com');
      expect(createdUser).to.have.property('id');
    });
  });

  it('should handle GraphQL errors', () => {
    const invalidQuery = {
      query: `
        query GetInvalidField {
          users {
            invalidField
          }
        }
      `
    };

    cy.request({
      method: 'POST',
      url: graphqlUrl,
      body: invalidQuery,
      headers: {
        'Content-Type': 'application/json'
      },
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(400);
      expect(response.body).to.have.property('errors');
      expect(response.body.errors).to.be.an('array');
      expect(response.body.errors[0]).to.have.property('message');
    });
  });
});

// 6. WebSockets Testing
// cypress/e2e/api/websockets.cy.js
describe('WebSocket Testing', () => {
  it('should test WebSocket connections', () => {
    cy.visit('/websocket-test');

    // Wait for WebSocket connection to establish
    cy.getDataCy('connection-status').should('contain', 'Connected');

    // Send message through WebSocket
    cy.getDataCy('message-input').type('Hello WebSocket!');
    cy.getDataCy('send-button').click();

    // Verify message is echoed back
    cy.getDataCy('message-log').should('contain', 'Hello WebSocket!');
  });

  it('should handle WebSocket reconnection', () => {
    cy.visit('/websocket-test');

    // Simulate connection loss
    cy.window().then((win) => {
      win.websocket.close();
    });

    // Verify disconnection status
    cy.getDataCy('connection-status').should('contain', 'Disconnected');

    // Wait for reconnection
    cy.getDataCy('connection-status').should('contain', 'Connected', { timeout: 10000 });
  });
});
CódigojavascriptComplejo

Pruebas de Regresión Visual y Multi-Navegador

Estrategias de pruebas visuales incluyendo comparación de screenshots, pruebas multi-navegador, verificación de diseño responsivo y flujos de trabajo de regresión visual con Cypress

Dificultad

8/10

Tiempo estimado

40 min

Etiquetas

cypress, visual testing, regression, cross-browser, responsive

Requisitos previos

Cypress advanced, CSS/HTML knowledge, Responsive design, Web accessibility

javascript
// Visual Regression Testing with Cypress

// 1. Basic Visual Testing Setup
// cypress.config.js - Visual testing configuration
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    // Visual testing configuration
    screenshotOnRunFailure: true,
    trashAssetsBeforeRuns: false,
    video: false,

    // Viewports for responsive testing
    viewportWidth: 1280,
    viewportHeight: 720,

    // Environment variables for visual testing
    env: {
      // cypress-visual-regression plugin config
      visualRegressionType: 'regression',
      visualRegressionBaseDirectory: 'cypress/snapshots/base',
      visualRegressionDiffDirectory: 'cypress/snapshots/diff',
      visualRegressionGenerateDiff: {
        viewport: true,
        errorThreshold: 0.1
      }
    }
  }
});

// cypress/support/commands.js - Visual testing commands
import { compareSnapshotCommand } from 'cypress-visual-regression/dist/command';

// Add visual comparison command
compareSnapshotCommand();

// Custom visual testing commands
Cypress.Commands.add('checkVisualRegression', (name, options = {}) => {
  cy.get('body').compareSnapshot(name, {
    capture: 'fullPage',
    errorThreshold: 0.1,
    ...options
  });
});

Cypress.Commands.add('checkElementVisualRegression', (selector, name, options = {}) => {
  cy.get(selector).compareSnapshot(name, {
    capture: 'viewport',
    errorThreshold: 0.05,
    ...options
  });
});

// Custom responsive testing command
Cypress.Commands.add('testResponsive', (callback, viewports = null) => {
  const defaultViewports = [
    { name: 'mobile', width: 375, height: 667 },
    { name: 'tablet', width: 768, height: 1024 },
    { name: 'desktop', width: 1280, height: 720 },
    { name: 'large', width: 1920, height: 1080 }
  ];

  const viewportsToTest = viewports || defaultViewports;

  viewportsToTest.forEach((viewport) => {
    cy.viewport(viewport.width, viewport.height);
    cy.log(`Testing ${viewport.name} (${viewport.width}x${viewport.height})`);

    if (typeof callback === 'function') {
      callback(viewport);
    }
  });
});

// 2. Basic Visual Regression Tests
// cypress/e2e/visual/basic-visual.cy.js
describe('Basic Visual Regression Testing', () => {
  beforeEach(() => {
    cy.visit('/home');
  });

  it('should match homepage snapshot', () => {
    cy.checkVisualRegression('homepage');
  });

  it('should match header element snapshot', () => {
    cy.checkElementVisualRegression('[data-cy=header]', 'header-component');
  });

  it('should match hero section snapshot', () => {
    cy.checkElementVisualRegression('[data-cy=hero-section]', 'hero-section');
  });

  it('should match product grid snapshot', () => {
    cy.checkElementVisualRegression('[data-cy=product-grid]', 'product-grid');
  });

  it('should handle dynamic content with waiting', () => {
    // Wait for dynamic content to load
    cy.getDataCy('featured-products').should('be.visible');
    cy.getDataCy('loading-spinner').should('not.exist');

    cy.checkElementVisualRegression('[data-cy=featured-products]', 'featured-products');
  });

  it('should exclude dynamic elements from comparison', () => {
    // Hide dynamic elements before taking snapshot
    cy.getDataCy='current-time').hide();
    cy.getDataCy('random-quote').hide();
    cy.getDataCy('user-count').hide();

    cy.checkVisualRegression('homepage-without-dynamic-content');
  });
});

// 3. Responsive Design Testing
// cypress/e2e/visual/responsive-design.cy.js
describe('Responsive Design Testing', () => {
  beforeEach(() => {
    cy.visit('/responsive-test');
  });

  it('should look correct on all viewport sizes', () => {
    cy.testResponsive((viewport) => {
      cy.checkVisualRegression(`responsive-${viewport.name}`);

      // Verify key elements are visible and properly positioned
      cy.getDataCy('navigation-menu').should('be.visible');
      cy.getDataCy('main-content').should('be.visible');
      cy.getDataCy('footer').should('be.visible');

      // Viewport-specific assertions
      if (viewport.name === 'mobile') {
        cy.getDataCy('mobile-menu-button').should('be.visible');
        cy.getDataCy('desktop-menu').should('not.exist');
      } else if (viewport.name === 'desktop') {
        cy.getDataCy('desktop-menu').should('be.visible');
        cy.getDataCy('mobile-menu-button').should('not.exist');
      }
    });
  });

  it('should handle navigation correctly across devices', () => {
    cy.testResponsive((viewport) => {
      cy.checkElementVisualRegression('[data-cy=navigation]', `navigation-${viewport.name}`);

      // Test navigation interactions
      if (viewport.name === 'mobile') {
        cy.getDataCy('mobile-menu-button').click();
        cy.getDataCy('mobile-menu').should('be.visible');
        cy.checkElementVisualRegression('[data-cy=mobile-menu]', `mobile-menu-open-${viewport.name}`);
        cy.getDataCy('mobile-menu-button').click();
      } else {
        cy.getDataCy('nav-link').first().realHover();
        cy.checkElementVisualRegression('[data-cy=dropdown-menu]', `dropdown-${viewport.name}`);
      }
    });
  });

  it('should adapt card layouts appropriately', () => {
    // Load test data
    cy.intercept('GET', '/api/cards', { fixture: 'cards.json' }).as('getCards');
    cy.wait('@getCards');

    cy.testResponsive((viewport) => {
      cy.checkElementVisualRegression('[data-cy=card-grid]', `card-grid-${viewport.name}`);

      // Verify card count and layout
      cy.getDataCy('card').should('have.length', 6);

      if (viewport.name === 'mobile') {
        // Should be single column
        cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr');
      } else if (viewport.name === 'tablet') {
        // Should be 2 columns
        cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr 1fr');
      } else {
        // Should be 3 columns
        cy.getDataCy('card-grid').should('have.css', 'grid-template-columns', '1fr 1fr 1fr');
      }
    });
  });
});

// 4. Cross-Browser Testing
// cypress/e2e/visual/cross-browser.cy.js
describe('Cross-Browser Visual Testing', () => {
  const browsers = ['chrome', 'firefox', 'edge'];

  browsers.forEach((browser) => {
    describe(`${browser.charAt(0).toUpperCase() + browser.slice(1)} Browser`, () => {
      beforeEach(() => {
        cy.visit('/cross-browser-test');
      });

      it('should render consistently across browsers', () => {
        cy.checkVisualRegression(`cross-browser-${browser}`);

        // Browser-specific assertions if needed
        if (browser === 'firefox') {
          // Firefox-specific rendering quirks
          cy.getDataCy('firefox-specific-element').should('exist');
        }
      });

      it('should handle CSS Grid correctly', () => {
        cy.checkElementVisualRegression('[data-cy=grid-layout]', `grid-${browser}`);
      });

      it('should handle Flexbox correctly', () => {
        cy.checkElementVisualRegression('[data-cy=flex-layout]', `flex-${browser}`);
      });

      it('should handle custom fonts correctly', () => {
        // Wait for fonts to load
        cy.document().its('fonts.ready').should('be.true');
        cy.checkElementVisualRegression('[data-cy=typography]', `typography-${browser}`);
      });
    });
  });
});

// 5. Interactive Component Visual Testing
// cypress/e2e/visual/interactive-components.cy.js
describe('Interactive Component Visual Testing', () => {
  beforeEach(() => {
    cy.visit('/interactive-components');
  });

  it('should capture button states', () => {
    const button = cy.getDataCy('interactive-button');

    // Default state
    button.compareSnapshot('button-default');

    // Hover state
    button.realHover();
    button.compareSnapshot('button-hover');

    // Active/pressed state
    button.realClick();
    button.compareSnapshot('button-active');

    // Focus state
    button.focus();
    button.compareSnapshot('button-focus');

    // Disabled state
    cy.getDataCy('disable-button').click();
    button.should('be.disabled');
    button.compareSnapshot('button-disabled');
  });

  it('should capture form field states', () => {
    const input = cy.getDataCy('text-input');

    // Empty state
    input.compareSnapshot('input-empty');

    // Focused state
    input.focus();
    input.compareSnapshot('input-focused');

    // With text
    input.type('Test input value');
    input.compareSnapshot('input-with-text');

    // Error state
    input.blur();
    cy.getDataCy('submit-form').click();
    cy.getDataCy('input-error').should('be.visible');
    input.compareSnapshot('input-error');
  });

  it('should capture modal/dialog states', () => {
    // Before opening
    cy.checkVisualRegression('modal-closed');

    // Open modal
    cy.getDataCy('open-modal').click();
    cy.getDataCy('modal').should('be.visible');
    cy.checkElementVisualRegression('[data-cy=modal]', 'modal-open');

    // With content loaded
    cy.wait(1000); // Wait for any animations
    cy.checkElementVisualRegression('[data-cy=modal-content]', 'modal-with-content');

    // Close modal
    cy.getDataCy('close-modal').click();
    cy.getDataCy('modal').should('not.exist');
    cy.checkVisualRegression('modal-after-close');
  });

  it('should capture loading states', () => {
    cy.intercept('GET', '/api/data', {
      statusCode: 200,
      body: { data: 'test' },
      delay: 2000
    }).as('getData');

    // Initial state
    cy.checkVisualRegression('loading-initial');

    // Click to trigger loading
    cy.getDataCy('load-data-button').click();

    // Loading state
    cy.getDataCy('loading-spinner').should('be.visible');
    cy.checkElementVisualRegression('[data-cy=loading-spinner]', 'loading-spinner');

    // After loading completes
    cy.wait('@getData');
    cy.getDataCy('loading-spinner').should('not.exist');
    cy.getDataCy('data-content').should('be.visible');
    cy.checkElementVisualRegression('[data-cy=data-content]', 'data-loaded');
  });
});

// 6. Dark Mode and Theme Testing
// cypress/e2e/visual/theme-testing.cy.js
describe('Theme and Dark Mode Testing', () => {
  const themes = ['light', 'dark', 'auto'];

  themes.forEach((theme) => {
    describe(`${theme.charAt(0).toUpperCase() + theme.slice(1)} Theme`, () => {
      beforeEach(() => {
        cy.visit('/theme-test');

        // Set theme
        if (theme !== 'auto') {
          cy.getDataCy('theme-toggle').click();
          cy.getDataCy(`theme-${theme}`).click();
        }
      });

      it('should match snapshot for theme', () => {
        cy.checkVisualRegression(`theme-${theme}`);
      });

      it('should handle color scheme correctly', () => {
        // Test color scheme meta tag
        if (theme === 'dark') {
          cy.get('html').should('have.class', 'dark');
          cy.get('meta[name="color-scheme"]').should('have.attr', 'content', 'dark');
        } else {
          cy.get('html').should('not.have.class', 'dark');
          cy.get('meta[name="color-scheme"]').should('have.attr', 'content', 'light');
        }

        cy.checkElementVisualRegression('[data-cy=color-scheme-demo]', `colors-${theme}`);
      });

      it('should handle images and icons in different themes', () => {
        cy.checkElementVisualRegression('[data-cy=icon-set]', `icons-${theme}`);
        cy.checkElementVisualRegression('[data-cy=image-gallery]', `images-${theme}`);
      });

      it('should handle text readability', () => {
        // Check contrast ratios (visual check through snapshot)
        cy.checkElementVisualRegression('[data-cy=text-content]', `text-${theme}`);

        // Ensure text is legible (basic automated check)
        cy.getDataCy('heading').should('be.visible');
        cy.getDataCy('body-text').should('be.visible');
        cy.getDataCy('small-text').should('be.visible');
      });
    });
  });
});

// 7. Performance-Related Visual Testing
// cypress/e2e/visual/performance-visual.cy.js
describe('Performance-Related Visual Testing', () => {
  it('should handle lazy loaded images', () => {
    cy.visit('/lazy-loading');

    // Before images load
    cy.checkVisualRegression('lazy-loading-before');

    // Scroll to trigger lazy loading
    cy.getDataCy('lazy-images-container').scrollIntoView();

    // Wait for images to load
    cy.getDataCy('lazy-image').each(($img) => {
      cy.wrap($img).should('be.visible').and('have.prop', 'naturalWidth').and('be.greaterThan', 0);
    });

    // After images load
    cy.checkVisualRegression('lazy-loading-after');
  });

  it('should handle skeleton loading states', () => {
    cy.visit('/skeleton-loading');

    // Initial skeleton state
    cy.checkElementVisualRegression('[data-cy=skeleton-loader]', 'skeleton-loading');

    // Wait for content to load
    cy.intercept('GET', '/api/content', { fixture: 'content.json', delay: 1500 }).as('getContent');
    cy.wait('@getContent');

    // Content loaded state
    cy.getDataCy('skeleton-loader').should('not.exist');
    cy.getDataCy('loaded-content').should('be.visible');
    cy.checkElementVisualRegression('[data-cy=loaded-content]', 'content-loaded');
  });

  it('should handle progressive image loading', () => {
    cy.visit('/progressive-images');

    // Low-quality placeholder
    cy.checkElementVisualRegression('[data-cy=progressive-image]', 'image-placeholder');

    // Wait for high-quality image
    cy.getDataCy('progressive-image').should('have.attr', 'data-loaded', 'true');

    // High-quality final image
    cy.checkElementVisualRegression('[data-cy=progressive-image]', 'image-final');
  });
});

// 8. Advanced Visual Testing Utilities
// cypress/support/visual-utils.js
export class VisualTestingUtils {
  // Helper to hide dynamic elements
  static hideDynamicElements() {
    const dynamicSelectors = [
      '[data-cy=current-time]',
      '[data-cy=random-number]',
      '[data-cy=user-avatar]',
      '.ad-banner',
      '.notification-toast'
    ];

    dynamicSelectors.forEach(selector => {
      cy.get(selector).invoke('hide').should('be.hidden');
    });
  }

  // Helper to wait for images to load
  static waitForImagesToLoad() {
    cy.get('img').each(($img) => {
      cy.wrap($img)
        .should('have.attr', 'src')
        .and('not.have.attr', 'src', '')
        .and('have.prop', 'complete', true)
        .and('have.prop', 'naturalWidth').and('be.greaterThan', 0);
    });
  }

  // Helper to test color schemes
  static testColorScheme(theme) {
    cy.document().its('documentElement.style')
      .should('include', theme === 'dark' ? 'background-color: rgb(17, 24, 39)' : 'background-color: rgb(255, 255, 255)');
  }

  // Helper to capture multiple screenshots for animation testing
  static captureAnimationFrames(selector, name, duration = 1000, interval = 100) {
    const frames = Math.ceil(duration / interval);

    for (let i = 0; i < frames; i++) {
      cy.wait(interval);
      cy.get(selector).compareSnapshot(`${name}-frame-${String(i).padStart(3, '0')}`);
    }
  }

  // Helper to compare across viewports
  static compareAcrossViewports(selector, name, viewports) {
    viewports.forEach((viewport, index) => {
      cy.viewport(viewport.width, viewport.height);
      cy.get(selector).compareSnapshot(`${name}-viewport-${index}`);
    });
  }

  // Helper for accessibility visual testing
  static checkAccessibilityVisuals() {
    // Focus states
    cy.getDataCy('focusable-elements').each(($el) => {
      cy.wrap($el).focus();
      cy.wrap($el).compareSnapshot(`focus-${$el[0].tagName.toLowerCase()}-${$el.attr('data-cy')}`);
      cy.wrap($el).blur();
    });

    // High contrast mode
    cy.window().then((win) => {
      win.matchMedia('(prefers-contrast: high)').matches = true;
      win.dispatchEvent(new Event('change'));
    });

    cy.checkVisualRegression('high-contrast-mode');
  }
}

// Export for use in tests
window.VisualTestingUtils = VisualTestingUtils;

Herramientas

Herramientas que suelen usarse con este ejemplo

Relacionado

Colecciones de ejemplos relacionadas

Relacionado

Temas relacionados

Tema

Corrupción y mutación deliberada de texto para pruebas

Rompa texto limpio con intención — filtre, duplique e invierta frases y palabras, inyecte letras y símbolos aleatorios, desordene líneas y palabras, y demuestre que cada mutación queda bajo control para pruebas de estrés de parsers, ensayos de OCR y experimentos lingüísticos.

Tema

Definición de contratos API, validación de esquemas y pruebas de cambios

Define un contrato API, valida esquemas y cargas capturadas, detecta riesgos de compatibilidad y registra la aceptación.

Tema

Flujo de pruebas, visualizacion y seguridad de regex

Crea expresiones regulares fiables con referencia de sintaxis, pruebas, visualizacion, preview de reemplazos, benchmarks y escaneo ReDoS.

Tema

Flujo utilitario de OpenAPI

Genera tipos y documentacion OpenAPI, revisa cambios incompatibles, valida respuestas y endurece el contrato con pruebas de estres y mutacion.

Inicio
Herramientas3212
Banco de trabajo
Ejemplos593
Visualizaciones471
Visores de archivos103
Temas239
Etiquetas333
Imágenes, audio y video657

Herramientas de procesamiento, conversión y optimización de imágenes, audio y video

Matemáticas, fechas y finanzas647

Calculadoras, herramientas numéricas, lógica de fechas, estadística y finanzas

Diseño y color298

Herramientas de color, maquetación, gráficos, estilo visual y apoyo al diseño

Texto y redacción209

Herramientas de redacción, limpieza, formato, extracción y análisis de texto

Desarrollo y Web193

Utilidades para desarrolladores, red, depuración web y automatización

Conversión y codificación175

Herramientas de conversión de formatos, archivos, unidades, archivos comprimidos y codificación

Lanzamiento de un cuestionario de captación y entrega de contactos
Textos conmemorativos: revisión familiar y entrega para impresión
Hábitos privados: copia, restauración y revisión antes de compartir
De artículo a borradores de X y LinkedIn: revisión factual y entrega
Revisión de una lección Markdown interactiva y entrega sin conexión

© 2026 Elysia Tools. Todos los derechos reservados.

DocumentaciónPreciosAcerca deTérminos de servicioPolítica de privacidadCookiesContacto

Development

Extractor de Docstrings

Extrae y analiza documentación de código fuente JavaScript/TypeScript, Python y Java

Pruebas

Framework de Pruebas E2E Playwright

Ejemplos comprensivos de pruebas Playwright incluyendo automatización multi-navegador, pruebas de API, pruebas móviles, pruebas de regresión visual y patrones E2E avanzados para aplicaciones web modernas

Pruebas

Framework de Pruebas Jest

Ejemplos completos de pruebas Jest incluyendo pruebas unitarias, de integración, mocking, pruebas asíncronas y patrones avanzados para aplicaciones JavaScript/TypeScript

Pruebas

Colecciones Postman - Pruebas API

Ejemplos completos de colecciones Postman incluyendo pruebas de API, scripts de automatización, variables de entorno, servidores mock y patrones avanzados de prueba para REST APIs

Pruebas

Framework de Pruebas JUnit 5

Ejemplos completos de pruebas JUnit 5 incluyendo pruebas unitarias, de integración, parametrizadas, mocking con Mockito y patrones avanzados de prueba para aplicaciones Java

Pruebas

K6 Herramienta Moderna de Pruebas de Rendimiento

Ejemplos completos de pruebas de rendimiento K6 incluyendo pruebas de carga, pruebas de estrés, pruebas de rendimiento de API y patrones avanzados para aplicaciones modernas

Pruebas

Framework de Pruebas Pytest

Ejemplos completos de pruebas con Pytest incluyendo pruebas unitarias, de integración, fixtures, parametrización, mocking y patrones avanzados de prueba para aplicaciones Python