Тестируем веб-приложение с Cypress

Всем привет, сегодня я рад представить материал по end-to-end (e2e) тестированию. Данный вид тестирования поможет вам сэкономить время, если у вас есть задачи по проверке работоспособности веб-приложения.

Статья рассчитана на новичков в программировании (как начинающих разработчиков, так и начинающих тестировщиков).

Ручное тестирование VS автоматическое тестирование Мария Marik0<br />
Ручное тестирование VS автоматическое тестирование Мария Marik0

End-to-end тестирование - это вид тестирования, при котором компьютер максимально близко к реальности эмулирует поведение пользователя. То есть, ваше web-приложение загружается в браузере, затем нажимаются кнопки, происходят переходы по ссылкам, страница прокручивается и так далее. При данном подходе backend не "мокается", таким образом все http-запросы происходят в реальном времени.

Можно провести аналогию, что за компьютером сидит робот и нажимает кнопки в указанном порядке.

Содержание:

Введение

Не важно на чем ты пишешь "Фронтенд" Результат проверять придется! Ручному тестированию - гордое "НЕТ!" Пусть Cypress во всем разберется. За несколько быстрых и точных шагов, Настроим мы правое дело. Тесты отдай машине своей, А сам программируй смело! Во-первых, нам нужен малый пример, Давай нарисуем счетчик? Функционал его лишь "минус" и "плюс"... Готов ли ты, разработчик? Две кнопки, а также текст результата Хотелось бы нам лицезреть. git clone, как всегда, нам поможет ребята, Не стоит даже потеть!

Не скоро проза складывается, да скоро тесты пишутся. Распрощаемся с музой и посмотрим на исходный код. На выбор у нас два репозитория:

  • счетчик на React (потому что React популярный, и спасибо ему за все! За основу взят репозиторий create-react-app).
  • счетчик на Elm (потому что Elm - это лучшее, что со мной случилось во фронтенде на данный момент. За основу взят репозиторий create-elm-app).

Также два репозитория докажут нам на деле, что Cypress'у не важно на чем написано приложение.

В master ветке находится логика и верстка, а тесты мы сейчас с вами добавим.

Приложение "счетчик" (слева React, справа Elm)<br />
Приложение "счетчик" (слева React, справа Elm)

Установка Cypress

NPM / Yarn на ваше усмотрение. Я буду использовать npm и устанавливать Cypress как локальную зависимость. Все примеры я выполнял на macOS.

npm install cypress --save-dev

Установка может занять продолжительное время.

Добавляем скрипт для запуска тестов

Запустить любой "исполняемый" скрипт можно командой:

node_modules/.bin/script_name -параметры

Но это не удобно, поэтому давайте добавим в package.json в секцию scripts новую строку:

... scripts: { ... "test:cy": "cypress open" }

Теперь наши тесты можно запускать с помощью команды:

npm run test:cy

Попробуйте выполнить команду. В первый раз выполнение займет больше времени, чем обычно (так как Cypress скачает все необходимое для работы).

Главное окно Cypress, где можно выбрать браузер, в котором вы будете "прогонять" тесты. По умолчанию это Electron 83 (Chromium)<br />
Главное окно Cypress, где можно выбрать браузер, в котором вы будете "прогонять" тесты. По умолчанию это Electron 83 (Chromium)

Если установка прошла успешно, у вас откроется окно с тестами-примерами, которые были добавлены командой разработчиков Cypress. Рекомендую их запустить, чтобы ознакомиться с возможностями инструмента.

Можно выбрать Electron 83 (Chromium), Google Chrome (настоящий) или Firefox. Я оставлю Electron 83 по умолчанию.

Перед тем, как мы начнем писать наши тесты, давайте удалим все тесты-примеры из директории cypress/integrations/examples.

Добавляем тесты

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

Наш план автоматического тестирования будет состоять из следующих пунктов:

  • убедиться, что приложение ("сайт") на localhost открылось.
  • убедиться, что у нас есть кнопка "+".
  • убедиться, что у нас есть кнопка "-".
  • убеиться, что результат "ноль".

Начнем с первого пункта - проверки, что приложение загрузилось.

Создадим новый файл в директории с проектом:

cypress/integration/main.js

