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

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

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

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

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

Мы экспериментировали с несколькими техническими подходами, пока не нашли идеальное решение для E2E-тестирования. На высоком уровне это подводит итог нашему путешествию:

  • Создание собственного собственного решения Ruby Selenium, SiteTestUI, также известного как STUI.
  • Переход от STUI к WebdriverIO на основе узла
  • Неудовлетворенный ни одной из настроек и, наконец, переход на Cypress

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

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

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

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

Оглавление:

Первое знакомство с E2E-тестированием: siteTESUI, также известный как STUI

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

Шаг 1: Принятие решения о зависимостях для WebdriverIO

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

Шаг 3: Локальное внедрение тестов ENE

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

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

Компромиссы с WebdriverIo

Переезд на Кипарис

Первый шаг в E2E-тестирование: SiteTestUI, он же STUI

При первоначальном поиске инструмента автоматизации браузера наши SDET (инженеры-разработчики программного обеспечения в тестировании) погрузились в создание собственного собственного решения, созданного с использованием Ruby и Selenium, в частности, Rspec и специальной среды Selenium под названием Gridium. Мы оценили его кросс-браузерную поддержку, возможность настраивать наши собственные пользовательские интеграции с TestRail для наших тестовых случаев инженера QA (Quality Assurance) и мысль о создании идеального репозитория для всех интерфейсных команд, чтобы писать тесты E2E в одном месте и быть работать по расписанию.

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

Мы постепенно создавали существенные наборы тестов для разных команд в одном и том же репозитории, следуя схожим шаблонам, но вскоре столкнулись со многими разочарованиями, которые значительно замедлили наш прогресс для новых разработчиков и постоянных участников STUI, таких как:

  • Подготовка и запуск потребовали значительного времени и усилий для установки всех драйверов браузера, зависимостей Ruby Gem и правильных версий, прежде чем запускать наборы тестов. Иногда нам приходилось выяснять, почему тесты будут выполняться на одной машине, а не на машине другого человека, и чем отличаются их настройки.
  • Наборы тестов разрастались и выполнялись часами до завершения. Поскольку все команды вносили свой вклад в одно и то же репо, последовательное выполнение всех тестов означало ожидание запуска всего набора тестов в течение нескольких часов, а несколько команд, загружающих новый код, потенциально приводили к еще одному неработающему тесту в другом месте.
  • Мы разочаровались в ненадежных селекторах CSS и сложных селекторах XPath . На приведенном ниже рисунке достаточно показано, как использование XPath может усложнить ситуацию, и это были одни из самых простых примеров.

  • Отладка тестов была болезненной . У нас были проблемы с отладкой расплывчатых выходных данных об ошибках, и мы обычно понятия не имели, где и как произошел сбой. Мы могли только многократно запускать тесты и наблюдать, как браузер делает вывод, где он, возможно, дал сбой и какой код был ответственен за это. Когда тест не прошел в среде Docker в CICD, и нам не на что было смотреть, кроме выходных данных консоли, мы изо всех сил пытались воспроизвести локально и решить проблему.
  • Мы столкнулись с ошибками и медлительностью Selenium . Тесты выполнялись медленно из-за того, что все запросы отправлялись с сервера в браузер, и иногда наши тесты вообще зависали при попытке выбрать много элементов на странице или по неизвестным причинам во время тестовых прогонов.
  • Больше времени было потрачено на исправление и пропуск тестов, а нарушенные запланированные запуски тестов сборки стали игнорироваться. Тесты не давали значения для фактического выявления истинных ошибок в системе.
  • Наши фронтенд-команды чувствовали себя оторванными от тестов E2E, поскольку они находились в репозитории, отдельном от соответствующих репозиториев веб-приложений. Нам часто приходилось открывать оба репозитория одновременно и продолжать просматривать кодовые базы и обратно в дополнение к вкладкам браузера во время выполнения тестов.
  • Фронтенд-командам не нравилось переключение контекста с ежедневного написания кода на JavaScript или TypeScript на Ruby и необходимость заново учиться писать тесты при работе с STUI.
  • Поскольку для многих из нас это был первый опыт участия в тестировании, мы столкнулись со множеством антипаттернов , таких как создание состояния через пользовательский интерфейс для входа в систему, недостаточная разборка или настройка через API и отсутствие достаточной документации. следовать за тем, что делает большое испытание.

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

