E2E тестирование с Cypress

06 ноября 2026
Автор

Олег Марков

Введение

E2E (end-to-end) тестирование — один из ключевых уровней тестирования современных веб-приложений. Оно позволяет проверить, как приложение работает целиком: от пользовательского интерфейса до API и базы данных. Cypress — один из наиболее популярных инструментов для E2E тестирования, который завоевал признание в React-сообществе благодаря удобному API, встроенному отладчику и быстрой настройке.

В этой статье вы узнаете, что такое E2E тестирование и зачем нужен Cypress, как установить и настроить его в React-проекте, освоите основные команды, научитесь тестировать формы, навигацию и API-вызовы, а также настроите запуск тестов в CI/CD.

Курс по теме

Пройти курс React и Redux Toolkit

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Что такое E2E тестирование и зачем нужен Cypress

Уровни тестирования

В разработке принято выделять три уровня тестирования:

  • Unit-тесты — проверяют отдельные функции и компоненты в изоляции.
  • Интеграционные тесты — проверяют взаимодействие нескольких модулей.
  • E2E-тесты — проверяют приложение целиком, имитируя реальные действия пользователя в браузере.

E2E-тесты наиболее близки к реальному сценарию использования: они открывают браузер, переходят по страницам, заполняют формы, нажимают кнопки и проверяют результаты. Именно поэтому они дают наибольшую уверенность в том, что приложение работает корректно для конечного пользователя.

Почему Cypress

Cypress — это инструмент E2E тестирования, разработанный специально для современных веб-приложений. Его отличают:

  • Работа внутри браузера. В отличие от Selenium, Cypress выполняется непосредственно в контексте браузера, что даёт доступ ко всем DOM-событиям и сетевым запросам без сторонних драйверов.
  • Автоматическое ожидание. Cypress автоматически ждёт появления элементов, завершения запросов и анимаций — без явных sleep() или waitFor().
  • Встроенный отладчик. Интерактивный режим Cypress Test Runner позволяет просматривать каждый шаг теста в реальном времени с историей снимков DOM.
  • Мокирование сети. Встроенная команда cy.intercept() позволяет перехватывать и подменять HTTP-запросы прямо в тестах.
  • Компонентное тестирование. Помимо E2E, Cypress поддерживает изолированное тестирование React-компонентов.

Установка и настройка Cypress в React проекте

Установка

Для установки Cypress выполните следующую команду в корне вашего React-проекта:

npm install --save-dev cypress

или с использованием yarn:

yarn add --dev cypress

Первый запуск

После установки запустите Cypress через npx:

npx cypress open

При первом запуске Cypress предложит выбрать тип тестирования: E2E Testing или Component Testing. Выберите нужный вариант, и Cypress автоматически создаст конфигурационный файл cypress.config.ts (или cypress.config.js) и базовую структуру директорий.

Структура проекта

После инициализации в корне проекта появится папка cypress:

cypress/
├── e2e/           # E2E тесты
├── fixtures/      # Фиктивные данные (JSON-файлы)
├── support/
│   ├── commands.ts  # Кастомные команды
│   └── e2e.ts       # Конфигурация поддержки
cypress.config.ts    # Основной конфиг Cypress

Конфигурационный файл

Пример базовой конфигурации cypress.config.ts:

import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000', // базовый URL вашего приложения
    setupNodeEvents(on, config) {
      // плагины и обработчики событий
    },
  },
});

Параметр baseUrl позволяет использовать относительные пути в командах cy.visit(), например cy.visit('/login') вместо полного URL.

Добавление скриптов в package.json

Для удобства добавьте скрипты в package.json:

{
  "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run"
  }
}

Основные команды Cypress

cy.visit()

Команда cy.visit() открывает страницу по указанному URL:

cy.visit('/');           // корневой маршрут (относительно baseUrl)
cy.visit('/about');      // страница "О нас"
cy.visit('https://example.com'); // абсолютный URL

cy.get()

cy.get() — основной способ поиска элементов в DOM. Принимает CSS-селектор:

cy.get('button');                   // все кнопки
cy.get('.submit-button');           // элемент по классу
cy.get('#username');                // элемент по ID
cy.get('[data-cy="login-btn"]');    // элемент по data-атрибуту
cy.get('form input[type="email"]'); // вложенный селектор

Рекомендуется использовать специальный атрибут data-cy (или data-testid) для тестирования — это делает тесты независимыми от изменений CSS-классов и структуры DOM.

cy.contains()

Команда cy.contains() ищет элемент по текстовому содержимому:

cy.contains('Войти');              // найти элемент с текстом "Войти"
cy.contains('button', 'Войти');   // кнопка с текстом "Войти"
cy.contains(/^Добро пожаловать/); // регулярное выражение

cy.click()

Команда cy.click() имитирует клик по элементу:

cy.get('[data-cy="submit-btn"]').click();
cy.contains('Войти').click();
cy.get('.menu-item').first().click();

cy.type()

cy.type() вводит текст в поле ввода:

cy.get('#email').type('user@example.com');
cy.get('#password').type('secret123');

// Специальные символы
cy.get('#search').type('React{enter}'); // нажать Enter
cy.get('#field').type('{selectall}{backspace}'); // выделить всё и удалить

Cypress поддерживает специальные клавиши в фигурных скобках: {enter}, {tab}, {backspace}, {esc}, {selectall} и другие.

cy.clear()

Очищает содержимое поля ввода:

cy.get('#search').clear();
cy.get('#search').clear().type('новый текст');

cy.should()

Команда cy.should() используется для утверждений (assertions). Cypress использует синтаксис Chai:

cy.get('[data-cy="title"]').should('be.visible');
cy.get('[data-cy="error"]').should('contain', 'Неверный пароль');
cy.get('button').should('be.disabled');
cy.get('[data-cy="count"]').should('have.text', '5');
cy.url().should('include', '/dashboard');

cy.wait()

Хотя Cypress автоматически ждёт большинства событий, иногда нужно явно дождаться запроса или заданного времени:

cy.wait('@apiRequest'); // ждать перехваченный запрос (см. cy.intercept)
cy.wait(1000);          // ждать 1 секунду (использовать с осторожностью)

Cypress Component Testing vs E2E Testing

Cypress поддерживает два режима тестирования, которые решают разные задачи.

E2E Testing

E2E тестирование запускает реальный браузер и тестирует приложение в целом — с реальным сервером, роутингом и API. Тест открывает страницу и взаимодействует с ней так, как это делал бы пользователь:

// cypress/e2e/login.cy.ts
describe('Страница входа', () => {
  it('должна позволять авторизоваться', () => {
    cy.visit('/login');
    cy.get('#email').type('admin@example.com');
    cy.get('#password').type('password');
    cy.get('[data-cy="login-btn"]').click();
    cy.url().should('include', '/dashboard');
  });
});

Когда использовать: для критических пользовательских сценариев (авторизация, оформление заказа, навигация по приложению).

Component Testing

Component Testing позволяет монтировать React-компоненты в изоляции, без запуска всего приложения. Это быстрее и удобнее для тестирования отдельных компонентов:

// cypress/component/Button.cy.tsx
import Button from './Button';

describe('Компонент Button', () => {
  it('вызывает onClick при клике', () => {
    const onClick = cy.stub().as('clickHandler');
    cy.mount(<Button onClick={onClick}>Нажми меня</Button>);
    cy.get('button').click();
    cy.get('@clickHandler').should('have.been.calledOnce');
  });
});

Когда использовать: для тестирования логики и состояний отдельных компонентов, особенно с разными наборами пропсов.

Сравнение

ХарактеристикаE2E TestingComponent Testing
СкоростьМедленнееБыстрее
ИзоляцияНет (реальное приложение)Да
Сложность настройкиТребует запущенного сервераНет
ОхватВесь пользовательский путьОтдельный компонент

Тестирование форм

Формы — один из наиболее распространённых объектов E2E тестирования. Рассмотрим пример тестирования формы регистрации:

// cypress/e2e/registration.cy.ts
describe('Форма регистрации', () => {
  beforeEach(() => {
    cy.visit('/register');
  });

  it('должна отображать ошибки при отправке пустой формы', () => {
    cy.get('[data-cy="register-form"]').submit();
    cy.get('[data-cy="email-error"]').should('contain', 'Введите email');
    cy.get('[data-cy="password-error"]').should('contain', 'Введите пароль');
  });

  it('должна успешно зарегистрировать пользователя', () => {
    cy.get('#name').type('Иван Петров');
    cy.get('#email').type('ivan@example.com');
    cy.get('#password').type('SecurePass123');
    cy.get('#confirm-password').type('SecurePass123');
    cy.get('[data-cy="submit-btn"]').click();

    // Проверяем редирект после успешной регистрации
    cy.url().should('include', '/welcome');
    cy.contains('Добро пожаловать, Иван!').should('be.visible');
  });

  it('должна блокировать кнопку отправки при несовпадении паролей', () => {
    cy.get('#password').type('password1');
    cy.get('#confirm-password').type('password2');
    cy.get('[data-cy="submit-btn"]').should('be.disabled');
  });
});

