TL;DR

  • Cypress работает внутри браузера — тесты быстрые и надёжные без WebDriver
  • Установка: npm install cypress --save-dev, запуск: npx cypress open
  • Используй data-* атрибуты для селекторов — они переживают изменения UI

Подойдёт: Начинающим в автоматизации, командам с JavaScript веб-приложениями Не подойдёт: Если нужно тестирование мобильных приложений или Safari из коробки

Помню свою первую неделю с автоматизацией браузерных тестов на Selenium. Конфигурационные файлы, скачивание драйверов, непонятные ошибки про session ID. Я тратил больше времени на отладку настроек, чем на написание тестов. Когда я открыл для себя Cypress, всё изменилось. Рабочий тест был готов за 15 минут.

Cypress стал основным выбором для тестирования современных веб-приложений. Он работает прямо в браузере, даёт мгновенную обратную связь и требует минимальной конфигурации. Это руководство проведёт тебя от установки до запуска тестов в CI/CD пайплайнах.

Почему Cypress?

Прежде чем писать код, давай разберёмся, чем Cypress отличается от традиционных инструментов.

Cypress vs традиционные инструменты:

ХарактеристикаCypressSelenium/WebDriver
АрхитектураРаботает внутри браузераВнешний процесс драйвера
Время настройкиМинутыЧасы (драйверы, конфиги)
ОтладкаTime-travel, снапшотыСкриншоты, логи
СкоростьБыстрая (нет сетевых запросов)Медленнее (HTTP команды)
НестабильностьНизкая (автоматические ожидания)Выше (ручные ожидания)

Cypress отлично подходит для тестирования SPA на React, Vue, Angular и подобных фреймворках. Он обеспечивает live-перезагрузку, автоматические ожидания и отладку на уровне DevTools браузера.

Если ты строишь комплексную стратегию тестирования, понимание пирамиды автоматизации тестов поможет определить место Cypress рядом с unit-тестами и API-тестами.

Установка Cypress

Требования

Нужен Node.js на машине. Любая версия от 18.x подойдёт.

# Проверка версии Node.js
node --version

# Должно вывести v18.x.x или выше

Шаги установки

1. Инициализация проекта (если его нет):

mkdir my-cypress-tests
cd my-cypress-tests
npm init -y

2. Установка Cypress:

npm install cypress --save-dev

Это скачает Cypress и его Electron-браузер. Первая установка занимает пару минут.

3. Запуск Cypress:

npx cypress open

Это открывает Cypress Test Runner — визуальный интерфейс для запуска тестов.

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

При первом открытии Cypress создаёт структуру папок:

cypress/
├── e2e/              # Твои тестовые файлы
├── fixtures/         # Тестовые данные (JSON)
├── support/          # Кастомные команды и настройка
│   ├── commands.js   # Кастомные команды
│   └── e2e.js        # Выполняется перед каждым файлом
└── downloads/        # Скачанные файлы во время тестов

Cypress также создаёт cypress.config.js в корне проекта — это главный конфигурационный файл.

Пишем первый тест

Напишем тест, который переходит на страницу и проверяет её содержимое. Создай файл cypress/e2e/first-test.cy.js:

describe('Мой первый тест', () => {
  it('посещает тестовую страницу', () => {
    cy.visit('https://example.cypress.io')
    cy.contains('type').click()
    cy.url().should('include', '/commands/actions')
    cy.get('.action-email')
      .type('test@example.com')
      .should('have.value', 'test@example.com')
  })
})

Разбор:

  • describe() группирует связанные тесты
  • it() определяет один тест-кейс
  • cy.visit() переходит по URL
  • cy.contains() ищет элемент по тексту
  • cy.get() ищет элемент по CSS-селектору
  • .type() вводит текст в поле
  • .should() делает проверку

Запусти этот тест, кликнув на first-test.cy.js в Test Runner.

Структура теста

Каждый тест Cypress следует паттерну:

describe('Название фичи', () => {
  beforeEach(() => {
    // Выполняется перед каждым тестом
    cy.visit('/login')
  })

  it('делает что-то конкретное', () => {
    // Arrange: подготовка условий
    // Act: выполнение действия
    // Assert: проверка результата
  })

  it('обрабатывает другой сценарий', () => {
    // Ещё один тест-кейс
  })
})

Хук beforeEach выполняется перед каждым тестом в блоке describe. Используй его для общей подготовки: логин, переход на страницу.

Селекторы: поиск элементов

Правильный поиск элементов критичен для надёжных тестов. Cypress поддерживает несколько стратегий селекторов.

Приоритет селекторов (от лучшего к худшему)

1. Data-атрибуты (рекомендуется):

cy.get('[data-test="submit-button"]')
cy.get('[data-cy="login-form"]')
cy.get('[data-testid="user-email"]')

Data-атрибуты существуют специально для тестирования. Они не меняются при изменении стилей.

2. ID-селекторы:

cy.get('#username')

ID стабильны, но не всегда доступны. Не добавляй ID только для тестов — используй data-атрибуты.

3. Текстовое содержимое:

cy.contains('Отправить')
cy.contains('button', 'Отправить')  // Более точный

Хорош для кнопок и ссылок. Ломается при изменении текста или локализации.

4. CSS-селекторы (избегай):

cy.get('.btn-primary')
cy.get('form input[type="email"]')

CSS-классы часто меняются. Сложные селекторы хрупкие.

Добавление тестовых атрибутов в приложение

Работай с командой разработки для добавления тестовых атрибутов:

<!-- До -->
<button class="btn btn-primary">Регистрация</button>

<!-- После -->
<button class="btn btn-primary" data-test="signup-button">Регистрация</button>

Атрибут data-test переживает рефакторинг, смену темы и обновления CSS.

Cypress Testing Library

Для лучших селекторов на основе доступности установи Testing Library:

npm install @testing-library/cypress --save-dev

Добавь в cypress/support/commands.js:

import '@testing-library/cypress/add-commands'

Теперь можно использовать доступные селекторы:

cy.findByRole('button', { name: 'Отправить' })
cy.findByLabelText('Email адрес')
cy.findByPlaceholderText('Введите email')

Эти селекторы соответствуют тому, как пользователи взаимодействуют с приложением.

Assertions и проверки

Cypress использует Chai assertions. Самые частые паттерны:

Should assertions

// Видимость
cy.get('[data-test="header"]').should('be.visible')
cy.get('[data-test="modal"]').should('not.exist')

// Содержимое
cy.get('[data-test="title"]').should('have.text', 'Добро пожаловать')
cy.get('[data-test="title"]').should('contain', 'Добро')

// Атрибуты
cy.get('input').should('have.value', 'test@example.com')
cy.get('a').should('have.attr', 'href', '/dashboard')

// Состояние
cy.get('button').should('be.disabled')
cy.get('input').should('be.enabled')
cy.get('checkbox').should('be.checked')

// Количество
cy.get('[data-test="list-item"]').should('have.length', 5)
cy.get('[data-test="list-item"]').should('have.length.gt', 3)

Цепочки assertions

cy.get('[data-test="user-card"]')
  .should('be.visible')
  .and('contain', 'Иван Петров')
  .and('have.class', 'active')

Expect для сложных проверок

cy.get('[data-test="product-list"]').then(($list) => {
  const itemCount = $list.find('.product').length
  expect(itemCount).to.be.greaterThan(0)
  expect(itemCount).to.be.lessThan(100)
})

Взаимодействие с элементами

Основные действия

// Клики
cy.get('button').click()
cy.get('button').dblclick()
cy.get('button').rightclick()

// Ввод текста
cy.get('input').type('Привет мир')
cy.get('input').type('Привет{enter}')  // Нажать Enter
cy.get('input').clear().type('Новый текст')

// Выбор из списка
cy.get('select').select('Опция 1')
cy.get('select').select(['Опция 1', 'Опция 2'])  // Мультивыбор

// Чекбоксы и радиокнопки
cy.get('[type="checkbox"]').check()
cy.get('[type="checkbox"]').uncheck()
cy.get('[type="radio"]').check()

// Загрузка файлов
cy.get('input[type="file"]').selectFile('cypress/fixtures/image.png')

// Скролл
cy.get('[data-test="footer"]').scrollIntoView()

Работа с формами

Типичный тест формы:

describe('Форма контактов', () => {
  beforeEach(() => {
    cy.visit('/contact')
  })

  it('успешно отправляется с валидными данными', () => {
    cy.get('[data-test="name"]').type('Анна Сидорова')
    cy.get('[data-test="email"]').type('anna@example.com')
    cy.get('[data-test="message"]').type('Привет от Cypress!')

    cy.get('[data-test="submit"]').click()

    cy.get('[data-test="success-message"]')
      .should('be.visible')
      .and('contain', 'Спасибо')
  })

  it('показывает ошибку при невалидном email', () => {
    cy.get('[data-test="email"]').type('invalid-email')
    cy.get('[data-test="submit"]').click()

    cy.get('[data-test="email-error"]')
      .should('be.visible')
      .and('contain', 'валидный email')
  })
})

Работа с API

Cypress может перехватывать и мокать API-вызовы. Это делает тесты быстрее и надёжнее.

Перехват запросов

cy.intercept('GET', '/api/users').as('getUsers')

cy.visit('/users')
cy.wait('@getUsers')

cy.get('[data-test="user-list"]').should('be.visible')

cy.wait('@getUsers') останавливается до завершения API-вызова. Никаких хаков с cy.wait(5000).

Мокирование API-ответов

cy.intercept('GET', '/api/products', {
  statusCode: 200,
  body: [
    { id: 1, name: 'Продукт A', price: 29.99 },
    { id: 2, name: 'Продукт B', price: 39.99 }
  ]
}).as('getProducts')

cy.visit('/products')
cy.wait('@getProducts')

cy.get('[data-test="product"]').should('have.length', 2)

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

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

cy.intercept('POST', '/api/checkout', {
  statusCode: 500,
  body: { error: 'Ошибка оплаты' }
}).as('checkoutError')

cy.get('[data-test="checkout-button"]').click()
cy.wait('@checkoutError')

cy.get('[data-test="error-message"]')
  .should('be.visible')
  .and('contain', 'Ошибка оплаты')

Для глубокого погружения в сетевые стабы читай Cypress Deep Dive: Архитектура, отладка и работа с сетью.

Конфигурация тестов

cypress.config.js

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    viewportWidth: 1280,
    viewportHeight: 720,
    defaultCommandTimeout: 10000,
    video: false,
    screenshotOnRunFailure: true,

    setupNodeEvents(on, config) {
      // Обработчики событий Node.js
    }
  }
})

Основные настройки:

НастройкаОписаниеПо умолчанию
baseUrlДобавляется к URL в cy.visit()нет
viewportWidth/HeightРазмеры браузера1000 × 660
defaultCommandTimeoutВремя повторных попыток4000мс
videoЗапись видеоtrue
retriesАвтоповтор упавших тестов0

Переменные окружения

// cypress.config.js
module.exports = defineConfig({
  e2e: {
    env: {
      apiUrl: 'http://localhost:4000',
      adminUser: 'admin@test.com'
    }
  }
})

Доступ в тестах:

cy.visit(Cypress.env('apiUrl') + '/login')
cy.get('input').type(Cypress.env('adminUser'))

Или через командную строку:

npx cypress run --env apiUrl=http://staging.example.com

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

Создавай переиспользуемые команды в cypress/support/commands.js:

Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login')
  cy.get('[data-test="email"]').type(email)
  cy.get('[data-test="password"]').type(password)
  cy.get('[data-test="submit"]').click()
  cy.url().should('include', '/dashboard')
})

Cypress.Commands.add('logout', () => {
  cy.get('[data-test="user-menu"]').click()
  cy.get('[data-test="logout"]').click()
})

Использование в тестах:

describe('Дашборд', () => {
  beforeEach(() => {
    cy.login('user@example.com', 'password123')
  })

  it('показывает профиль пользователя', () => {
    cy.get('[data-test="profile"]').should('be.visible')
  })
})

Программный логин

Для ускорения тестов пропускай UI-логин:

Cypress.Commands.add('loginByApi', (email, password) => {
  cy.request({
    method: 'POST',
    url: '/api/auth/login',
    body: { email, password }
  }).then((response) => {
    window.localStorage.setItem('authToken', response.body.token)
  })
})

Это намного быстрее, чем заполнять формы каждый раз.

Организация тестов

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

cypress/
├── e2e/
│   ├── auth/
│   │   ├── login.cy.js
│   │   └── registration.cy.js
│   ├── products/
│   │   ├── listing.cy.js
│   │   └── checkout.cy.js
│   └── smoke/
│       └── critical-paths.cy.js
├── fixtures/
│   ├── users.json
│   └── products.json
└── support/
    ├── commands.js
    └── e2e.js

Использование fixtures

Храни тестовые данные в cypress/fixtures/:

// cypress/fixtures/users.json
{
  "admin": {
    "email": "admin@example.com",
    "password": "admin123"
  },
  "regular": {
    "email": "user@example.com",
    "password": "user123"
  }
}

Загрузка в тестах:

cy.fixture('users').then((users) => {
  cy.login(users.admin.email, users.admin.password)
})

// Или напрямую в intercept
cy.intercept('GET', '/api/users', { fixture: 'users.json' })

Запуск тестов в CI/CD

Выполнение из командной строки

# Запуск всех тестов
npx cypress run

# Запуск конкретного файла
npx cypress run --spec "cypress/e2e/auth/*.cy.js"

# Запуск в конкретном браузере
npx cypress run --browser chrome

# Запуск с переменными окружения
npx cypress run --env apiUrl=http://staging.example.com

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

Создай .github/workflows/cypress.yml:

name: Cypress Tests

on: [push, pull_request]

jobs:
  cypress:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Start application
        run: npm start &

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

      - name: Run Cypress tests
        uses: cypress-io/github-action@v6
        with:
          wait-on: 'http://localhost:3000'
          browser: chrome

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

Больше паттернов CI/CD в статье GitHub Actions для QA-автоматизации.

Параллельное выполнение

Для больших тест-сьютов запускай тесты параллельно:

jobs:
  cypress:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        containers: [1, 2, 3]

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

Для этого нужен Cypress Cloud (есть бесплатный тариф).

Отладка упавших тестов

Time Travel

Кликни на любую команду в Test Runner, чтобы увидеть состояние DOM в тот момент. Это невероятно полезно для понимания, почему селектор не сработал.

Скриншоты и видео

// Сделать скриншот вручную
cy.screenshot('before-submit')

// Скриншоты автоматические при падении
// Видео записывает весь прогон теста

Настройка в cypress.config.js:

module.exports = defineConfig({
  e2e: {
    screenshotOnRunFailure: true,
    video: true,
    videosFolder: 'cypress/videos',
    screenshotsFolder: 'cypress/screenshots'
  }
})

Команда debug

cy.get('[data-test="menu"]').debug()

Это останавливает выполнение и выводит элемент в консоль.

Логирование в консоль

cy.get('[data-test="items"]').then(($items) => {
  console.log('Количество элементов:', $items.length)
  console.log('Первый элемент:', $items.first().text())
})

Открой DevTools браузера, чтобы увидеть вывод.

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

Делай так

  • Используй data-test атрибуты для селекторов
  • Жди API-вызовы вместо произвольных таймаутов
  • Держи тесты независимыми (каждый может запускаться отдельно)
  • Используй beforeEach для общей подготовки
  • Тестируй одну вещь в одном тест-кейсе
  • Мокируй внешние сервисы

Не делай так

  • Не используй cy.wait(5000) — жди конкретных событий
  • Не передавай состояние между тестами
  • Не тестируй детали реализации (CSS-классы, HTML-структуру)
  • Не злоупотребляй моками — некоторые тесты должны идти на реальный API
  • Не пиши огромные тесты — держи их фокусированными

Борьба с нестабильными тестами

// Плохо - зависит от времени
cy.wait(3000)
cy.get('[data-test="results"]').should('exist')

// Хорошо - ждёт конкретное условие
cy.intercept('GET', '/api/search*').as('search')
cy.get('[data-test="search"]').type('query')
cy.wait('@search')
cy.get('[data-test="results"]').should('exist')

Что дальше?

Когда освоишь основы, изучи продвинутые темы:

  • Компонентное тестирование: тесты React/Vue компонентов в изоляции
  • Визуальная регрессия: ловля неожиданных изменений UI
  • Мониторинг производительности: отслеживание времени загрузки
  • Тестирование доступности: приложение работает для всех

Для продвинутых паттернов Cypress включая глубокий разбор архитектуры и мастерство сетевых стабов читай Cypress Deep Dive. Если оцениваешь альтернативы, Полное руководство по Playwright покрывает конкурирующий фреймворк от Microsoft.

FAQ

Cypress бесплатный?

Да, Cypress — open-source и полностью бесплатный для локальной разработки и CI/CD. Cypress Cloud (бывший Dashboard) предлагает бесплатный тариф с ограниченным количеством записей тестов. Платные тарифы дают больше параллельных запусков, длинную историю и продвинутую аналитику. Большинство команд начинают бесплатно и апгрейдятся по мере роста тест-сьюта.

Нужно ли знать JavaScript для работы с Cypress?

Базовые знания JavaScript помогут, но не нужно быть экспертом. Синтаксис Cypress спроектирован читаемым — cy.get('button').click() понятен даже без опыта в JavaScript. Начни с простых тестов и изучай паттерны JavaScript (промисы, стрелочные функции, деструктуризация) по мере необходимости. Многие QA-инженеры изучают JavaScript через Cypress.

Может ли Cypress тестировать любой сайт?

Cypress тестирует веб-приложения, которые работают в браузере. Он работает с любым фронтенд-фреймворком: React, Vue, Angular, чистый JavaScript или серверный рендеринг. Однако Cypress не может тестировать нативные мобильные приложения (iOS/Android), десктопные приложения или Electron-приложения в продакшн-сборках. Для мобильного тестирования нужны инструменты вроде Appium. У Cypress также есть ограничения с cross-origin iframe и несколькими вкладками браузера.

Сколько времени нужно на изучение Cypress?

Первый тест можно написать и запустить в течение часа после установки. Базовое владение — написание надёжных тестов, правильное использование селекторов, работа с формами — приходит через 2-4 недели регулярной практики. Продвинутые навыки вроде кастомных команд, моков API и интеграции с CI/CD занимают больше времени. Большинство инженеров чувствуют уверенность после автоматизации первой реальной фичи от начала до конца.

Смотрите также

Официальные ресурсы