Мы оценили способ дать другим разработчикам интерфейсов и тестировщикам возможность создавать свои собственные стабильные комплекты тестирования E2E с помощью JavaScript, который находится в их собственном коде приложения, чтобы способствовать повторному использованию, близости и владению тестами. Это побудило нас исследовать WebdriverIO, платформу Selenium на основе JavaScript для автоматизированных тестов браузера, в качестве нашей первоначальной замены для STUI, специального собственного решения Ruby Selenium.

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

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

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

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

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

Шаг 1: Принятие решения о зависимостях для WebdriverIO

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

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

Мы решили позволить каждой из команд внешнего интерфейса настраивать его в соответствии со своими предпочтениями, и обычно мы использовали Mocha в качестве тестовой среды, Mochawesome в качестве репортера, автономный сервис Selenium и поддержку Typescript. Мы выбрали Mocha и Mochawesome из-за того, что наши команды уже были знакомы с Mocha и имели предыдущий опыт работы с ним, но другие команды решили использовать и другие альтернативы.

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

После принятия решения об инфраструктуре WebdriverIO нам нужен был способ запускать наши тесты 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.

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

Мы сформировали общий базовый файл конфигурации , wdio.conf.js , вот так:

Чтобы соответствовать нашему первому серьезному варианту использования тестов E2E на локальном сервере разработки webpack, указывающем на API среды, мы создали файл конфигурации localhost, wdio.localhost.conf.js , следующим образом:

Обратите внимание, что мы объединили базовый файл и добавили в него свойства, характерные для локального хоста, чтобы сделать его более компактным и простым в обслуживании. Мы также используем автономный сервис Selenium для расширения возможностей различных типов браузеров.

Для второго варианта использования тестов E2E для развернутого веб-приложения мы настроили файлы конфигурации приложения для тестирования и промежуточной подготовки, `wdio.testing.conf.js` и wdio.staging.conf.js , примерно так:

Здесь мы добавили некоторые дополнительные переменные среды в файлы конфигурации, такие как учетные данные для входа в систему для выделенных пользователей при подготовке, и обновили `baseUrl`, чтобы он указывал на URL-адрес развернутого приложения для подготовки.

В третьем случае запуска тестов E2E в контейнере Docker для развернутого веб-приложения в сфере нашего поставщика CICD мы настроили файлы конфигурации CICD, wdio.cicd.testing.conf.js и wdio.cicd.staging.conf.js вот так:

Обратите внимание, что мы больше не используем службу Selenium Standalone, поскольку позже мы установим Selenium Chrome, Selenium Hub и код приложения в отдельных службах в файле Docker Compose. Эта конфигурация также содержит те же переменные среды, что и промежуточная конфигурация, такие как учетные данные для входа и `baseUrl`, поскольку мы ожидаем запускать наши тесты для развернутого промежуточного приложения, и единственное отличие состоит в том, что эти тесты предназначены для выполнения в контейнере Docker. .

Установив эти файлы конфигурации среды, мы наметили команды сценария package.json , которые послужат основой для нашего тестирования. В этом примере мы добавили к командам префикс «uitest», чтобы обозначить тесты пользовательского интерфейса с помощью WebdriverIO, а также потому, что мы также заканчивали тестовые файлы *.uitest.js . Вот несколько примеров команд для промежуточной среды:

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

Имея под рукой все тестовые команды, мы выделили тесты в нашем репозитории STUI, чтобы преобразовать их в тесты WebdriverIO. Мы сосредоточились на тестах страниц малого и среднего размера и начали применять шаблон объекта страницы, чтобы организованно инкапсулировать весь пользовательский интерфейс для каждой страницы.