Работа с выпадающими списками и чекбоксами

// Выбор из <select>
cy.get('select[name="country"]').select('Russia');

// Чекбокс
cy.get('[data-cy="agree-checkbox"]').check();
cy.get('[data-cy="agree-checkbox"]').should('be.checked');

// Radio button
cy.get('[data-cy="role-admin"]').check();

Тестирование навигации

Cypress позволяет проверять маршрутизацию React Router или других роутеров:

// cypress/e2e/navigation.cy.ts
describe('Навигация приложения', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('должна переходить на страницу "О нас" по клику на ссылку', () => {
    cy.get('[data-cy="nav-about"]').click();
    cy.url().should('include', '/about');
    cy.get('h1').should('contain', 'О нас');
  });

  it('должна перенаправлять неавторизованного пользователя на страницу входа', () => {
    cy.visit('/profile');
    cy.url().should('include', '/login');
  });

  it('должна сохранять историю браузера', () => {
    cy.visit('/about');
    cy.visit('/contact');
    cy.go('back');
    cy.url().should('include', '/about');
  });
});

Тестирование API вызовов с cy.intercept()

Команда cy.intercept() позволяет перехватывать HTTP-запросы, что необходимо для:

  • мокирования ответов API (без реального бекенда);
  • проверки отправляемых данных;
  • тестирования состояний загрузки и ошибок.

Базовое мокирование

describe('Список пользователей', () => {
  it('должен отобразить список пользователей из API', () => {
    // Перехватываем GET запрос и возвращаем фиктивные данные
    cy.intercept('GET', '/api/users', {
      statusCode: 200,
      body: [
        { id: 1, name: 'Анна Смирнова', email: 'anna@example.com' },
        { id: 2, name: 'Борис Иванов', email: 'boris@example.com' },
      ],
    }).as('getUsers'); // псевдоним для ожидания

    cy.visit('/users');
    cy.wait('@getUsers'); // ждём завершения запроса

    cy.get('[data-cy="user-item"]').should('have.length', 2);
    cy.contains('Анна Смирнова').should('be.visible');
  });
});

Использование фикстур

Фикстуры — это JSON-файлы с тестовыми данными, которые хранятся в cypress/fixtures/:

// cypress/fixtures/users.json
[
  { "id": 1, "name": "Анна Смирнова", "email": "anna@example.com" },
  { "id": 2, "name": "Борис Иванов", "email": "boris@example.com" }
]
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');

Тестирование состояния ошибки

it('должен отображать сообщение об ошибке при сбое API', () => {
  cy.intercept('GET', '/api/users', {
    statusCode: 500,
    body: { message: 'Внутренняя ошибка сервера' },
  }).as('getUsersError');

  cy.visit('/users');
  cy.wait('@getUsersError');

  cy.get('[data-cy="error-message"]').should('be.visible');
  cy.contains('Не удалось загрузить пользователей').should('be.visible');
});

Проверка тела запроса

it('должен отправить корректные данные при создании пользователя', () => {
  cy.intercept('POST', '/api/users', (req) => {
    // Проверяем тело запроса
    expect(req.body).to.have.property('name', 'Новый Пользователь');
    expect(req.body).to.have.property('email', 'new@example.com');
    req.reply({ statusCode: 201, body: { id: 3, ...req.body } });
  }).as('createUser');

  cy.visit('/users/new');
  cy.get('#name').type('Новый Пользователь');
  cy.get('#email').type('new@example.com');
  cy.get('[data-cy="submit-btn"]').click();
  cy.wait('@createUser');
});

Тестирование состояния загрузки

it('должен отображать спиннер во время загрузки', () => {
  cy.intercept('GET', '/api/data', (req) => {
    // Задержка ответа для проверки состояния загрузки
    req.reply((res) => {
      res.delay = 1000;
      res.body = { items: [] };
    });
  }).as('slowRequest');

  cy.visit('/dashboard');
  cy.get('[data-cy="loading-spinner"]').should('be.visible');
  cy.wait('@slowRequest');
  cy.get('[data-cy="loading-spinner"]').should('not.exist');
});

Кастомные команды

Cypress позволяет создавать собственные команды для переиспользования часто повторяющейся логики. Команды определяются в cypress/support/commands.ts:

// cypress/support/commands.ts

// Команда для авторизации
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.visit('/login');
  cy.get('#email').type(email);
  cy.get('#password').type(password);
  cy.get('[data-cy="login-btn"]').click();
  cy.url().should('include', '/dashboard');
});

// Команда для авторизации через API (быстрее, чем через UI)
Cypress.Commands.add('loginByApi', (email: string, password: string) => {
  cy.request('POST', '/api/auth/login', { email, password }).then((response) => {
    window.localStorage.setItem('token', response.body.token);
  });
});

Использование кастомных команд в тестах:

describe('Личный кабинет', () => {
  beforeEach(() => {
    cy.login('admin@example.com', 'password');
  });

  it('должен отображать данные профиля', () => {
    cy.visit('/profile');
    cy.get('[data-cy="user-name"]').should('be.visible');
  });
});

Запуск тестов в headless режиме и CI/CD

Headless режим

Для запуска тестов без открытия браузера (например, в CI) используйте команду:

npx cypress run

По умолчанию Cypress запускает тесты в браузере Electron. Для указания конкретного браузера:

npx cypress run --browser chrome
npx cypress run --browser firefox

Запуск отдельного файла или группы тестов:

npx cypress run --spec "cypress/e2e/login.cy.ts"
npx cypress run --spec "cypress/e2e/**/*.cy.ts"

Интеграция с GitHub Actions

# .github/workflows/e2e.yml
name: E2E Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Start server
        run: npm start &

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

      - name: Run Cypress tests
        uses: cypress-io/github-action@v6
        with:
          browser: chrome
          headed: false

Официальный GitHub Action от Cypress

Cypress предоставляет официальный GitHub Action, который упрощает интеграцию:

- name: Cypress run
  uses: cypress-io/github-action@v6
  with:
    start: npm start          # команда запуска сервера
    wait-on: 'http://localhost:3000' # ждать запуска сервера
    browser: chrome

Action автоматически устанавливает зависимости, кеширует бинарники Cypress и запускает тесты.

Сохранение артефактов

При падении тестов Cypress автоматически сохраняет скриншоты и видео. В CI их можно сохранить как артефакты:

- name: Upload screenshots
  uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: cypress-screenshots
    path: cypress/screenshots

- name: Upload videos
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: cypress-videos
    path: cypress/videos

Параллельный запуск

Для ускорения можно запускать тесты параллельно через Cypress Cloud (ранее Cypress Dashboard):

- name: Cypress run parallel
  uses: cypress-io/github-action@v6
  with:
    record: true
    parallel: true
    group: 'E2E Tests'
  env:
    CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

Лучшие практики

Используйте data-атрибуты для селекторов

Вместо CSS-классов или ID рекомендуется использовать специальные data-cy атрибуты:

// ✅ Правильно
<button data-cy="submit-btn">Отправить</button>

// ❌ Избегайте
<button className="btn-primary">Отправить</button>

Это делает тесты независимыми от изменений стилей.

Избегайте явных задержек

// ❌ Плохо
cy.wait(2000);

// ✅ Хорошо — ждать конкретного события
cy.wait('@apiRequest');
cy.get('[data-cy="result"]').should('be.visible');

Изолируйте тесты

Каждый тест должен быть независимым. Используйте beforeEach для сброса состояния:

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

Переиспользуйте логику через кастомные команды

Выносите повторяющуюся логику (авторизация, заполнение форм) в кастомные команды Cypress.

Заключение

Cypress — это мощный и удобный инструмент для E2E тестирования React-приложений. Его основные преимущества — автоматическое ожидание, встроенный отладчик, простое мокирование API через cy.intercept() и поддержка компонентного тестирования.

Начните с написания тестов для критических пользовательских сценариев: авторизации, оформления заказа, основной навигации. Используйте data-cy атрибуты для стабильных селекторов, кастомные команды для переиспользования логики, и CI/CD интеграцию для автоматического запуска тестов при каждом изменении кода.

Cypress значительно снижает риск регрессий и повышает уверенность команды в работоспособности приложения на каждом этапе разработки.

Стрелочка влевоДинамические стили в ReactБесконечная прокруткаСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

React тестирование с Vitest
Zustand: продвинутые паттерны, persist middleware и slicesUncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиReact с WebSocket — реалтайм в компонентахReact Native New Architecture: Fabric и JSIReact Native приложение с нуля до публикации в сторахПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыОшибки гидратации в React и способы их устраненияHOC в React: мастерство композиции компонентовАрхитектура больших React приложений: Feature-Sliced DesignuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptXState - конечные автоматыYup - валидация схемТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressБесконечная прокруткаCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
Valtio: атомарный state management в ReactuseTransition - плавные переходы между состояниямиuseState в React что это и как использоватьuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseDeferredValue — отложенное обновление состоянияuseFormStatus - отслеживание статуса отправки формыuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19React Stale Closures — устаревшие замыкания в хукахОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияflushSync — синхронные обновления состояния в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в ReactНовые возможности React 19
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий