Framework de Testes E2E Cypress

Exemplos abrangentes de testes E2E Cypress incluindo configuração de testes, modelos de objeto de página, testes de API, testes de regressão visual e padrões E2E avançados para aplicações web modernas

💻 Configuração de Projeto Cypress javascript

🟢 simple ⭐⭐

Configuração completa de projeto Cypress com arquivos de configuração, estrutura de testes, comandos personalizados e melhores práticas de testes E2E

⏱️ 20 min 🏷️ cypress, setup, configuration, e2e testing
Prerequisites: JavaScript, Web development basics, npm/yarn
// 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
{
  "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;
  }

  // Return false to prevent Cypress from failing the test
  return true;
});

// 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": "[email protected]",
    "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"
  }
}

💻 Interações Avançadas de Usuário e Asserções javascript

🟡 intermediate ⭐⭐⭐

Exemplos complexos de interações de usuário incluindo drag and drop, upload de arquivos, atalhos de teclado, efeitos hover e estratégias avançadas de asserção

⏱️ 30 min 🏷️ cypress, interactions, advanced testing, accessibility
Prerequisites: JavaScript advanced, DOM manipulation, Web accessibility
// 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('[email protected]');

    // 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('[email protected]');
    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', '[email protected]');
    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('[email protected]', { 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');
  });
});

💻 Testes de API e Interceptação de Rede javascript

🟡 intermediate ⭐⭐⭐⭐

Exemplos abrangentes de testes de API incluindo interceptação de request/response, stubbing, autenticação e testes de performance com Cypress

⏱️ 35 min 🏷️ cypress, api testing, network, performance testing
Prerequisites: JavaScript advanced, REST APIs, HTTP protocols, GraphQL basics
// 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: '[email protected]',
      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: '[email protected]'
    };

    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: '[email protected]' },
        { id: 2, name: 'Jane Smith', email: '[email protected]' }
      ]
    }).as('getUsers');

    // Intercept POST user request
    cy.intercept('POST', '/api/users', {
      statusCode: 201,
      body: { id: 3, name: 'New User', email: '[email protected]' }
    }).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: '[email protected]'
        }
      }
    };

    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('[email protected]');
      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 });
  });
});

💻 Testes de Regressão Visual e Cross-Browser javascript

🔴 complex ⭐⭐⭐⭐

Estratégias de testes visuais incluindo comparação de screenshots, testes cross-browser, verificação de design responsivo e fluxos de trabalho de regressão visual com Cypress

⏱️ 40 min 🏷️ cypress, visual testing, regression, cross-browser, responsive
Prerequisites: Cypress advanced, CSS/HTML knowledge, Responsive design, Web accessibility
// 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;