У нас могли быть структурированные файлы с кучей вспомогательных функций или объектных литералов или любой другой стратегией, но ключевой момент заключался в том, чтобы иметь последовательный способ быстрой доставки поддерживаемых тестов и придерживаться его. Если поток пользовательского интерфейса или элементы DOM изменились для конкретной страницы, нам нужно было только реорганизовать объект страницы, связанный с ней, и, возможно, тестовый код, чтобы снова пройти тесты.

Мы реализовали шаблон объекта страницы, имея базовый объект страницы с общей функциональностью, от которого будут расширяться все остальные объекты страницы. У нас были такие функции, как open, чтобы обеспечить согласованный API для всех наших объектов страницы, чтобы «открывать» или посещать URL-адрес страницы в браузере. Это было похоже на что-то вроде этого:

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

Обратите внимание, как мы использовали базовый класс open через super.open(...) с конкретным маршрутом страницы, чтобы мы могли посетить страницу с помощью этого вызова SomePage.open() . Мы также экспортировали уже инициализированный класс, чтобы мы могли ссылаться на такие элементы, как SomePage.submitButton или SomePage.tableRows и взаимодействовать с этими элементами или утверждать их с помощью команд WebdriverIO. Если бы объект страницы предназначался для совместного использования и инициализации с его собственными свойствами-членами в конструкторе, мы также могли бы напрямую экспортировать класс и создать экземпляр объекта страницы в тестовых файлах с помощью new SomePage(...constructorArgs) .

После того, как мы разместили объекты страницы с селекторами и некоторыми вспомогательными функциями, мы затем написали тесты E2E и обычно моделировали эту формулу теста:

  • Настройте или отключите через API то, что необходимо для сброса условий тестирования до ожидаемой начальной точки, прежде чем запускать фактические тесты.
  • Войдите в систему под выделенным пользователем для теста, чтобы всякий раз, когда мы посещали страницы напрямую, мы оставались в системе и не должны были проходить через пользовательский интерфейс. Мы создали простую вспомогательную функцию login в систему, которая принимает имя пользователя и пароль, выполняет тот же вызов API, который мы используем для нашей страницы входа, и которая в конечном итоге возвращает обратно наш токен авторизации, необходимый для входа в систему и передачи заголовков защищенных запросов API. У других компаний может быть даже больше настраиваемых внутренних конечных точек или инструментов для быстрого создания новых пользователей с начальными данными и конфигурациями, но у нас, к сожалению, не было достаточной конкретизации. Мы делали это старомодным способом и создавали выделенных тестовых пользователей в наших средах с различными конфигурациями через пользовательский интерфейс и часто разбивали тесты для страниц с разными пользователями, чтобы избежать конфликтов ресурсов и оставаться изолированными, когда тесты выполнялись параллельно. Мы должны были убедиться, что выделенных тестовых пользователей не трогали другие, иначе тесты сломались, когда кто-то неосознанно возился с одним из них.
  • Автоматизируйте шаги, как если бы конечный пользователь взаимодействовал с функцией/страницей. Как правило, мы посещаем страницу, содержащую наш поток функций, и начинаем следовать высокоуровневым шагам, которые конечный пользователь должен делать, таким как заполнение входных данных, нажатие кнопок, ожидание появления модальных окон или баннеров и просмотр таблиц для измененных выходных данных. результат действия. Используя наши удобные объекты страницы и селекторы, мы быстро реализовали каждый шаг и, по мере проверки работоспособности, утверждали, что пользователь должен или не должен видеть на странице во время потока функций, чтобы убедиться, что некоторые вещи ведут себя так, как ожидалось. до и после каждого шага. Мы также сознательно выбирали ценные тесты «счастливого пути» и иногда легко воспроизводимые состояния общих ошибок, откладывая остальную часть низкоуровневого тестирования до модульных и интеграционных тестов.

Вот приблизительный пример общей схемы наших E2E-тестов (эта стратегия применялась и к другим средам тестирования, которые мы пробовали):

