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

16 июня 2026
Автор

Олег Марков

Введение

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

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

Что такое 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 Testing Component 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 значительно снижает риск регрессий и повышает уверенность команды в работоспособности приложения на каждом этапе разработки.

Стрелочка влевоДинамические стили в ReactCSSTransition - переходыСтрелочка вправо

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

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

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

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

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в 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 тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
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 ₽
Подробнее

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