Идеи по настройке, организации и объединению ваших тестов Cypress #frontend@twiliosendgrid

Опубликовано: 2020-12-15

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

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

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

В противном случае давайте продолжим и сначала познакомим вас с некоторыми советами по настройке Cypress.

Настройка вашего cypress.json

В файле cypress.json вы можете установить всю конфигурацию для ваших тестов Cypress, например базовые тайм-ауты для ваших команд Cypress, переменные среды и другие свойства.

Вот несколько советов, которые вы можете попробовать в своей конфигурации:

  • Точно настройте таймауты базовых команд, чтобы вам не приходилось всегда добавлять { timeout: timeoutInMs } во все ваши команды Cypress. Повозитесь с числами, пока не найдете правильный баланс для таких параметров, как «defaultCommandTimeout», «requestTimeout» и «responseTimeout».
  • Если в вашем тесте используются фреймы iframe, вам, скорее всего, нужно установить для параметра chromeWebSecurity значение false, чтобы вы могли получить доступ к фреймам из разных источников в своем приложении.
  • Попробуйте заблокировать сторонние сценарии маркетинга, аналитики и ведения журналов при выполнении тестов Cypress, чтобы повысить скорость и не запускать нежелательные события. Вы можете легко настроить список запретов с их свойством «blacklistHosts» (или свойством «blockHosts» в Cypress 5.0.0), взяв массив строковых глобусов, соответствующих путям сторонних хостов.
  • Отрегулируйте размеры окна просмотра по умолчанию с помощью «viewportWidth» и «viewportHeight», когда вы открываете графический интерфейс Cypress, чтобы упростить просмотр.
  • Если в Docker есть проблемы с памятью для тяжелых тестов или вы хотите помочь сделать вещи более эффективными, вы можете попробовать изменить «numTestsKeptInMemory» на меньшее число, чем по умолчанию, и установить для «videoUploadOnPasses» значение false, чтобы сосредоточиться на загрузке видео для неудачного теста. работает только.