Кстати, мы решили не освещать все советы и рекомендации по использованию WebdriverIO и лучших практик E2E в этой серии сообщений, но мы поговорим об этих темах в следующих сообщениях, так что следите за обновлениями!

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

При выполнении каждого шага конвейера Buildkite на новой машине AWS в облаке мы не могли просто вызвать «npm run uitests:staging», потому что на этих машинах нет Node, браузеров, кода нашего приложения или каких-либо других зависимостей для фактического запуска тестов. .

Чтобы решить эту проблему, мы объединили все зависимости, такие как Node, Selenium, Chrome и код приложения, в контейнер Docker для успешного выполнения тестов WebdriverIO. Мы воспользовались преимуществами Docker и Docker Compose, чтобы собрать все службы, необходимые для запуска и работы, которые были преобразованы в Dockerfiles и docker-compose.yml , а также множество экспериментов с локальным запуском контейнеров Docker, чтобы все заработало.

Чтобы предоставить больше контекста, мы не были экспертами в Docker, поэтому потребовалось значительное время, чтобы понять, как собрать все вместе. Существует несколько способов докеризации тестов WebdriverIO, и нам было сложно организовать множество различных сервисов вместе и просеивать различные образы Docker, версии Compose и учебные пособия, пока все не заработало.

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

На высоком уровне наши тесты требовали следующего:

  • Selenium для выполнения команд и связи с браузером. Мы использовали Selenium Hub для запуска нескольких экземпляров по желанию и загрузили образ «selenium/hub» для службы selenium-hub в файле docker-compose.
  • Браузер для запуска. Мы запустили экземпляры Selenium Chrome и установили образ «selenium/node-chrome-debug» для службы selenium-chrome в docker-compose.yml file .
  • Код приложения для запуска наших тестовых файлов с любыми другими установленными модулями Node. Мы создали новый Dockerfile , чтобы предоставить среду с Node для установки пакетов npm и запуска сценариев package.json , копирования тестового кода и назначения службы, предназначенной для запуска тестовых файлов с именем uitests в файле docker-compose.yml .

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

Чтобы объединить Selenium Hub, браузер Chrome и тестовый код приложения для запуска тестов WebdriverIO, мы описали сервисы selenium-hub , selenium-chrom и uitest в файле docker-compose.uitests.yml :

Мы подключили образы Selenium Hub и Chrome через переменные среды, depends_on и открыли порты для сервисов. Образ кода нашего тестового приложения в конечном итоге будет загружен и извлечен из частного реестра Docker, которым мы управляем.

Мы создадим образ Docker для тестового кода во время CICD с определенными переменными среды, такими как VERSION и PIPELINE_SUFFIX , чтобы ссылаться на образы по тегу и более конкретному имени. Затем мы запускали службы Selenium и выполняли команды через службу uitests для выполнения тестов WebdriverIO.

Когда мы создавали наши файлы Docker Compose, мы использовали полезные команды, такие как docker-compose up и docker-compose down с Mac Docker, установленным на наших машинах, чтобы локально проверить, что наши образы имеют правильные конфигурации и работают без сбоев перед интеграцией с Buildkite. Мы задокументировали все команды, необходимые для создания помеченных образов, добавления их в реестр, извлечения из них и выполнения тестов в соответствии со значениями переменных среды.

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

После того, как мы установили рабочие команды Docker и наши тесты успешно прошли в контейнере Docker в различных средах, мы начали интеграцию с Buildkite, нашим поставщиком CICD.

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

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

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

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

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

Следует обратить внимание на первый шаг «Сборка образа Docker UITests» и на то, как он настраивает образы Docker для теста. Он использовал команду build Docker Compose для создания службы uitests со всем тестовым кодом приложения и пометил его latest версией и переменной среды ${VERSION} , чтобы мы могли в конечном итоге получить тот же образ с соответствующим тегом для этой сборки в будущем. шаг.

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

