Cypress — это инструмент для автоматизации тестирования веб-приложений на базе JavaScript. С помощью него frontend разработчики могут создавать unit тесты, а QA инженеры проводить e2e и интеграционное тестирование. Инструмент работает внутри браузера, и благодаря своим встроенным функциям решает многие проблемы, с которыми сталкиваются команды при тестировании современных веб-приложений: от ожидания элементов до имитации ответов бекенда.
Преимущества
На официальном сайте можно прочитать об основных функциях Cypress. Ниже представлены некоторые из них, которые выделяют его среди других инструментов:
-
Нет необходимости в установке дополнительных инструментов — установка происходит с помощью одной команды и не требует сложных конфигураций или дополнительных зависимостей: test runner, assertions, отчеты и другое уже встроены в Cypress.
-
Быстрое и надежные тесты — Cypress не использует Selenium или WebDriver, а работает напрямую внутри браузера (“встраивается” в браузер через iframe и JavaScript). Отсутствие сетевых задержек и мгновенный доступ к DOM сокращает время выполнения тестов.
-
Интерактивный Test Runner — фреймворк предоставляет удобный интерфейс для запуска тестов, где в реальном времени можно увидеть все, что происходит с приложением. Описание тестов (шаги, логи) выводятся слева, а само приложение справа.
-
Time Travel — инструмент делает снимки (snapshots) каждого шага. В результате вы можете передвигаться по прошедшим шагам и просматривать, как вело себя приложение в то или иное время.
-
Автоматические ожидания (auto-retry) — больше не нужно добавлять sleep и wait в тесты, ведь Cypress автоматически ждет появления элементов на странице перед выполнением команд или assertions.
-
Управление сетевым трафиком — Cypress предоставляет контроль над запросами: можно перехватывать или мокировать API-вызовы, изменять ответы сервера и тестировать разные сценарии без реальных HTTP-запросов;
-
Сообщество и документация — обширная и подробная официальная документация, которая содержит видео, примеры и API-справочник.
Когда выбрать Cypress?
Основное направление автоматизации — UI тестирование
Как упоминалось ранее, Cypress направлен на тестирование веб-приложений. Инструмент подойдет вам, если вы планируете автоматизировать UI тестирование. Он легко интегрируется с популярными фреймворками, такими как React, Angular, Vue и другими.
Инструмент предоставляет решения и для других видов тестирования (интеграционного, API тестирования и др.), однако по сравнению с другими специальными инструментами есть некоторые ограничения.
Требуется быстрое внедрение
Установка и конфигурация Cypress занимает минимальное количество времени (убедитесь сами в следующем разделе), а Cypress Studio поможет вам сгенерировать код для тестов за пару кликов. Даже если не использовать Cypress Studio, можно за 10 минут написать первый тест с помощью документации.
Необходима стабильность
Инструмент уменьшает количество flaky тестов благодаря архитектуре и встроенным возможностям (автоматическое ожидание, работа внутри браузера, изолированность тестов и другое).
Однако Cypress, как и любой другой инструмент, имеет ряд ограничений, которые необходимо учитывать при выборе инструмента автоматизации:
-
Отсутствуют возможности взаимодействовать с элементами за пределами DOM (мульти-вкладки/окна/iframe);
-
Нет поддержки Safari/старые IE/Yandex;
-
Не поддерживает мобильное тестирование (только эмуляция в браузере).
Для таких сценариев лучше рассмотреть другие инструменты, например, Playwright или Selenium.
Установка и запуск
Для начала работы с Cypress вам необходимо:
1.VS Code или другая среда разработки;
2.Установить Node.js (версии 18 и выше) и пакетный менеджер (устанавливается по умолчанию с Node.js);
3.Инициализировать проект (файл package.json в директории проекта):
mkdir cypress-project cd cypress-project npm init -y
4.Установить Cypress.
npm install cypress --save-dev
После установки запускаем инструмент с помощью команды:
npx cypress open
Вы увидите интерфейс с выбором типа тестирования. В данной статье мы будем создавать e2e сценарии.
После выбора типа тестирования, Cypress создаст структуру файлов по умолчанию. Она будет выглядеть следующим образом:
/cypress.config.js //Конфигурация Cypress /cypress/fixtures/example.json //Статичные тестовых данных /cypress/support/commands.js //Кастомные команды, которые будут использоваться в тесте /cypress/support/e2e.js //Поведения, которые будут выполняться до всех спецификаций /cypress/e2e/ //Спецификации. Данная директория будет добавлена автоматически после создания первой спеки.
Далее выбираем необходимый браузер и попадаем на страницу со списком спецификаций. Так как это первый запуск, файлы с тестами отсутствуют. Cypress предлагает на выбор две опции: сгенерировать примеры спек для ознакомления или создать шаблон спецификации.
Написание тестов
В рамках данной статьи мы сосредоточимся на структуре и логике тестов. Для поиска элементов на странице будут использоваться базовые CSS селекторы Больше о локаторах и поиске элементов можно узнать в статье Локаторы. Стратегии поиска веб-элементов. Тестовые сценарии были подобраны таким образом, чтобы продемонстрировать разные возможности Cypress на практике:
-
Авторизация с валидными тестовыми данными — основные команды, кастомная
команда, переменные окружения; -
Авторизация с некорректными данными — параметризованный тест, фикстуры;
-
Удаление номера — хуки, перехват и отправка api запросов;
-
Отображение ошибки при создании номера — заглушка и изменение существующего ответа.
Все тесты будут выполняться на сайте https://automationintesting.online/ (GitHub), поэтому первым делом добавим настройку базового url в конфигурационный файл cypress.config.js Cypress автоматически будет подставлять его при открытии страницы.
const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { baseUrl: 'https://automationintesting.online/', }, });
Авторизация с валидными тестовыми данными
Сценарий:
-
Зайти на сайт https://automationintesting.online/
-
Перейти на страницу авторизации администратора
-
Ввести валидные логин и пароль и нажать на кнопку «Login»
-
Проверить, что пользователь успешно залогинился и перешел на страницу https://automationintesting.online/admin/rooms
Создание тестового сценария
Создадим спецификацию e2e/login.cy.js и добавим следующий код:
describe('Авторизация на сайте', () => { //Организация тестов в логический блок. Описывает тестируемую функциональность it('Авторизация с валидными данными', () => { //Тест cy.visit('/'); //Отрыть страницу cy.contains('Admin').click(); // Найти элемент, содержащий текст "Admin", и кликнуть по нему cy.get('#username').type('admin'); //Найти элемент по css селекторы и ввести текст "admin" cy.get('#password').type('password'); cy.get('#doLogin').click(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); //Получить текущий ulr и сравнить с ожидаемым }) })
В тесте используются базовые команды Cypress и утверждения (assertions). О командах и утверждениях можно прочитать подробнее здесь и здесь.
Теперь запустим наш первый тест через UI интерфейс. Выбираем нужную спецификацию и нажимаем на нее:
Наш тест упал из-за ошибки uncaught:exception. Cypress считает необработанным исключением любую ошибку, которая не была перехвачена приложением, и проваливает тест.
Отключить такое поведение можно с помощью глобальной настройки в файле support/e2e.js:
import './commands' Cypress.on('uncaught:exception', (err, runnable) => { return false })
Перезапустим тест и увидим, что он прошел успешно.
Создание кастомной команды
Иногда нам может потребоваться выполнять одни и те же действия несколько раз. Например, ввод различных данных в форму авторизации или авторизоваться на сайте для выполнения других сценариев. Cypress позволяет создавать собственные команды. Вынесем повторяющие действия (первые три шага сценария) в файл support/commands.js:
Cypress.Commands.add('login', (email = 'admin', password = 'password') => { cy.visit('/'); cy.contains('Admin').click(); if (email) cy.get('#username').type(email); if (password) cy.get('#password').type(password); cy.get('#doLogin').click(); });
Функция принимает два аргумента: email и password. Если аргументы не переданы, будут подставляться дефолтные значения. В случае передачи пустой строки, ничего не произойдет (Cypress не разрешает передавать в метод type() пустую строку, поэтому используем if для проверки)
Тест с кастомной командой будет выглядеть следующий образом:
describe('Авторизация на сайте', () => { //Организация тестов в логический блок. Описывает тестируемую функциональность it('Авторизация с валидными данными', () => { //Тест cy.visit('/'); //Отрыть страницу cy.contains('Admin').click(); // Найти элемент, содержащий текст "Admin", и кликнуть по нему cy.get('#username').type('admin'); //Найти элемент по css селекторы и ввести текст "admin" cy.get('#password').type('password'); cy.get('#doLogin').click(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); //Получить текущий ulr и сравнить с ожидаемым }) //С кастомной командой it('Авторизация с валидными данными', { tags: 'auth' }, () => { cy.login(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); }) })
Сокрытие чувствительных данных
Обратим внимание, что все это время чувствительные данные хранятся в коде. Создадим файл cypress.env.json и пропишем в нем учетные данные:
{ "admin_user": "admin", "admin_password": "password" }
Важно: не забудьте добавить файл в .gitignore.
Воспользуемся методом Cypress.env() для получения доступа к переменными окружениям и обновим кастомную команду:
Cypress.Commands.add('login', (email = Cypress.env('admin_user'), password = Cypress.env('admin_password')) => { //Получаем переменные из файла cy.visit('/'); cy.contains('Admin').click(); if (email) cy.get('#username').type(email); if (password) cy.get('#password').type(password, { log: false }); //Опция log: false скрывает данные в логах Cypress cy.get('#doLogin').click(); });
Также добавим сокрытие данных для поля password. При запуске тестов не будет видно, какое значение было введено в это поле.
Авторизация с некорректными данными
Довольно часто возникает ситуация, когда необходимо запускать тест с разными наборами данных. В Cypress нет встроенной поддержки параметризованных тестов, но такое поведение можно реализовать с помощью массива/фикстуры и метода forEach().
Фикстуры — это файлы, содержащие статические данные. По умолчанию Cypress ищет их в директории fixtures/.
Давайте автоматизируем следующие сценарии:
-
Зайти на сайт https://automationintesting.online/
-
Перейти на страницу авторизации администратора
-
Ввести некорректные данные и нажать на кнопку «Login»:
email: », password: ‘test’
email: ‘user’, password: »
email: ‘admi’, password: ‘password’
email: ‘admin’, password: ‘test’ -
Проверить, что появилась ошибка «Invalid credentials» и пользователь остался на странице авторизации
Создадим json объект в fixtures/invalidLoginData.json:
[ { "title": "Обязательность поля email", "email": "", "password": "test" }, { "title": "Обязательность поля password", "email": "user", "password": "" }, { "title": "Неверный email", "email": "admi" }, { "title": "Неверный пароль", "password": "test" } ]
Импортируем фикстуру в спецификацию e2e/login.cy.js и с помощью цикла создадим тестовые сценарии:
describe('Авторизация на сайте', () => { //Организация тестов в логический блок. Описывает тестируюмую функциональность const invalidLoginData = require('../fixtures/invalidLoginData.json'); //Импортируем фикстуру it('Авторизация с валидными данными', () => { //Тест cy.visit('/'); //Отрыть страницу cy.contains('Admin').click(); // Найти элемент, содержащий текст "Admin", и кликнуть по нему cy.get('#username').type('admin'); //Найти элемент по css селекторы и ввести текст "admin" cy.get('#password').type('password'); cy.get('#doLogin').click(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); //Получить текущий ulr и сравнить с ожидаемым }) //С кастомной командой it('Авторизация с валидными данными', { tags: 'auth' }, () => { cy.login(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); }) //Параметризованный тест invalidLoginData.forEach(data => { //Перебираем объекты массива it(data.title, () => { cy.login(data.email, data.password); cy.get('[role="alert"]').should('be.visible').and('have.text', 'Invalid credentials'); cy.url().should('eq', Cypress.config('baseUrl') + 'admin'); }) }); })
Перезапустим спецификацию и увидим, что было запущено четыре теста с разными данными.
Удаление номера
Предусловие:
-
Пользователь авторизован с ролью модератор на сайте https://automationintesting.online/ и находится на странице со списком номеров;
-
Номер создан на сайте.
Сценарий:
-
Нажать на кнопку «Удалить» у соответствующего номера;
-
Проверить, что строка с номером не отображается на странице.
Так как это новая функциональность создадим спеку e2e/rooms.cy.js
Хуки
С помощью хуков можно выполнять код до или после тестов. Их удобно использовать для создания предусловий (настройка окружения, подготовка данных, авторизация и другого). Cypress предоставляет четыре метода:
-
before() — выполняется один раз перед всеми тестами в блоке;
-
beforeEach() — выполняется перед каждым тестом;
-
afterEach() — выполняется после каждого теста;
-
after() — выполняется один раз после всех тестов.
Авторизуем пользователя не в теле теста, а до теста с помощью beforeEach():
describe('Действия с номерами', () => { beforeEach(() => { cy.login(); //Авторизация с дефолтными значениями перед каждым тестом }) })
Однако подготавливать предусловия для тестов или сьютов следует с помощью API/БД, а не UI. Это связано с тем, что
а) запросы реже изменяются и выполняются быстрее
б) некоторые предусловия нельзя воссоздать через UI
Авторизуем пользователя и добавим номер на сайт с помощью api. Для работы с запросами у Cypress есть метод cy.request(). При необходимости можно распространить авторизацию на весь проект (вынести метод в файл support\e2e.js)
Создание предусловий с помощью api
Итак, добавим кастомную команду, которая будет возвращать токен пользователя:
Cypress.Commands.add('loginRequest', (email = Cypress.env('admin_user'), password = Cypress.env('admin_password')) => { return cy.request('POST','api/auth/login',{ username: email, password: password }).then(response => { //Отправка POST запроса по url с телом expect(response.status).to.eq(200); //Проверяем статус ответа return response.body.token; //Возвращаем значение поля token из тела ответа }) });
Нам не обязательно получать токен каждый раз перед тестом, достаточно получить его один раз и использовать в дальнейшем. В файле e2e/rooms.cy.js опишем before(), который будет получать токен и создавать номер:
describe('Действия с номерами', () => { before(() => { cy.loginRequest().as('token').then(token => { //Получение токена cy.fixture('rooms').then(room => { cy.request({ //Отправка запроса для создания номера в отеле method: 'POST', url: 'api/room', body: room.deleteRoom, headers: { Cookie: 'token=' + token } }).then(response => { expect(response.status).to.eq(200); }) }) }); }) })
Данные для создания номера в fixtures/rooms.json:
{ "deleteRoom": { "roomName": "cy_test_delete", "type": "Single", "accessible": false, "roomPrice": "140", "features": [] } }
Итак, мы получили значение токена, но не используем его. На данном сайте токен аутентификации храниться в Cookie файлах, следовательно, нам необходимо перед каждым тестом добавлять его. Это можно реализовать с помощью beforeEach() и cy.setCookie():
beforeEach(function () { //Используем function() вместо стрелочной функции из-за передачи контекста (this) cy.setCookie('token', this.token); //Добавляем куки cy.visit('admin/rooms'); //Открываем страницу })
Ожидание загрузки данных
Когда пользователь откроет страницу, данные о номерах могут не подгрузиться сразу. Для предотвращения такой ситуации можно отследить запрос и после получения ответа продолжить выполнения теста.
Добавим перехватчик запроса перед открытием страницы в хук beforeEach() и в тесте будем ожидать ответа с помощью cy.wait()
describe('Действия с номерами', () => { before(() => { cy.loginRequest().as('token').then(token => { //Получение токена cy.fixture('rooms').then(room => { cy.request({ //Отправка запроса для создания номера в отеле method: 'POST', url: 'api/room', body: room.deleteRoom, headers: { Cookie: 'token=' + token } }).then(response => { expect(response.status).to.eq(200); }) }) }); }) beforeEach(function () { cy.setCookie('token', this.token); //Добавляем куки cy.intercept('GET', 'api/room').as('getRooms') //Отслеживание запроса cy.visit('admin/rooms'); //Открываем страницу }) it('Удаление номера', () => { cy.wait('@getRooms').its('response.statusCode').should('eq', 200).then(() => { //Ожидаем статус код 200 cy.fixture('rooms').then(room => { let roomRow = cy.get('#roomName' + room.deleteRoom.roomName).parents('[data-testid="roomlisting"]'); //Находим строку с номером roomRow.find('.roomDelete').click(); //Внутри строки ищем элемент с классом roomDelete и нажимаем roomRow.should('not.exist'); //Проверяем, что строка с номером отсутствует }) }) }) })
Отображение ошибки при создании номера
Представим ситуацию, что нам необходимо проверить поведение UI на разные данных. Однако по каким-то причинам это сделать нельзя (api еще не готов, данные зависят от внешнего сервера и др.) Для решения данной проблемы нам поможет команда cy.intercept()
Создание заглушки
Например, при создании комнаты мы хотим получать ошибку с текстом «Подложили ответ с помощью Cypress». Создадим тест в room.cy.js и добавим следующий код:
it('Отображение ошибки', () => { cy.intercept('POST', '/api/room', { statusCode: 400, body: { "errors": ["Подложили ответ с помощью Cypress"] } }).as('postRoom') cy.contains('Create').click() cy.get('.alert-danger').should('have.text', 'Подложили ответ с помощью Cypress') })
Данный код будет перехватывает запрос с помощью cy.intercept(method, url, staticResponse) и возвращать данные, указанные в staticResponse.

Изменение ответа
Представим ситуацию, что нам приходит большой объект в ответе, а изменить необходимо только 1-2 параметра. В таком случае мы можем воспользоваться методом req.continue(), который позволит изменить реальный ответ:
it('Изменение существующего ответа ', () => { cy.intercept('POST', 'api/room', (req) => { req.continue(res => { res.body.errors[0] = "Описание первой ошибки изменено в ответе"; res.send({ statusCode: res.statusCode, headers: { ...res.headers }, body: res.body }) }) }).as('postRoom') cy.contains('Create').click() cy.get('.alert-danger').children('p').first().should('have.text', 'Описание первой ошибки изменено в ответе') })
Cypress Studio
Cypress Studio позволяет записывать действия в приложении. Она автоматически генерирует код теста при взаимодействии с DOM элементами. Инструмент поддерживает только часть команд (.click(), .type(), .check(), .uncheck(), .select()) и генерацию определенных утверждений (assertions).
Сгенерируем код для сценария «авторизация с валидными тестовыми данными»: добавим параметр experimentalStudio: true в cypress.config.js -> запустим Studio -> повторим действия из сценария в UI и вуаля, тест готов:
describe('template spec', () => { it('passes', () => { cy.visit('https://example.cypress.io') }) /* ==== Test Created with Cypress Studio ==== */ it('studio', function() { /* ==== Generated with Cypress Studio ==== */ cy.visit('https://automationintesting.online/'); cy.get(':nth-child(6) > .nav-link').click(); cy.get('#username').clear('a'); cy.get('#username').type('admin'); cy.get('#password').clear('p'); cy.get('#password').type('password'); cy.get('#doLogin').click(); /* ==== End Cypress Studio ==== */ }); })
Запуск тестов по тегам
Иногда мы не хотим прогонять весь блок функционала, а выполнить лишь некоторые тестовые сценарии. Для решения данной проблемы можно установить плагин cypress/grep. Он позволяет запускать тесты не только по соответствию названию тестового сценария, но и по тегам.
Устанавливаем плагин
npm i -D @cypress/grep
Загружаем и регистрируем модуль в support/e2e.js
import './commands' const registerCypressGrep = require('@cypress/grep') registerCypressGrep() Cypress.on('uncaught:exception', (err, runnable) => { return false })
Добавляем настройки в cypress.config.js:
const { defineConfig } = require("cypress"); module.exports = defineConfig({ e2e: { baseUrl: 'https://automationintesting.online/', experimentalStudio: true, setupNodeEvents(on, config) { require('@cypress/grep/src/plugin')(config); return config; }, env: { grepFilterSpecs: true, grepOmitFiltered: true } }, });
Теперь осталось добавить теги на тесты. Пример:
it('Авторизация с валидными данными', { tags: 'auth' }, () => { cy.login(); cy.url().should('eq', Cypress.config('baseUrl') + 'admin/rooms'); })
Запускаем тесты командой
npx cypress run --env grepTags=auth
Как видите, был запущен один тест в спецификации login.cy.js
ссылка на оригинал статьи https://habr.com/ru/articles/909568/
Добавить комментарий