context("Counter main", () => { // приложение должно открыться по адресу: http://localhost:3000 it("Should open app on localhost:3000", () => { cy.visit("http://localhost:3000"); }); });

context (вы можете так же использовать describe) - это название категории (группы) ваших тестов. it - элемент в группе, то есть сам тест.

Сразу же запустим наш тест...

Приложение по адресу http://localhost:3000 недоступно<br />
Приложение по адресу http://localhost:3000 недоступно

Почему ошибка? Потому что мы забыли запустить приложение.

Давайте еще раз повторим: end-to-end тестирование - это "эмуляция" действий реального пользователя в реальном браузере. Мы можем посетить "vc.ru" и кликнуть на заголовок первого материала, если мы хотим это протестировать. Но, так как мы тестируем наше приложение в режиме разработки, то нам необходимо до запуска e2e-тестов запустить наше приложение и оставить его запущенным. Поэтому, запустите приложение в соседней вкладке терминала и запустите тесты вновь.

Cypress открыл приложение по адресу http://localhost:3000<br />
Cypress открыл приложение по адресу http://localhost:3000

Приложение загрузилось, порядок. Можно заниматься поиском элементов. Для этого, как советует раздел документации, добавим тестируемым элементам data-cy аттрибут (привожу примеры на react, elm код здесь)

src/App.js

import React, { useState } from "react"; import "./App.css"; const App = () => { const [count, setCount] = useState(0); return ( <div className="app"> <div className="result"> Результат: <span data-cy="result">{count}</span> </div> <div className="buttons"> <button data-cy="plus" onClick={() => setCount(count + 1)}> + </button> <button data-cy="minus" onClick={() => setCount(count - 1)}> - </button> </div> </div> ); }; export default App;

Данный атрибут нам нужен, чтобы наверняка найти нужный элемент. Давайте напишем тесты для проверки, действительно ли кнопки "+" и "-" отрисованы на странице.

cypress/integration/main.js

context("Counter main", () => { // приложение должно открыться по адресу: http://localhost:3000 it("Should open app on localhost:3000", () => { cy.visit("http://localhost:3000"); }); // кнопка "+" должна присутствовать на странице it("Should render plus button", () => { cy.get('[data-cy="plus"]'); }); // кнопка "-" должна присутствовать на странице it("Should render minus button", () => { cy.get('[data-cy="minus"]'); }); });
Все тесты зеленые<br />
Все тесты зеленые

Порядок. Мы нашли элементы, этого достаточно.

Для проверки, что "результат" при загрузке приложения равен нулю, нам нужно не только найти DOM-элемент, но также посмотреть чему равно его текстовое содержимое.

(троеточие означает, что часть кода скрыта для краткости)

cypress/integration/main.js

... // результат должен быть равен 0 it("Should render result equal to 0", () => { cy.get('[data-cy="result"]').should("have.text", 0); }); ...

Проверяем:

Результат содержит "ноль"<br />
Результат содержит "ноль"

Обратим внимание на синтаксис: сначала мы нашли элемент (cy.get), затем проверили его с нашим "утверждением/ожиданием" (в англ - assertion). Такой подход проверки результата с ожиданием используется в тестах повсеместно. Все возможные "ожидания" и примеры представлены в документации.

В руководствах часто игнорируют возможные ошибки, но это не наш случай, давайте изменим 0 на 1 в файле с тестами и Cypress автоматически "прогонит" наши тесты вновь.

Ошибка, ожидаем получить "единицу", а у нас "не единица"
Ошибка, ожидаем получить "единицу", а у нас "не единица"

Подведем итог. В данном разделе мы узнали:

  • как посетить нужный веб-сайт для теста (cy.visit).
  • как найти элемент на странице (cy.get).
  • как посмотреть содержимое найденного элемента (assertions).

Больше тестов, стабильней результат

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

  • убедимся, что по клику на "+" результат равен единице.
  • убедимся, что по клику на "+" еще раз результат равен двум.
  • убедимся, что по клику на "-" результат вновь равен единице (повторный клик на "минус" тестировать не будем).

Как я уже отмечал выше, Cypress позволяет не только найти элемент на странице, но также позволяет нам выполнить весь набор возможных действий (click, double click, right click, и т.д.). Нам потребуется click.

cypress/integration/main.js

... // после клика на "плюс", результат должен быть равен единице it("After click on plus, result should be equal 1", () => { cy.get('[data-cy="plus"]').click(); cy.get('[data-cy="result"]').should("have.text", 1); }); // после еще одного клика на "плюс", результат должен быть равен двум it("After second click on plus, result should be equal 2", () => { cy.get('[data-cy="plus"]').click(); cy.get('[data-cy="result"]').should("have.text", 2); }); // после клика на "минус", результат должен быть равен единице it("After click on minus, result should be equal 1", () => { cy.get('[data-cy="minus"]').click(); cy.get('[data-cy="result"]').should("have.text", 1); }); ...

Принцип тестов не изменился: сначала находим элемент с помощью cy.get, затем производим необходимые манипуляции.

Обратите внимание на порядок выполнения тестов. Сначала приложение загрузилось, затем мы кликнули на "плюс" - проверили, кликнули еще раз на "плюс" - проверили, кликнули на "минус" - проверили. Если мы в текущем коде поменяем it секции местами, то тесты могут сломаться. Так как мы тестируем последовательность действий, то наши тесты должны отрабатывать в корректной последовательности.

Итог раздела: научились эмулировать клики. Узнали, что порядок важен.

Новая фича: Reset (сброс результата)

Сначала напишем код, а затем напишем тест. Кое-кто воскликнет: "Постойте, а как же TDD?" На что мы ответим: спокойствие, end-to-end тесты можно (и я думаю даже нужно) писать после того, как код уже написан.

Добавляем кнопку (примеры кода на react, elm код здесь)

src/App.js

... <button data-cy="reset" onClick={() => setCount(0)}> Сбросить </button> ...

Добавляем тесты, но сначала план. Нам необходимо:

  • убедиться, что у нас есть кнопка "Сбросить".
  • кликнуть на кнопку.
  • убедиться, что результат стал равен нулю.
... // кнопка "Сбросить" должна присутствовать на странице it("Should render reset button", () => { cy.get('[data-cy="reset"]'); }); // эмулируем клик по кнопке "Сбросить" it("Reset should be able for click", () => { cy.get('[data-cy="reset"]').click(); }); // проверяем, что результат равен 0 it("After click on 'Reset' button, result should be equal 0", () => { cy.get('[data-cy="result"]').should("have.text", 0); }); ...
Кнопка "Сбросить" отработала, результат равен нулю<br />
Кнопка "Сбросить" отработала, результат равен нулю

В данном тестовом наборе у нас есть избыточный тест, в котором мы убеждаемся, что кнопка "Сбросить" отрисована. Так как в следующем тесте мы кликаем по данной кнопке и если кнопки не будет - Cypress сообщит нам об этом. Поэтому тест отрисовки кнопки "Сбросить" можно удалить.

На этом мы завершаем практическую часть. Исходный код приложения с тестами можно найти в соответствующей ветке (elm-репозиторий, react-репозиторий)

Какие минусы имеют end-to-end тесты?

Стабильность

По мнению многих, end-to-end тесты не стабильны. Сложно опровергать данное заявление, так как достичь 100% стабильности затруднительно. Однако, за многие годы Cypress добился достойных показателей по стабильности. Зачастую проблема не в том, что Cypress плох и не может выдать стабильный результат, а в том, что тест написан плохо и поэтому его результат не стабилен.

Посмотрите раздел документации Conditional Testing, почитайте советы.

Медлительность

Прогон end-to-end тестов занимает много времени. Тем не менее, это в разы быстрее чем ручное тестирование.

Конкуренты

Конкуренты на рынке есть, самый известный мне - puppeteer (от Google, между прочим). Определенно заслуживает внимания. В свое время, в 2017м году, мы сделали выбор в сторону puppeteer для тестирования десктопного приложения на electron.

Больше автоматизации

Любые тесты, в том числе и написанные на Cypress, приятнее запускать не в ручную, а в автоматическом режиме. На деле это выглядит так: у вас есть скрипт, который выпускает ваше приложение в свет. На одном из этапов у вас автоматически запускаются тесты, и если все в порядке - скрипт продолжает свою работу. Если вам интересно, почитайте материалы по CI/CD (например здесь).

Заключение

В данном материале мы узнали:

  • что такое end-to-end тестирование и как оно может нам помочь.
  • принцип тестирования: нашел элемент, выполнил действие, сравнил с ожиданием.
  • какие существуют минусы.

Также на простых примерах мы попрактиковались в написании тестов.

Что дальше?

Посмотрите огромный тестовый набор от команды Cypress.

Посмотрите документацию.

Почитайте как тестировал свой продукт Саша Беспоясов.

Начните внедрять e2e тесты в вашей работе!

End-to-end тестирование не заменяет unit-тестов. Тестирование - это комплекс мер по поддержанию высокой планки качества, и e2e тесты лишь часть пирамиды тестирования.

Спасибо всем, кто дочитал до конца, а так же спасибо Selectel за мотивацию поработать "пером".

Если у вас остались вопросы - напишите в комментариях.

1010
3 комментария

а чем он лучше чем Nightwatch JS или Webdriver + Cucumber JS ?

Ответить

или puppeteer...

Мне нравится у Cypress то что он популярный, хорошая документация и легко стартануть с минимальным уровнем.

Ответить

Я только начал во всем этом разбираться и сразу наткулся на то что по всей видимости выбрал не верный фреймворк в виде cypress для автоматизации! ситуация такая:
первая боль -  у нас несколько сайтов с редиректами между собой, например если на первом купить что ни будь то тебя средиректит на другой рессурс уже с твоей покупкой
вторая боль - при регистрации происходит переход на другой ресурс для оплаты подписки, сама платежная система наша и мне надо совершать и тестовые покупки в связке и сам факт перехода
третья боль - это переходы с основного ресурса на форум, который так же находится под другим адресом 

Все это связанно с редиректами, так вот вопрос, на cypress это ограничение как то можно снять? а то получается что я не могу даже регистрацию заавтоматизировать )))

Заранее благодарен!!

Ответить