С другой стороны, после настройки вашей конфигурации Cypress вы также можете добавить TypeScript и лучше печатать свои тесты Cypress, как мы это сделали в этом сообщении в блоге. Это особенно полезно для автоматического завершения, предупреждений о типах или ошибок при вызове и цепочке функций (таких как cy.task('someTaskPluginFunction) , Cypress.env('someEnvVariable') или cy.customCommand() ).

Вы также можете поэкспериментировать с настройкой базового файла cypress.json и загрузкой отдельного файла конфигурации Cypress для каждой тестовой среды, например staging.json , когда вы запускаете разные скрипты Cypress в package.json . Документация Cypress поможет вам разобраться с этим подходом.

Организация вашей папки Cypress

Cypress уже устанавливает папку cypress верхнего уровня с управляемой структурой, когда вы впервые запускаете Cypress в своей кодовой базе. Это включает в себя другие папки, такие как integration для ваших файлов спецификаций, fixtures для файлов данных JSON, plugins для ваших cy.task(...) и другие конфигурации, а также папку support для ваших пользовательских команд и типов.

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

Мы создали отдельную папку pages , разделенную по имени функции, например «SenderAuthentication», чтобы хранить все объекты страницы под маршрутом /settings/sender_auth , такие как «DomainAuthentication» (/settings/sender_auth/domains/**/*) и «LinkBranding». (/settings/sender_auth/links/**/*). В нашей папке plugins мы также сделали то же самое, организовав все файлы плагинов cy.task(...) для определенной функции или страницы в той же папке для аутентификации отправителя, и мы использовали тот же подход для нашей папки integration для файлы спецификаций. Мы расширили наши тесты до сотен файлов спецификаций, объектов страниц, плагинов и других файлов в одной из наших кодовых баз, и навигация по ним проста и удобна.

Вот схема того, как мы организовали папку cypress .

Еще одна вещь, которую следует учитывать при организации вашей папки integration (где хранятся все ваши спецификации), — это возможное разделение файлов спецификаций в зависимости от приоритета теста. Например, у вас могут быть все тесты с наивысшим приоритетом и значением в папке «P1», а тесты со вторым приоритетом — в папке «P2», поэтому вы можете легко запускать все тесты «P1», установив параметр --spec , например --spec 'cypress/integration/P1/**/*' .

Создайте иерархию папок спецификаций, которая работает для вас, чтобы вы могли легко группировать спецификации не только по страницам или функциям, таким как --spec 'cypress/integration/SomePage/**/*' , но и по некоторым другим критериям, таким как приоритет, продукт , или окружающая среда.

Объединение селекторов элементов

При разработке компонентов React нашей страницы мы обычно добавляем некоторый уровень модульных и интеграционных тестов с помощью Jest и Enzyme. Ближе к концу разработки функции мы добавляем еще один уровень тестов Cypress E2E, чтобы убедиться, что все работает с серверной частью. Как модульные тесты для наших компонентов React, так и объекты страницы для наших тестов Cypress E2E требуют селекторов для компонентов/элементов DOM на странице, с которыми мы хотим взаимодействовать и утверждать.

Когда мы обновляем эти страницы и компоненты, есть вероятность возникновения дрейфа и ошибок из-за необходимости синхронизировать несколько мест от селекторов модульных тестов до объекта страницы Cypress и самого фактического кода компонента. Если бы мы полагались исключительно на имена классов, связанные со стилями, было бы сложно не забыть обновить все места, которые могут сломаться. Вместо этого мы добавляем «data-hook», «data-testid» или любой другой атрибут с постоянным именем «data-*» к определенным компонентам и элементам на странице, которые мы хотим запомнить, и пишем для них селектор в наших тестовых слоях.

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

Для папки верхнего уровня каждой страницы мы создадим файл hooks.ts , который управляет и экспортирует объект с удобочитаемым именем ключа для элемента и фактическим строковым селектором CSS для элемента в качестве значения. Мы будем называть их «селекторами чтения», так как нам нужно читать и использовать форму селектора CSS для элемента в таких вызовах, как wrapper.find(“[data-hook='selector']”) в наших модульных тестах или Cypress. cy.get(“[data-hook='selector']”) в объектах нашей страницы. Тогда эти вызовы будут выглядеть чище, как wrapper.find(Selectors.someElement) или cy.get(Selectors.someElement) .

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

Точно так же мы также экспортируем объекты с читаемым именем ключа для элемента и с таким объектом, как { “data-hook”: “selector” } в качестве значения. Мы будем называть их «селекторами записи», так как нам нужно записать или распространить эти объекты на компонент React в качестве реквизита, чтобы успешно добавить атрибут «перехватчик данных» к базовым элементам. Целью было бы сделать что-то вроде этого и фактический элемент DOM под ним — при условии, что реквизиты правильно переданы элементам JSX — также будет иметь установленный атрибут «data-hook=».

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

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

Для функции генератора селекторов чтения мы пройдемся по свойствам входного объекта и сформируем строку селектора CSS [data-hook=”selector”] для каждого имени элемента. Если соответствующее значение ключа равно null , мы предполагаем, что имя элемента во входном объекте будет таким же, как значение «перехватчика данных», например { someElement: null } => { someElement: '[data-hook=”someElement”] } . В противном случае, если соответствующее значение ключа не равно нулю, мы можем переопределить это значение «перехватчика данных», например { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' } .

Для функции генератора селектора записи мы пройдемся по свойствам входного объекта и сформируем объекты { “data-hook”: “selector” } для каждого имени элемента. Если соответствующее значение ключа равно null , мы предполагаем, что имя элемента во входном объекте будет таким же, как значение «перехватчика данных», например { someElement: null } => { someElement: { “data-hook”: “someElement” } } . В противном случае, если соответствующее значение ключа не равно null , мы можем переопределить это значение «перехватчика данных», например { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' } .

Используя эти две функции генератора, мы создаем наши объекты селектора чтения и селектора записи для страницы и экспортируем их для повторного использования в наших модульных тестах и ​​объектах страницы Cypress. Еще одним преимуществом является то, что эти объекты типизированы таким образом, что мы не можем случайно сделать Selectors.unknownElement или WriteSelectors.unknownElement в наших файлах TypeScript. Перед экспортом наших селекторов чтения мы также разрешаем добавлять сопоставления дополнительных элементов и селекторов CSS для сторонних компонентов, над которыми у нас нет контроля. В некоторых случаях мы не можем добавить атрибут «перехватчик данных» к определенным элементам, поэтому нам все равно нужно выбирать по другим атрибутам, идентификаторам и классам и добавлять дополнительные свойства к объекту селектора чтения, как показано ниже.

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

Объединение тайм-аутов

Одна вещь, которую мы заметили после написания многих тестов Cypress, заключалась в том, что время ожидания наших селекторов для определенных элементов истекало и занимало больше времени, чем значения времени ожидания по умолчанию, установленные в нашем cypress.json , такие как «defaultCommandTimeout» и «responseTimeout». Мы возвращались и настраивали определенные страницы, которые требовали более длительных тайм-аутов, но со временем количество произвольных значений тайм-аута росло, и поддерживать его становилось все труднее для крупномасштабных изменений.

В результате мы объединили наши тайм-ауты в объекте, начинающемся выше нашего «defaultCommandTimeout», который находится где-то в диапазоне от пяти до десяти секунд, чтобы покрыть большинство общих тайм-аутов для наших селекторов, таких как cy.get(...) или cy.contains(...) . Помимо этого тайм-аута по умолчанию, мы бы увеличили масштаб до «короткий», «средний», «длинный», «xlong» и «xxlong» в объекте тайм-аута, который мы можем импортировать в любое место в наших файлах для использования в наших командах Cypress, таких как cy.get(“someElement”, { timeout: timeouts.short }) или cy.task('pluginName', {}, { timeout: timeouts.xlong }) . После замены наших тайм-аутов этими значениями из нашего импортированного объекта у нас есть одно место для обновления, чтобы увеличить или уменьшить время, необходимое для определенных тайм-аутов.

Пример того, как вы можете легко начать с этого, показан ниже.

Подведение итогов

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

По мере изменения пользовательского интерфейса вы можете использовать какой-либо типизированный объект (например, селекторы чтения и записи), чтобы поддерживать селекторы атрибутов «data-» для ключевых элементов каждой страницы, которые вы хотели бы использовать или взаимодействовать в своих модульных тестах и ​​Cypress. тесты. Если вы обнаружите, что начинаете применять слишком много произвольных значений для таких вещей, как значения времени ожидания для ваших команд Cypress, возможно, пришло время настроить объект, заполненный масштабированными значениями, чтобы вы могли обновлять эти значения в одном месте.

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

Хотите больше сообщений о Cypress? Взгляните на следующие статьи:

  • Что следует учитывать при написании E2E-тестов
  • 1000-футовый обзор написания тестов Cypress
  • TypeScript — все, что нужно для ваших тестов Cypress
  • Работа с потоками электронной почты в тестах Cypress
  • Интеграция тестов Cypress с Docker, Buildkite и CICD