Путешествие по E2E-тестированию, часть 2: WebdriverIO на Cypress

Опубликовано: 2019-11-21

Примечание. Это сообщение от #frontend@twiliosendgrid. Другие инженерные сообщения можно найти в техническом блоге.

Для всех наших интерфейсных приложений у нас была и остается следующая цель: предоставить способ написания согласованных, отлаживаемых, поддерживаемых и ценных автоматизированных тестов E2E (сквозных) для наших интерфейсных приложений и интегрировать с CICD (непрерывная интеграция). и непрерывное развертывание).

Чтобы достичь того состояния, которое мы имеем сегодня, когда сотни E2E-тестов, возможно, запускаются или выполняются по расписанию во всех наших клиентских командах в SendGrid, нам пришлось исследовать и экспериментировать со множеством потенциальных решений, пока мы не достигли этого основного. цель.

Мы попытались развернуть наше собственное решение Ruby Selenium, разработанное преданными инженерами-испытателями, под названием SiteTestUI, также известное как STUI, в котором все команды могли вносить свой вклад в один репозиторий и запускать кросс-браузерные тесты автоматизации. К сожалению, он уступил медленным, ненадежным тестам, ложным срабатываниям, отсутствию контекста между репозиториями и языками, слишком большому количеству рук в одной корзине, болезненным процессам отладки и большему количеству времени, затраченному на обслуживание, чем на обеспечение ценности.

Затем мы поэкспериментировали с другой многообещающей библиотекой в ​​WebdriverIO, чтобы написать тесты на JavaScript, размещенные вместе с репозиториями приложений каждой команды. Хотя это решило некоторые проблемы, усилило командную ответственность и позволило нам интегрировать тесты с Buildkite, нашим поставщиком CICD, у нас все еще были ненадежные тесты, которые было сложно отлаживать и сложно писать, в дополнение к работе со всеми слишком похожими ошибками и причудами Selenium. .

Мы хотели избежать еще одного STUI 2.0 и начали изучать другие варианты. Если вы хотите узнать больше о наших извлеченных уроках и стратегиях, обнаруженных в ходе перехода от STUI к WebdriverIO, ознакомьтесь с частью 1 серии сообщений в блоге.

В этой заключительной второй части серии сообщений в блоге мы расскажем о нашем пути от STUI и WebdriverIO к Cypress и о том, как мы прошли аналогичные миграции при настройке общей инфраструктуры, написании организованных тестов E2E, интеграции с нашими пайплайнами Buildkite и масштабировании до другие фронтенд-команды в организации.

TLDR: мы перешли на Cypress вместо STUI и WebdriverIO и достигли всех наших целей, написав полноценные E2E-тесты для интеграции с CICD. Большая часть нашей работы и уроков, извлеченных из WebdriverIO и STUI, хорошо перенесены в то, как мы используем и интегрируем тесты Cypress сегодня.

Оглавление

Исследование и посадка на Кипарис

Переход со STUI и WebdriverIO на Cypress

Шаг 1: Установка зависимостей для Cypress

Шаг 2: Конфигурации среды и сценарии

Первый проход конфигураций и скриптов среды

Эволюция конфигураций и скриптов окружения

Шаг 3. Локальное внедрение E2E-тестов

Шаг 4: Докеризация тестов

Шаг 5: Интеграция с CICD

Шаг 6: Сравнение Cypress и WebdriverIO/STUI

Шаг 7: Масштабирование на другие команды фронтенда

Чего мы с нетерпением ждем от Cypress

Принятие Cypress в будущее

Исследование и посадка на Кипарис

Когда мы искали альтернативы WebdriverIO, мы видели другие оболочки Selenium, такие как Protractor и Nightwatch с набором функций, аналогичным WebdriverIO, но мы чувствовали, что, скорее всего, столкнемся с длительными настройками, ненадежными тестами и утомительной отладкой и обслуживанием в будущем.

К счастью, мы наткнулись на новую среду тестирования E2E под названием Cypress, которая продемонстрировала быструю настройку, быстрые и отлаживаемые тесты, выполняемые в браузере, заглушение запросов на сетевом уровне и, что наиболее важно, отсутствие использования Selenium под капотом.

Нас поразили потрясающие функции, такие как запись видео, графический интерфейс Cypress, платная служба Dashboard и распараллеливание. Мы были готовы пойти на компромисс с кросс-браузерной поддержкой в ​​пользу того, чтобы ценные тесты последовательно проходили против Chrome с набором инструментов в нашем распоряжении для реализации, отладки и поддержки тестов для наших разработчиков и QA.

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

Имея это в виду, мы взяли на себя обязательство протестировать Cypress как еще одно решение для написания быстрых, последовательных и отлаживаемых E2E-тестов, которые в конечном итоге будут интегрированы с Buildkite во время CICD. Мы также намеренно поставили еще одну цель — сравнить Cypress с предыдущими решениями на основе Selenium, чтобы в конечном итоге определить по точкам данных, следует ли нам продолжать искать или создавать наши тестовые наборы с Cypress в будущем. Мы планировали преобразовать существующие тесты WebdriverIO и любые другие высокоприоритетные тесты, все еще находящиеся в STUI, в тесты Cypress и сравнить опыт наших разработчиков, скорость, стабильность, время выполнения тестов и обслуживание тестов.

Переход со STUI и WebdriverIO на Cypress

При переходе со STUI и WebdriverIO на Cypress мы систематически решали эту проблему с помощью той же высокоуровневой стратегии, которую мы использовали, когда пытались выполнить миграцию со STUI на WebdriverIO в наших репозиториях внешних приложений. Для получения более подробной информации о том, как мы выполнили такие шаги для WebdriverIO, обратитесь к части 1 из серии сообщений в блоге. Общие шаги по переходу на Cypress включали следующее:

  1. Установка и настройка зависимостей для подключения к Cypress
  2. Установка конфигураций среды и команд скриптов
  3. Внедрение E2E-тестов, которые проходят локально в разных средах.
  4. Докеризация тестов
  5. Интеграция Dockerized тестов с Buildkite, нашим поставщиком CICD

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

6. Сравнение Cypress с точки зрения опыта разработчиков, скорости и стабильности тестов с WebdriverIO и STUI.
7. Масштабирование на другие фронтенд-команды

Шаг 1: Установка зависимостей для Cypress

Чтобы быстро приступить к работе с Cypress, все, что нам нужно было сделать, это `npm install cypress` в наших проектах и ​​запустить Cypress в первый раз, чтобы он был автоматически размещен с файлом конфигурации ` cypress и папкой cypress. со стартовыми приспособлениями, тестами и другими установочными файлами для команд и плагинов. Мы оценили, как Cypress поставлялся в комплекте с Mocha в качестве средства запуска тестов, Chai для утверждений, а также Chai-jQuery и Sinon-Chai для еще большего количества утверждений, которые можно использовать и связывать. Нам больше не нужно было тратить много времени на изучение того, какие средства запуска тестов, генератор отчетов, утверждения и сервисные библиотеки нужно установить и использовать, по сравнению с тем, когда мы только начинали с WebdriverIO или STUI. Мы сразу же запустили некоторые из сгенерированных тестов с их графическим интерфейсом Cypress и изучили множество имеющихся в нашем распоряжении функций отладки, таких как отладка во времени, игровая площадка селекторов, записанные видео, снимки экрана, журналы команд, инструменты разработчика браузера и т. д.

Мы также настроили его позже с помощью Eslint и TypeScript для дополнительной статической проверки типов и правил форматирования, которым необходимо следовать при фиксации нового тестового кода Cypress. Первоначально у нас были некоторые проблемы с поддержкой TypeScript, и некоторые файлы должны были быть файлами JavaScript, например, те, которые сосредоточены вокруг файлов плагинов, но по большей части мы смогли проверить большинство наших файлов для наших тестов, объектов страницы и команд.

Вот пример структуры папок, которой следовала одна из наших групп внешнего интерфейса для включения объектов страницы, плагинов и команд:

Шаг 2: Конфигурации среды и сценарии

После быстрой установки и настройки Cypress для локального запуска нам понадобился способ запуска наших тестов Cypress с различными настройками для каждой среды, и мы хотели поддерживать те же варианты использования, которые позволяли нам делать наши команды WebdriverIO. Вот список, иллюстрирующий большинство случаев использования того, как мы хотели выполнить эти тесты и почему мы хотели их поддерживать:

  • В отношении сервера разработки Webpack, работающего на локальном хосте (например, http://localhost:8000), и этот сервер разработки будет указывать на определенный API среды (например, https://testing.api.com или https://staging.api. com), например тестирование или постановка.
    Почему? Иногда нам нужно внести изменения в наше локальное веб-приложение, например, добавить более конкретные селекторы для наших тестов, чтобы более надежно взаимодействовать с элементами, или мы разрабатывали новую функцию, и нам нужно было настроить и проверить существующие тесты автоматизации. будет проходить локально против наших новых изменений кода. Всякий раз, когда код приложения менялся и мы еще не переходили в развернутую среду, мы использовали эту команду для запуска наших тестов с нашим локальным веб-приложением.
  • Против развернутого приложения для определенной среды (например, https://testing.app.com или https://staging.app.com), например для тестирования или подготовки.
    Почему? В других случаях код приложения не меняется, но нам, возможно, придется изменить наш тестовый код, чтобы исправить некоторую ненадежность, или мы чувствуем себя достаточно уверенно, чтобы полностью добавлять или удалять тесты, не внося никаких изменений во внешний интерфейс. Мы активно использовали эту команду для локального обновления или отладки тестов развернутого приложения, чтобы более точно имитировать выполнение наших тестов в CICD.
  • Запуск в контейнере Docker против развернутого приложения для определенной среды , такой как тестирование или подготовка.
    Почему? Это предназначено для CICD, поэтому мы можем запустить тесты E2E для запуска в контейнере Docker, например, для промежуточного развернутого приложения, и убедиться, что они проходят перед развертыванием кода в рабочей среде или в запланированных тестовых запусках в выделенном конвейере. При первоначальной настройке этих команд мы выполнили множество проб и ошибок, чтобы запустить контейнеры Docker с различными значениями переменных среды и проверить, чтобы убедиться, что правильные тесты выполняются успешно, прежде чем подключать их к нашему поставщику CICD, Buildkite.

Первый проход конфигураций и скриптов среды

Когда мы впервые экспериментировали с настройкой Cypress, мы сделали это в репозитории, которое охватывает https://app.sendgrid.com, веб-приложение, которое включает в себя страницы функций, такие как аутентификация отправителя, активность электронной почты и проверка электронной почты, и мы неизбежно поделились наши открытия и знания с командами, создавшими наше веб-приложение Marketing Campaigns, которое охватывает домен https://mc.sendgrid.com. Мы хотели запустить наши тесты E2E в нашей промежуточной среде и использовали интерфейс командной строки Cypress и параметры, такие как --config или --env , для выполнения наших сценариев использования.

Чтобы запустить тесты Cypress для веб-приложения локально, скажем, по http://127.0.0.1:8000 или по URL-адресу развернутого промежуточного приложения, мы изменили флаг конфигурации baseUrl в команде и добавили дополнительные переменные среды, такие как testEnv , чтобы помочь загружать определенные приборы или тестовые данные для конкретной среды в наших тестах Cypress. Например, используемые ключи API, созданные пользователи и другие ресурсы могут различаться в разных средах. Мы использовали testEnv для переключения этих фикстур или добавления специальной условной логики, если некоторые функции не поддерживались в среде или настройки тестирования отличались, и мы могли получить доступ к среде через вызов, подобный Cypress.env(“testEnv”) в наших спецификациях.

Затем мы организовали наши команды cypress:open:* , чтобы представить открытие графического интерфейса Cypress, чтобы мы могли выбрать наши тесты для запуска через пользовательский интерфейс, когда мы локально разрабатываем, и cypress:run:* , чтобы обозначить выполнение тестов в автономном режиме, который был более адаптирован. для запуска в контейнере Docker во время CICD. После open или run будет среда, поэтому наши команды будут легко читаться, например, npm run cypress:open:localhost:staging , чтобы открыть графический интерфейс и запустить тесты на локальном сервере разработки Webpack, указывающем на промежуточные API, или npm run cypress:run:staging для запуска тестов в автономном режиме с развернутым промежуточным приложением и API. Скрипты package.json Cypress получились такими:

Эволюция конфигураций и скриптов окружения

В другом проекте мы усовершенствовали наши команды и конфигурации Cypress, чтобы воспользоваться преимуществами некоторой логики Node в файле cypress/plugins/index.js , чтобы иметь базовый файл cypress.json и отдельные файлы конфигурации, которые будут считываться на основе переменной среды с именем configFile для загрузки определенного файла конфигурации. Затем загруженные файлы конфигурации будут объединены с базовым файлом, чтобы в конечном итоге указать на промежуточный или фиктивный внутренний сервер.

Если вам интересно узнать больше о фиктивном внутреннем сервере, мы разработали сервер Express с внутренними конечными точками, которые просто возвращают различные ответы статических данных JSON и коды состояния (например, 200, 4XX, 5XX) в зависимости от параметров запроса, переданных в запросах. Это разблокировало внешний интерфейс для продолжения разработки потоков страниц с реальными сетевыми вызовами к фиктивному внутреннему серверу с ответами, имитирующими то, как будет выглядеть фактический API, когда он будет доступен в будущем. Мы также могли бы легко смоделировать различные уровни ответа на успех и ошибку для наших различных состояний пользовательского интерфейса, которые в противном случае было бы трудно воспроизвести в рабочей среде, и, поскольку мы будем делать детерминированные сетевые вызовы, наши тесты Cypress будут менее ненадежными при запуске одной и той же сети. запросы и ответы каждый раз.

У нас был базовый файл cypress.json , который включал общие свойства для общих тайм-аутов, идентификатор проекта для подключения к сервису Cypress Dashboard, о котором мы поговорим позже, и другие настройки, как показано ниже:

Мы создали папку config в папке cypress для хранения каждого из наших файлов конфигурации, таких как localhostMock.json для запуска нашего локального сервера разработки Webpack на локальном фиктивном сервере API или staging.json для работы с развернутым промежуточным приложением и API. Эти файлы конфигурации для сравнения и объединения с базовой конфигурацией выглядели следующим образом:

Файлы конфигурации CICD имели еще более простой файл JSON, так как нам нужно было динамически устанавливать переменные среды, чтобы учитывать различные базовые URL-адреса внешнего интерфейса службы Docker и хосты API-интерфейса фиктивного сервера, которые мы рассмотрим позже.

В наш файл cypress/plugins/index.js мы добавили логику для чтения переменной среды с именем configFile , установленной из команды Cypress, которая в конечном итоге считывала соответствующий файл в папке config и объединяла его с базовым cypress.json , как показано ниже:

Чтобы написать разумные команды Cypress с набором переменных среды для наших случаев использования, мы воспользовались Makefile , который выглядел примерно так:

С этими командами, аккуратно расположенными в Makefile, мы могли быстро делать такие вещи, как make cypress_open_staging или make cypress_run_staging в наших npm-скриптах `package.json`.

Раньше мы помещали некоторые команды в одну длинную строку, которую было бы сложно отредактировать без ошибок. К счастью, Makefile помог намного лучше распределить информацию благодаря удобочитаемой интерполяции переменных среды в команды Cypress по нескольким строкам. Мы могли бы быстро установить или экспортировать переменные среды, такие как configFile для загрузки файла конфигурации среды, BASE_URL для посещения наших страниц, API_HOST для различных серверных сред или SPECS , чтобы определить, какие тесты следует запустить, прежде чем мы запустим любую из команд Makefile.

Мы также использовали команды Makefile для других длинных сценариев npm и команд Docker, а также для создания наших ресурсов Webpack, установки зависимостей или запуска команд одновременно с другими. Затем мы, наконец, переведем некоторые команды Makefile в раздел сценариев package.json , хотя в этом нет необходимости, если кто-то хочет использовать только Makefile, и это будет выглядеть следующим образом:

Мы намеренно исключили многие команды Cypress CICD, поскольку они не были командами, которые будут использоваться в повседневной разработке, и в результате package.json более упорядоченным. Самое главное, мы могли сразу увидеть все команды Cypress, связанные с фиктивным сервером и локальным сервером разработки Webpack по сравнению с промежуточными средами, и какие из них «открывают» графический интерфейс, а не «запускаются» в автономном режиме.

Шаг 3. Локальное внедрение E2E-тестов

Когда мы начали реализовывать E2E-тесты с помощью Cypress, мы сослались на наши существующие тесты из WebdriverIO и STUI, чтобы преобразовать их, и добавили новые тесты для других высокоприоритетных функций, начиная от простых проверок работоспособности и заканчивая сложными потоками счастливого пути. Перевод существующих объектов страницы и тестовых файлов из WebdriverIO или STUI в эквивалентные объекты страницы и спецификации в Cypress оказался легким делом. На самом деле это привело к гораздо более чистому коду, чем раньше, с менее явным ожиданием элементов и лучшей цепочкой утверждений и других команд Cypress.

Например, общие шаги тестов остались прежними с точки зрения конечного пользователя, поэтому работа по преобразованию включала сопоставление API WebdriverIO или STUI с API Cypress следующими способами:

  • Многие команды по существу появились и работали аналогично тому, как мы почти просто заменяли $ или browser на cy или Cypress , т.е. посещая страницу через $(“.button”).click() на cy.get(“.button”).click() , browser.url() в cy.visit() или $(“.input”).setValue() в cy.get(“.input”).type()
  • Использование $ или $$ обычно превращается в cy.get(...) или cy.contains(...) т.е. $$(“.multiple-elements-selector”) или $(“.single-element-selector”) превратился в cy.get(“.any-element-selector”) , cy.contains(“text”) или cy.contains(“.any-selector”)
  • Удаление $(“.selector”).waitForVisible(timeoutInMs) , $(“.selector”).waitUntil(...) или $(“.selector”).waitForExist() в пользу разрешения Cypress по умолчанию обрабатывать повторные попытки и извлечение элементов снова и снова с помощью cy.get('.selector') и cy.contains(textInElement) . Если бы нам требовался более длительный тайм-аут, чем по умолчанию, мы бы использовали cy.get('.selector', { timeout: longTimeoutInMs }) в целом, а затем после извлечения элемента мы бы связали следующую команду действия, чтобы сделать что-то с элементом, т.е. cy.get(“.selector”).click() .
  • Пользовательские команды с браузером. addCommand('customCommand, () => {})` turned into `Cypress.Commands.add('customCommand', () => {}) и выполнил `cy.customCommand()`
  • Выполнение сетевых запросов на установку или отключение через API с использованием библиотеки под названием node-fetch и обертывание ее в browser.call(() => return fetch(...)) и/или browser.waitUntil(...) привело к выполнение HTTP-запросов на сервере Cypress Node через cy.request(endpoint) или пользовательский плагин, который мы определили и сделали вызовы, такие как cy.task(taskToHitAPIOrService) .
  • Раньше, когда нам приходилось ждать завершения важного сетевого запроса без каких-либо заметных изменений пользовательского интерфейса, нам иногда приходилось прибегать к использованию browser.pause(timeoutInMs) , но с Cypress мы улучшили это с помощью функции сетевой заглушки и смогли слушать и дождитесь завершения конкретного запроса с помощью cy.server() , cy.route(“method”, “/endpoint/we/are/waiting/for).as(“endpoint”)`, and `cy.wait(“@endpoint”) перед запуском действия, которое вызовет запрос.

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

Расширенный объект страницы добавит геттеры для селекторов элементов, реализует функциональность open() с маршрутом страницы и предоставит любую вспомогательную функциональность, как показано ниже.

Наши фактические расширенные объекты страницы также использовали простую карту объектов для хранения всех наших селекторов элементов CSS в одном месте, которые мы подключали к нашим компонентам React в качестве атрибутов данных, ссылок в модульных тестах и ​​использовали в качестве наших селекторов в объектах страницы Cypress. Кроме того, наши классы объектов страниц время от времени различались в использовании преимуществ конструктора классов, если, скажем, объект страницы повторно использовался для группы похожих по внешнему виду и функционированию страниц, таких как наши страницы подавления, и мы передавали аргументы для изменения маршрута или определенных свойств.

В качестве примечания: командам не нужно было использовать объекты страницы, но мы оценили согласованность шаблона для сохранения функциональности страницы и ссылок на селекторы элементов DOM вместе со стандартной структурой объекта класса для совместного использования общих функций на всех страницах. Другие команды предпочитали создавать много разных файлов с небольшими служебными функциями и без использования классов ES6, но важно было вынести организованный, предсказуемый способ инкапсулировать все и написать тесты для повышения эффективности разработчиков и удобства сопровождения.

Мы придерживались той же общей стратегии тестирования, которая использовалась в наших старых тестах WebdriverIO, пытаясь максимально настроить тест через API. Мы особенно хотели избежать создания нашего состояния установки через пользовательский интерфейс, чтобы не создавать ненадежность и тратить время на части, которые мы не стремились тестировать. В большинстве тестов использовалась эта стратегия:

  • Настройка или удаление через API. Если нам нужно протестировать создание объекта через пользовательский интерфейс, мы должны сначала удалить объект через API. Независимо от того, каким образом предыдущее выполнение теста завершилось успехом или неудачей, тест необходимо было правильно настроить или отключить с помощью API, чтобы убедиться, что тест ведет себя согласованным образом и запускается с правильными условиями.
  • Вход в систему для выделенного тестового пользователя через API. Мы создали выделенных тестовых пользователей для каждой страницы или даже для каждого теста автоматизации, чтобы наши тесты были изолированы и не топтали ресурсы друг друга при параллельном выполнении. Мы сделали тот же запрос, что и наша страница входа через API, и сохранили файл cookie до начала теста, чтобы мы могли напрямую посетить страницу, прошедшую проверку подлинности, и начать фактические шаги теста.
  • Автоматизация шагов с точки зрения конечного пользователя. После входа в систему через API мы напрямую посетили страницу и автоматизировали шаги, которые конечный пользователь должен был бы выполнить, чтобы завершить поток функций и убедиться, что пользователь видит и взаимодействует с нужными вещами. по пути.

Чтобы сбросить тест обратно в его ожидаемое исходное состояние, мы должны войти в систему для выделенного тестового пользователя через API с помощью глобальной команды cy.login , установить файл cookie, чтобы пользователь оставался в системе, сделать вызовы API, необходимые для возврата пользователя в желаемое начальное состояние с помощью cy.request(“endpoint”) или cy.task(“pluginAction”) и посетить аутентифицированную страницу, которую мы хотели протестировать напрямую. Затем мы автоматизируем шаги для выполнения пользовательского потока функций, как показано на тестовом макете ниже.

Помните пользовательские команды, о которых мы говорили для входа в систему, cy.login() и выхода из системы, cy.logout() ? Мы легко реализовали их в Cypress таким образом, чтобы все наши тесты входили в систему пользователя через API одинаковым образом.

Кроме того, мы хотели автоматизировать и проверить некоторые сложные потоки, связанные с электронной почтой, которые раньше не удавались с помощью WebdriverIO или STUI. Некоторые примеры включают экспорт активности электронной почты в CSV, прохождение потока отправки коллеге для проверки подлинности отправителя или экспорт результатов проверки электронной почты в CSV. Cypress не позволяет получить доступ к нескольким супердоменам в одном тесте, поэтому переход к почтовому клиенту через пользовательский интерфейс, которым мы не владеем, был ненадежным и не вариант.

Вместо этого мы разработали плагины Cypress с помощью их cy.task(“pluginAction”) , чтобы использовать некоторые библиотеки на сервере Cypress Node для подключения к тестовому клиенту/почтовому ящику IMAP электронной почты, такому как SquirrelMail, для проверки соответствия электронных писем в почтовом ящике после запроса действия. в пользовательском интерфейсе и для перехода по ссылкам перенаправления из этих писем обратно в наш домен веб-приложения, чтобы убедиться, что определенные страницы загрузки отображаются и эффективно завершают весь поток клиентов. Мы внедрили плагины, которые будут ждать поступления электронных писем в папку «Входящие» SquirrelMail с учетом определенных строк темы, удалять электронные письма, отправлять электронные письма, запускать события электронной почты, опрашивать серверные службы и выполнять гораздо более полезные настройки и демонтаж через API для наших тестов.

Чтобы лучше понять, что мы на самом деле тестировали с Cypress, мы рассмотрели множество важных случаев, таких как эти:

  • Проверки работоспособности для всех наших страниц , также называемые прогулкой по приложению. Мы хотели убедиться, что страницы, загруженные каким-либо контентом, иногда вызывают отказ определенных серверных служб или внешнего хостинга. Мы также рекомендовали выполнить эти тесты в первую очередь, чтобы наработать умственную память для создания объектов страницы с селекторами и вспомогательными функциями, а также для получения быстрых рабочих тестов, запускаемых в среде.
  • Операции CRUD на странице. Мы всегда сбрасывали тесты через API, а затем специально тестировали создание, чтение, обновление или удаление в пользовательском интерфейсе. Например, если мы протестировали возможность создания аутентификации домена через пользовательский интерфейс, независимо от того, как закончился последний тестовый запуск, нам нужно было убедиться, что домен, который мы собирались создать с помощью пользовательского интерфейса, был сначала удален через API, прежде чем продолжить. с автоматизированными шагами пользовательского интерфейса для создания домена и предотвращения коллизий. Если мы тестировали возможность удаления подавления через пользовательский интерфейс, мы обязательно сначала создали подавление через API, а затем приступили к выполнению шагов.
  • Тестирование поисковых фильтров на странице. Мы протестировали настройку множества расширенных поисковых фильтров с помощью электронной почты и посещение страницы с параметрами запроса, чтобы убедиться, что фильтры заполняются автоматически. Мы также добавили данные через API для проверки электронной почты и снова запустили различные поисковые фильтры и проверили соответствие таблицы поисковым фильтрам на этой странице.
  • Разные права доступа пользователей . В Twilio SendGrid у нас есть родительские учетные записи, в которых могут быть товарищи по команде с различными областями действия или разрешениями на доступ или подпользователями под ними, которые также имеют разную степень доступа и ведут себя примерно так же, как родительская учетная запись. Товарищи по команде с доступом только для чтения по сравнению с доступом администратора для определенных страниц и субпользователей будут видеть или не видеть определенные вещи на странице, и это упростило автоматизацию входа в систему для этих типов пользователей и проверки того, что они видят или не видят в тестах Cypress.
  • Различные пользовательские пакеты . Наши пользователи также могут различаться по типам бесплатных и платных пакетов, таких как Essentials, Pro и Premier, и эти пакеты также могут видеть или не видеть определенные элементы на странице. Мы входили в систему для пользователей с разными пакетами и быстро проверяли функции, копии или страницы, к которым у пользователей был доступ в тестах Cypress.

Шаг 4: Докеризация тестов

При выполнении каждого шага конвейера Buildkite на новой машине AWS в облаке мы не могли просто вызвать npm run cypress:run:staging , потому что на этих машинах нет Node, браузеров, кода нашего приложения или каких-либо других зависимостей для фактического запуска Cypress. тесты. Когда мы ранее настраивали WebdriverIO, нам нужно было собрать три отдельных сервиса в файле Docker Compose, чтобы соответствующие службы Selenium, Chrome и код приложения работали вместе для запуска тестов.

С Cypress это было намного проще, так как нам требовался только базовый образ Docker Cypress, cypress/base , для настройки среды в Dockerfile и только одна служба в файле docker-compose.yml с нашим кодом приложения для запуска Cypress. тесты. Мы рассмотрим один из способов сделать это, так как есть другие образы Cypress Docker, которые можно использовать, и другие способы настройки тестов Cypress в Docker. Мы рекомендуем вам ознакомиться с документацией Cypress для поиска альтернативных вариантов.

Чтобы запустить службу со всем нашим приложением и тестовым кодом, необходимым для запуска тестов Cypress, мы создали Dockerfile с именем Dockerfile.cypress установили все node_modules и скопировали код в рабочий каталог образа в среде Node. Это будет использоваться нашей cypress службой Docker Compose, и мы выполнили настройку Dockerfile следующим образом:

С помощью этого Dockerfile.cypress мы можем интегрировать Cypress для запуска выбранных спецификаций с определенным API среды и развернутым приложением через одну службу Docker Compose под названием cypress . Все, что нам нужно было сделать, это интерполировать некоторые переменные среды, такие как SPECS и BASE_URL , для запуска выбранных тестов Cypress по определенному базовому URL-адресу с помощью команды npm run cypress:run:cicd:staging , которая выглядит следующим образом ”cypress:run:cicd:staging”: “cypress run --record --key --config baseUrl=$BASE_URL --env testEnv=staging” .

Эти переменные среды будут установлены либо через файлы настроек/конфигурации конвейера Buildkite, либо экспортируются динамически при запуске тестов Cypress для запуска из нашего конвейера развертывания. Пример файла docker-compose.cypress.yml выглядел примерно так:

Есть еще пара вещей, на которые стоит обратить внимание. Например, вы можете увидеть переменную среды VERSION , которая позволяет нам ссылаться на определенный тегированный образ Docker. Позже мы продемонстрируем, как мы помечаем образ Docker, а затем извлекаем тот же образ Docker, чтобы эта сборка выполнялась с правильным кодом для тестов Cypress.

Кроме того, вы также заметите BUILDKITE_BUILD_ID , который предоставляется бесплатно вместе с другими переменными среды Buildkite для каждой сборки, которую мы запускаем, и флагом ci-build-id . Это включает функцию распараллеливания Cypress, и когда мы устанавливаем определенное количество машин, выделенных для тестов Cypress, он автоматически волшебным образом узнает, как раскрутить эти машины и разделить наши тесты для запуска на всех этих машинных узлах, чтобы оптимизировать и ускорить наш тест. время работы.

Наконец-то мы также воспользовались возможностью монтирования томов и функцией артефактов Buildkite. Мы загружаем видео и снимки экрана, чтобы к ним был прямой доступ через вкладку «Артефакты» пользовательского интерфейса Buildkite на случай, если у нас закончатся оплаченные выделенные тестовые записи на месяц или по какой-то причине мы не сможем получить доступ к службе панели инструментов. Всякий раз, когда кто-то запускает команду Cypress «запустить» в автономном режиме, в папках cypress cypress/videos и cypress/screenshots появляется вывод для локального просмотра, и мы просто монтируем эти папки и загружаем их в Buildkite для нас в качестве отказоустойчивого.

Шаг 5: Интеграция с CICD

Как только мы успешно запустили тесты Cypress в контейнере Docker в различных средах, мы начали интеграцию с Buildkite, нашим поставщиком CICD. Buildkite предоставил способы выполнения шагов в файле .yml на наших компьютерах AWS с помощью сценариев Bash и переменных среды, установленных либо в коде, либо с помощью настроек конвейера Buildkite репозитория в веб-интерфейсе. Buildkite также позволил нам запустить этот конвейер тестирования из нашего основного конвейера развертывания с экспортированными переменными среды, и мы могли бы повторно использовать эти шаги тестирования для других изолированных конвейеров тестирования, которые будут выполняться по расписанию, чтобы наши тестировщики могли отслеживать и просматривать.

На высоком уровне наши тестовые пайплайны Buildkite для Cypress, а также наши предыдущие пайплайны WebdriverIO выполняли следующие схожие шаги:

  • Настройте образы Docker . Создайте, пометьте и отправьте образы Docker, необходимые для тестов, в реестр, чтобы мы могли извлечь их на более позднем этапе.
  • Запустите тесты на основе конфигураций переменных среды . Извлеките образы Docker с тегами для конкретной сборки и выполните соответствующие команды в развернутой среде, чтобы запустить выбранные наборы тестов из заданных переменных среды.

Вот пример файла pipeline.cypress.yml , который демонстрирует настройку образов Docker на этапе «Сборка образа Cypress Docker» и запуск тестов на этапе «Запуск тестов Cypress»:

Следует обратить внимание на первый шаг «Сборка образа Cypress Docker» и на то, как он настраивает образ Docker для теста. Он использовал команду build Docker Compose для сборки службы cypress со всем тестовым кодом приложения и пометил его latest переменной среды и ${VERSION} , чтобы мы могли в конечном итоге получить тот же образ с соответствующим тегом для этой сборки в будущий шаг. Каждый шаг может выполняться где-то на другом компьютере в облаке AWS, поэтому теги однозначно идентифицируют образ для конкретного запуска Buildkite. После того, как образ был помечен тегом, мы отправили образ с тегом последней версии и версии в наш частный реестр Docker для повторного использования.

На этапе «Запуск тестов Cypress» мы извлекаем образ, который создали, пометили и отправили на первом этапе, и запускаем службу Cypress для выполнения тестов. На основе переменных среды, таких как SPECS и BASE_URL , мы будем запускать определенные тестовые файлы в определенной среде развернутого приложения для этой конкретной сборки Buildkite. Эти переменные среды будут установлены с помощью настроек конвейера Buildkite или будут запускаться динамически из сценария Bash, который будет анализировать поле выбора Buildkite, чтобы определить, какие наборы тестов запускать и в какой среде.

Когда мы выбираем тесты для запуска во время нашего конвейера развертывания Buildkite CICD и запускаем выделенный конвейер триггерных тестов с определенными экспортированными переменными среды, мы следуем инструкциям в файле pipeline.cypress.yml , чтобы это произошло. Пример запуска тестов после развертывания некоторого нового кода в среде ветки функций из конвейера развертывания выглядит следующим образом:

Триггерные тесты будут запускаться в отдельном конвейере, и после перехода по ссылке «Сборка № 639» мы перейдем к этапам сборки для запуска триггерного теста, как показано ниже:

Повторно используя один и тот же файл pipeline.cypress.yml для наших выделенных конвейеров Cypress Buildkite, работающих по расписанию, у нас есть сборки, подобные той, на которой выполняются наши тесты E2E «P1» с наивысшим приоритетом, как показано на фотографии ниже:

Все, что нам нужно сделать, это установить правильные переменные среды для таких вещей, как, какие спецификации запускать и какую внутреннюю среду использовать в настройках конвейера Buildkite. Затем мы можем настроить запланированную сборку Cron, которая также находится в настройках конвейера, чтобы она запускалась через определенное количество часов, и все готово. We would then create many other separate pipelines for specific feature pages as needed to run on a schedule in a similar way and we would only vary the Cron schedule and environment variables while once again uploading the same `pipeline.cypress.yml` file to execute.

In each of those “Run Cypress tests” steps, we can see the console output with a link to the recorded test run in the paid Dashboard Service, the central place to manage your team's test recordings, billing, and other Cypress stats. Following the Dashboard Service link would take us to a results view for developers and QAs to take a look at the console output, screenshots, video recordings, and other metadata if required such as this:

Step 6: Comparing Cypress vs. WebdriverIO/STUI

After diving into our own custom Ruby Selenium solution in STUI, WebdriverIO, and finally Cypress tests, we recorded our tradeoffs between Cypress and Selenium wrapper solutions.

Плюсы

  • It's not another Selenium wrapper – Our previous solutions came with a lot of Selenium quirks, bugs, and crashes to work around and resolve, whereas Cypress arrived without the same baggage and troubles to deal with in allowing us full access to the browser.
  • More resilient selectors – We no longer had to explicitly wait for everything like in WebdriverIO with all the $(.selector).waitForVisible() calls and now rely on cy.get(...) and c y.contains(...) commands with their default timeout. It will automatically keep on retrying to retrieve the DOM elements and if the test demanded a longer timeout, it is also configurable per command. With less worrying about the waiting logic, our tests became way more readable and easier to chain.
  • Vastly improved developer experience – Cypress provides a large toolkit with better and more extensive documentation for assertions, commands, and setup. We loved the options of using the Cypress GUI, running in headless mode, executing in the command-line, and chaining more intuitive Cypress commands.
  • Significantly better developer efficiency and debugging – When running the Cypress GUI, one has access to all of the browser console to see some helpful output, time travel debug and pause at certain commands in the command log to see before and after screenshots, inspect the DOM with the selector playground, and discern right away at which command the test failed. In WebdriverIO or STUI we struggled with observing the tests run over and over in a browser and then the console errors would not point us toward and would sometimes even lead us astray from where the test really failed in the code. When we opted to run the Cypress tests in headless mode, we got console errors, screenshots, and video recordings. With WebdriverIO we only had some screenshots and confusing console errors. These benefits resulted in us cranking out E2E tests much faster and with less overall time spent wondering why things went wrong. We recorded it took less developers and often around 2 to 3 times less days to write the same level of complicated tests with Cypress than with WebdriverIO or STUI.
  • Network stubbing and mocking – With WebdriverIO or STUI, there was no such thing as network stubbing or mocking in comparison to Cypress. Now we can have endpoints return certain values or we can wait for certain endpoints to finish through cy.server() and cy.route() .
  • Less time to set up locally – With WebdriverIO or STUI, there was a lot of time spent up front researching which reporters, test runners, assertions, and services to use, but with Cypress, it came bundled with everything and started working after just doing an npm install cypress.
  • Less time to set up with Docker – There are a bunch of ways to set up WebdriverIO with Selenium, browser, and application images that took us considerably more time and frustration to figure out in comparison to Cypress's Docker images to use right out of the gate.
  • Parallelization with various CICD providers – We were able to configure our Buildkite pipelines to spin up a certain number of AWS machines to run our Cypress tests in parallel to dramatically speed up the overall test run time and uncover any flakiness in tests using the same resources. The Dashboard Service would also recommend to us the optimal number of machines to spin up in parallel for the best test run times.
  • Paid Dashboard Service – When we run our Cypress tests in a Docker container in a Buildkite pipeline during CICD, our tests are recorded and stored for us to look at within the past month through a paid Dashboard Service. We have a parent organization for billing and separate projects for each frontend application to check out console output, screenshots, and recordings of all of our test runs.
  • Tests are way more consistent and maintainable – Tests passed way more consistently with Cypress in comparison to WebdriverIO and STUI where the tests kept on failing so much to the point where they were often ignored. Cypress tests failing more often signaled actual issues and bugs to look into or suggested better ways to refactor our tests to be less flaky. With WebdriverIO and STUI, we wasted a lot more time in maintaining those tests to be somewhat useful, whereas with Cypress, we would every now and then adjust the tests in response to changes in the backend services or minor changes in the UI.
  • Tests are faster – Builds passed way more consistently and overall test run times would be around 2 to 3 times faster when run serially without parallelization. We used to have overall test runs that would take hours with STUI and around 40 minutes with WebdriverIO, but now with way more tests and with the help of parallelization across many machine nodes, we can run over 200 tests in under 5 minutes .
  • Room to grow with added features in the future – With a steady open-source presence and dedicated Cypress team working towards releasing way more features and improvements to the Cypress infrastructure, we viewed Cypress as a safer bet to invest in rather than STUI, which would require us to engineer and solve a lot of the headaches ourselves, and WebdriverIO, which appeared to feel more stagnant in new features added but with the same baggage as other Selenium wrappers.

Минусы

  • Lack of cross-browser support – As of this writing, we can only run our tests against Chrome. With WebdriverIO, we could run tests against Chrome, Firefox, Safari, and Opera. STUI also provided some cross-browser testing, though in a much limited form since we created a custom in-house solution
  • Cannot integrate with some third-party services – With WebdriverIO, we had the option to integrate with services like BrowserStack and Sauce Labs for cross-browser and device testing. However, with Cypress there are no such third-party integrations but there are some plugins with services like Applitools for visual regression testing available. STUI, on the other hand, also had some small integrations with TestRail , but as a compromise, we log out the TestRail links in our Cypress tests so we can refer back to them if we needed to.
  • Requires workarounds to test with iframes – There are some issues around handling iframes with Cypress. We ended up creating a global Cypress command to wrap how to deal with retrieving an iframe's contents as there is no specific API to deal with iframes like how WebdriverIO does.

To summarize our STUI, WebdriverIO, and Cypress comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:

Following our analysis of the pros and cons of Cypress versus our previous solutions, it was pretty clear Cypress would be our best bet to accomplish our goal of writing fast, valuable, maintainable, and debuggable E2E tests we could integrate with CICD.

Though Cypress lacked features such as cross-browser testing and other integrations with third-party services that we could have had with STUI or WebdriverIO, we most importantly need tests that work more often than not and with the right tools to confidently fix broken ones. If we ever needed cross-browser testing or other integrations we could always still circle back and use our knowledge from our trials and experiences with WebdriverIO and STUI to still run a subset of tests with those frameworks.

We finally presented our findings to the rest of the frontend organization, engineering management, architects, and product. Upon demoing the Cypress test tools and showcasing our results between WebdriverIO/STUI and Cypress, we eventually received approval to standardize and adopt Cypress as our E2E testing library of choice for our frontend teams.

Step 7: Scaling to Other Frontend Teams

After successfully proving that using Cypress was the way to go for our use cases, we then focused on scaling it across all of our frontend teams' repos. We shared lessons learned and patterns of how to get up and running, how to write consistent, maintainable Cypress tests, and of how to hook those tests up during CICD or in scheduled Cypress Buildkite pipelines.

To promote greater visibility of test runs and gain access to a private monthly history of recordings, we established our own organization to be under one billing method to pay for the Dashboard Service with a certain recorded test run limit and maximum number of users in the organization to suit our needs.

Once we set up an umbrella organization, we invited developers and QAs from different frontend teams and each team would install Cypress, open up the Cypress GUI, and inspect the “Runs” and “Settings” tab to get the “Project ID” to place in their `cypress.json` configuration and “Record Key” to provide in their command options to start recording tests to the Dashboard Service. Finally, upon successfully setting up the project and recording tests to the Dashboard Service for the first time, logging into the Dashboard Service would show that team's repo under the “Projects” tab like this:

When we clicked a project like “mako”, we then had access to all of the test runs for that team's repo with quick access to console output, screenshots, and video recordings per test run upon clicking each row as shown below:

For more insights into our integration, we set up many separate dedicated test pipelines to run specific, crucial page tests on a schedule like say every couple hours to once per day. We also added functionality in our main Buildkite CICD deploy pipeline to select and trigger some tests against our feature branch environment and staging environment.

Как и следовало ожидать, это быстро прервало запланированные на месяц тестовые прогоны, тем более, что есть несколько команд, которые вносят свой вклад и инициируют тесты различными способами. Исходя из опыта, мы рекомендуем помнить о том, сколько тестов выполняется по расписанию, как часто эти тесты выполняются и какие тесты выполняются во время CICD. Могут быть некоторые избыточные тестовые прогоны и другие области, которые должны быть более экономными, такие как снижение частоты запланированных тестовых прогонов или, возможно, полное избавление от некоторых для запуска тестов только во время CICD.

То же правило бережливости применяется к добавлению пользователей, как мы подчеркивали, предоставляя доступ только разработчикам и тестировщикам в командах внешнего интерфейса, которые будут активно использовать службу Dashboard, а не высшему руководству и другим людям за пределами этих команд, чтобы заполнить эти ограниченные места.

Чего мы с нетерпением ждем от Cypress

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

  • Кроссбраузерная поддержка . Это важный момент, потому что большая часть отказа от внедрения Cypress была связана с использованием только Chrome по сравнению с решениями на основе Selenium, которые поддерживали такие браузеры, как Firefox, Chrome и Safari. К счастью, наша организация выиграла от более надежных, удобных в сопровождении и отладке тестов, и мы надеемся расширить наши наборы тестов дополнительными кросс-браузерными тестами в будущем, когда команда Cypress выпустит такую ​​кросс-браузерную поддержку.
  • Переписывание сетевого уровня — это также огромная проблема, поскольку мы склонны активно использовать Fetch API в наших новых областях React, а в более старых областях приложений Backbone/Marionette мы все еще использовали jQuery AJAX и обычные вызовы на основе XHR. Мы можем легко заглушить или прослушивать запросы в областях XHR, но для достижения того же эффекта нам пришлось прибегнуть к некоторым хакерским обходным путям с полифиллами выборки. Предполагается, что переписывание сетевого уровня поможет облегчить эти боли.
  • Постепенные улучшения службы панели мониторинга. Недавно мы уже видели некоторые новые изменения пользовательского интерфейса службы панели мониторинга, и мы надеемся, что она продолжит расти за счет большего количества визуализаций статистики и разбивки полезных данных. Мы также активно используем функцию распараллеливания и часто проверяем записи неудачных тестов в службе Dashboard, поэтому было бы приятно увидеть любые итеративные улучшения макета и/или функций.

Принятие Cypress в будущее

Для нашей организации мы оценили эффективность разработчика, отладку и стабильность тестов Cypress при запуске в браузере Chrome. Мы достигли согласованных, ценных тестов с меньшим объемом обслуживания в будущем и с множеством инструментов для разработки новых тестов и исправления существующих.

В целом, документация, API и инструменты в нашем распоряжении намного перевешивают любые минусы. После знакомства с графическим интерфейсом Cypress и платной службой Dashboard мы определенно не хотели возвращаться к WebdriverIO или нашему собственному решению Ruby Selenium.

Мы подключили наши тесты к Buildkite и достигли нашей цели, предоставив возможность писать согласованные, отлаживаемые, поддерживаемые и ценные тесты автоматизации E2E для наших интерфейсных приложений для интеграции с CICD . Мы доказали остальным фронтенд-командам, инженерному начальству и владельцам продукта преимущества перехода на Cypress и отказа от WebdriverIO и STUI.

Позже были проведены сотни тестов внешними командами в Twilio SendGrid, и мы обнаружили множество ошибок в нашей тестовой среде и смогли быстро исправить любые ненадежные тесты на нашей стороне с гораздо большей уверенностью, чем когда-либо прежде. Разработчики и тестировщики больше не боятся написания E2E-тестов, а теперь с нетерпением ждут возможности написать их для каждой новой функции, которую мы выпускаем, или для каждой старой функции, которая требует большего покрытия.