Автоматизация UI тестов на Cypress

от автора

Cypress — это инструмент для автоматизации тестирования веб-приложений на базе JavaScript. С помощью него frontend разработчики могут создавать unit тесты, а QA инженеры проводить e2e и интеграционное тестирование. Инструмент работает внутри браузера, и благодаря своим встроенным функциям решает многие проблемы, с которыми сталкиваются команды при тестировании современных веб-приложений: от ожидания элементов до имитации ответов бекенда.

Преимущества

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

  1. Нет необходимости в установке дополнительных инструментов — установка происходит с помощью одной команды и не требует сложных конфигураций или дополнительных зависимостей: test runner, assertions, отчеты и другое уже встроены в Cypress.

  2. Быстрое и надежные тесты — Cypress не использует Selenium или WebDriver, а работает напрямую внутри браузера (“встраивается” в браузер через iframe и JavaScript). Отсутствие сетевых задержек и мгновенный доступ к DOM сокращает время выполнения тестов.

  3. Интерактивный Test Runner — фреймворк предоставляет удобный интерфейс для запуска тестов, где в реальном времени можно увидеть все, что происходит с приложением. Описание тестов (шаги, логи) выводятся слева, а само приложение справа.

  4. Time Travel — инструмент делает снимки (snapshots) каждого шага. В результате вы можете передвигаться по прошедшим шагам и просматривать, как вело себя приложение в то или иное время.

  5. Автоматические ожидания (auto-retry) — больше не нужно добавлять sleep и wait в тесты, ведь Cypress автоматически ждет появления элементов на странице перед выполнением команд или assertions.

  6. Управление сетевым трафиком — Cypress предоставляет контроль над запросами: можно перехватывать или мокировать API-вызовы, изменять ответы сервера и тестировать разные сценарии без реальных HTTP-запросов;

  7. Сообщество и документация — обширная и подробная официальная документация, которая содержит видео, примеры и 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 сценарии.

 UI Cypress

UI Cypress

После выбора типа тестирования, 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/',   }, });

Авторизация с валидными тестовыми данными

Сценарий:

  1. Зайти на сайт https://automationintesting.online/

  2. Перейти на страницу авторизации администратора

  3. Ввести валидные логин и пароль и нажать на кнопку «Login»

  4. Проверить, что пользователь успешно залогинился и перешел на страницу 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. При запуске тестов не будет видно, какое значение было введено в это поле.

Сокрытие password

Сокрытие password

Авторизация с некорректными данными

Довольно часто возникает ситуация, когда необходимо запускать тест с разными наборами данных. В Cypress нет встроенной поддержки параметризованных тестов, но такое поведение можно реализовать с помощью массива/фикстуры и метода forEach().

Фикстуры — это файлы, содержащие статические данные. По умолчанию Cypress ищет их в директории fixtures/.

Давайте автоматизируем следующие сценарии:

  1. Зайти на сайт https://automationintesting.online/

  2. Перейти на страницу авторизации администратора

  3. Ввести некорректные данные и нажать на кнопку «Login»:
    email: », password: ‘test’
    email: ‘user’, password: »
    email: ‘admi’, password: ‘password’
    email: ‘admin’, password: ‘test’

  4. Проверить, что появилась ошибка «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');     })   }); })

Перезапустим спецификацию и увидим, что было запущено четыре теста с разными данными.

Параметризованный тест

Параметризованный тест

Удаление номера

Предусловие:

  1. Пользователь авторизован с ролью модератор на сайте https://automationintesting.online/ и находится на странице со списком номеров;

  2. Номер создан на сайте.

Сценарий: 

  1. Нажать на кнопку «Удалить» у соответствующего номера;

  2. Проверить, что строка с номером не отображается на странице.

Так как это новая функциональность создадим спеку e2e/rooms.cy.js

Хуки

С помощью хуков можно выполнять код до или после тестов. Их удобно использовать для создания предусловий (настройка окружения, подготовка данных, авторизация и другого). Cypress предоставляет четыре метода:

  1. before() — выполняется один раз перед всеми тестами в блоке;

  2. beforeEach() — выполняется перед каждым тестом;

  3. afterEach() — выполняется после каждого теста;

  4. 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *