🎯 Рекомендуемые коллекции

Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать

Cypress E2E Тестовый Фреймворк

Всесторонние примеры E2E тестов Cypress включая конфигурацию тестов, Page Object Models, тесты API, визуальные регрессионные тесты и продвинутые E2E паттерны для современных веб приложений

💻 Настройка и Конфигурация Проекта Cypress javascript

🟢 simple ⭐⭐

Полная настройка проекта Cypress с конфигурационными файлами, структурой тестов, пользовательскими командами и лучшими практиками для 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"
  }
}

💻 Продвинутые Взаимодействия Пользователя и Проверки javascript

🟡 intermediate ⭐⭐⭐

Сложные примеры взаимодействий пользователя включая drag and drop, загрузку файлов, горячие клавиши, hover эффекты и продвинутые стратегии проверок

⏱️ 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');
  });
});

💻 Тесты API и Перехват Сетевых Запросов javascript

🟡 intermediate ⭐⭐⭐⭐

Всесторонние примеры тестов API включая перехват запросов/ответов, заглушки, аутентификацию и тесты производительности с 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 });
  });
});

💻 Визуальные Регрессионные Тесты и Мульти-браузерные Тесты javascript

🔴 complex ⭐⭐⭐⭐

Стратегии визуального тестирования включая сравнение скриншотов, мульти-браузерные тесты, проверку адаптивного дизайна и рабочие процессы визуальной регрессии с 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;