На этапе «Запустить тесты Webdriver для Chrome» мы извлекаем образ, который мы создали, пометили и отправили на первом этапе, и запускаем Selenium Hub, Chrome и тестовые сервисы. Основываясь на переменных среды, таких как $UITESTENV и $UITESTSUITE , мы выбираем тип команды для запуска, например npm run uitest: , и наборы тестов для запуска для этой конкретной сборки Buildkite, например --suite $UITESTSUITE .

Эти переменные среды будут установлены с помощью настроек конвейера Buildkite или будут запускаться динамически из сценария Bash, который будет анализировать поле выбора Buildkite, чтобы определить, какие наборы тестов запускать и в какой среде.

Вот пример тестов WebdriverIO, запущенных в выделенном конвейере тестов, в котором также повторно использовался тот же файл pipeline.uitests.yml , но с набором переменных среды, в которых был запущен конвейер. Эта сборка завершилась неудачно, и мы могли просмотреть скриншоты ошибок на вкладке « Artifacts » и вывод консоли на вкладке « Logs ». Запомните artifact_paths в файле pipeline.uitests.yml (https://gist.github.com/alfredlucero/71032a82f3a72cb2128361c08edbcff2#file-pipeline-uitests-yml-L38), настройки скриншотов для `mochawesome` в файле `wdio.conf.js. ` (https://gist.github.com/alfredlucero/4ee280be0e0674048974520b79dc993a#file-wdio-conf-js-L39), и монтирование томов в сервисе uitests в файле `docker-compose.uitests.yml` (https://gist.github.com/alfredlucero/d2df4533a4a49d5b2f2c4a0eb5590ff8#file-docker-compose-yml-L32)?

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

Другой пример тестов WebdriverIO, выполняемых в отдельном конвейере по расписанию для конкретной страницы с использованием файла pipeline.uitests.yml , за исключением переменных среды, уже настроенных в параметрах конвейера Buildkite, показан ниже.

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

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

Независимо от поставщика CICD, который вы можете использовать, стратегии интеграции тестов будут одинаковыми при настройке образов Docker и запуске тестов на основе переменных среды для переносимости и гибкости.

Компромиссы с WebdriverIO

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

Вот список некоторых плюсов, которые мы обнаружили из нашего опыта работы с WebdriverIO по сравнению с пользовательским решением Ruby Selenium:

  • Тесты были написаны исключительно на JavaScript или TypeScript, а не на Ruby . Это означало меньшее переключение контекста между языками и меньше времени, затрачиваемого на повторное изучение Ruby каждый раз, когда мы писали E2E-тесты.
  • Мы разместили тесты вместе с кодом приложения , а не в общем репозитории Ruby. Мы больше не чувствовали себя зависимыми от неудачных тестов других команд и стали более непосредственными владельцами тестов E2E для наших функций в наших репозиториях.
  • Мы оценили возможность кроссбраузерного тестирования . С помощью WebdriverIO мы могли запускать тесты для различных возможностей или браузеров, таких как Chrome, Firefox и IE, хотя в основном мы сосредоточились на выполнении наших тестов для Chrome, поскольку более 80% наших пользователей посещали наше приложение через Chrome.
  • Мы порадовали возможностью интеграции со сторонними сервисами . В документации WebdriverIO поясняется, как интегрироваться со сторонними службами, такими как BrowserStack и SauceLabs, чтобы обеспечить поддержку нашего приложения на всех устройствах и во всех браузерах.
  • У нас была возможность выбрать собственного исполнителя тестов, репортера и сервисы . WebdriverIO не предписывал, что использовать, поэтому каждая команда взяла на себя смелость решать, использовать ли такие вещи, как Mocha и Chai или Jest и другие сервисы. Это также можно интерпретировать как аферу, поскольку команды начали отдаляться друг от друга, и нам потребовалось значительное количество времени, чтобы поэкспериментировать с каждым из вариантов, которые мы могли выбрать.
  • WebdriverIO API, интерфейс командной строки и документация были достаточно исправны для написания тестов и интеграции с Docker и CIC D. У нас могло быть много разных файлов конфигурации, группировка спецификаций, выполнение тестов через командную строку и написание тестов в соответствии с шаблоном объекта страницы. Однако документация могла бы быть более понятной, и нам пришлось копаться во множестве странных ошибок. Тем не менее, мы смогли преобразовать наши тесты из решения Ruby Selenium.

Мы добились прогресса во многих областях, которых нам не хватало в предыдущем решении Ruby Selenium, но мы столкнулись со многими препятствиями, которые мешали нам полностью использовать WebdriverIO, например:

  • Поскольку WebdriverIO все еще был основан на Selenium , мы столкнулись с множеством странных тайм-аутов, сбоев и ошибок, что напомнило нам о негативных воспоминаниях о нашем старом решении Ruby Selenium. Иногда наши тесты вообще зависали, когда мы выбирали много элементов на странице, и тесты работали медленнее, чем хотелось бы. Нам приходилось искать обходные пути для решения многих проблем с Github или избегать определенных методологий при написании тестов.
  • Общий опыт разработчиков был неоптимальным . В документации представлен некоторый общий обзор команд, но недостаточно примеров, чтобы объяснить все способы их использования. Мы избегали написания E2E-тестов на Ruby и, наконец, стали писать тесты на JavaScript или TypeScript, но WebdriverIO API немного сбивал с толку. Некоторыми распространенными примерами были использование $ vs. $$ для единственного и множественного числа элементов, $('...').waitForVisible(9000, true) для ожидания, пока элемент не станет видимым, и другие неинтуитивные команды. У нас было много ненадежных селекторов, и нам приходилось явно $(...).waitForVisible() для всего.
  • Отладочные тесты были чрезвычайно болезненными и утомительными для разработчиков и тестировщиков. Whenever tests failed, we only had screenshots, which would often be blank or not capturing the right moment for us to deduce what went wrong, and vague console error messages that did not point us in the right direction of how to solve the problem and even where the issue occurred. We often had to re-run the tests many times and stare closely at the Chrome browser running the tests to hopefully put things together as to where in the code our tests failed. We used things like browser.debug() but it often did not work or did not provide enough information. We gradually gathered a bunch of console error messages and mapped them to possible solutions over time but it took lots of pain and headache to get there.
  • WebdriverIO tests were tough to set up with Docker . We struggled with trying to incorporate it into Docker as there were many tutorials and ways to do things in articles online, but it was hard to figure out a way that worked in general. Hooking up 2 to 3 services together with all these configurations led to long trial and error experiments and the documentation did not guide us enough in how to do that.
  • Choosing the test runner, reporter, assertions, and services demanded lots of research time upfront . Since WebdriverIO was flexible enough to allow other options, many teams had to spend plenty of time to even have a solid WebdriverIO infrastructure after experimenting with a lot of different choices and each team can have a completely different setup that doesn't transfer over well for shared knowledge and reuse.

To summarize our WebdriverIO and STUI 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:

Moving On to Cypress

At the end of the day, our WebdriverIO tests were still flaky and tough to maintain. More time was still spent debugging tests in dealing with weird Selenium issues, vague console errors, and somewhat useful screenshots than actually reaping the benefits of seeing tests fail for when the backend or frontend encountered issues.

We appreciated cross-browser testing and implementing tests in JavaScript, but if our tests could not pass consistently without much headache for even Chrome, then it became no longer worth it and we would then simply have a STUI 2.0.

With WebdriverIO we still strayed from the crucial aspect of providing a way to write consistent, debuggable, maintainable, and valuable E2E automation tests for our frontend applications in our original goal. Overall, we learned a lot about integrating with Buildkite and Docker, using page objects, and outlining tests in a structured way that will transfer over to our final solution with Cypress.

If we felt it was necessary to run our tests in multiple browsers and against various third-party services, we could always circle back to having some tests written with WebdriverIO, or if we needed something fully custom, we would revisit the STUI solution.

Ultimately, neither solution met our main goal for E2E tests, so follow us on our journey in how we migrated from STUI and WebdriverIO to Cypress in part 2 of the blog post series.