[Перевод] Веб- и мобильная автоматизация в CI-среде

Я вернулся с очередным постом. На этот раз я напишу о процессе интеграции веб- и мобильных проектов автоматизации с облачным провайдером непрерывной интеграции, создании CI-среды и ее подключении к Slack. 

Эта статья является продолжением первой, в которой я рассказывал об основах Cucumber BDD с Selenium и Java. 

Без лишних слов, let«s CI, что я подготовил для этого поста.

Непрерывная интеграция

Для начала я расскажу о значении и преимуществах среды непрерывной интеграции.

Непрерывная интеграция (CI, Continuous integration) — это практика разработки программного обеспечения, при которой разработчики регулярно вносят изменения в код, которые автоматически проверяются на ошибки. Из-за большого количества требований и этапов этот процесс автоматизируется. Это делается для того, чтобы команды могли собирать, тестировать и упаковывать свои приложения надежным и повторяемым способом. Это сказал бы Google, если спросить у него, что такое CI. Но Google довольно формален, поэтому я выражу своими словами: «It is what it is» (читайте голосом мема).

Допустим, у вас есть автоматизированный тест, который проверяет функциональность входа в систему для высокопроизводительного iOS-приложения. Если вам нужно было бы проверить функциональность входа в систему, вам пришлось бы запускать и настраивать все вручную (возможно, каждый день). Если же вы интегрируете этот проект в CI-среду, вам не придется об этом беспокоиться — ваши тесты будут запускаться, выполняться и автоматически отправлять отчеты, что очень просто. Если задуматься, то даже название говорит само за себя — Continuous Integration.

Есть множество CI-инструментов, которые используют DevOps и инженеры, занимающиеся разработкой программного обеспечения. От TeamCity,  Jenkins,  Azure DevOps,  AWS CodePipeline и CircleCi до моих личных фаворитов — GitHub Actions и Gitlab CI. В этой статье я расскажу о GitHub Actions. Я считаю, что он очень похож на Gitlab CI, но я буду рассказывать о GitHub, поскольку проект из моей первой статьи уже находится на GitHub. Однако, если кому-то интересно, то для автоматизации тестирования я предпочитаю Gitlab CI.

GitHub Actions

GitHub Actions — это платформа непрерывной интеграции и непрерывного развертывания (доставки) (CI/CD), которая автоматизирует пайплайн сборки, тестирования и развертывания. Вы можете создавать рабочие процессы (workflows), которые будут собирать и тестировать каждый пулл-реквест в вашем репозитории, или развертывать объединенные пулл-реквесты по средам. В нашем случае GitHub Actions будет использоваться для сборки и запуска тестов по заранее определенному графику.

GitHub Actions работает на машинах с Linux, Windows или MacOS для запуска рабочих процессов. Мы будем использовать MacOS.

Скажем пару слов о безопасности. GitHub Actions предоставляет множество фичей безопасности, например, GitHub Secrets. GitHub Secrets — это зашифрованные переменные, которые можно создать в среде репозитория. Это значит, что любую переменную можно держать скрытой, зашифрованной и под паролем. Эта переменная может быть чем угодно, паролем среды или чем-то простым, как строка. В реальной жизни ID доступа к среде и пароль необходимо хранить в секрете. Здесь у нас этого не будет, поэтому я создам GitHub Secret для нашего Slack webhook, но об этом позже.

Облачные платформы для CI-тестирования

Каким бы неудачным ни было название, «CI Testing Cloud Providers» говорит само за себя. Облачные платформы для автоматизированного тестирования поставляют необходимую среду тестирования в соответствии с требованиями тестируемого приложения. В качестве среды может выступать веб-браузер на настольном компьютере или мобильном устройстве. Не будем забывать о приложениях для Android и iOS на соответствующих платформах. Это устройство обычно запускается на виртуальной машине (Virtual Machine, VM), которая уничтожается после каждого теста, что делает тесты непрерывной интеграции надежными.

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

  • Облако виртуальных мобильных устройств для мобильного или веб-тестирования

  • Реальные мобильные устройства для мобильного или веб-тестирования

  • CI интеграция с ежедневными выпусками и графиками

  • Отслеживание ошибок

  • Запись тестов с возможностью их воспроизведения

  • Параллельное тестирование

  • Отчетность и анализ

  • Отладка

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

Пришло время упомянуть некоторых из самых надежных приложений, представленных сейчас на рынке. Я начну с SauceLabs и BrowserStack. Они довольно похожи и оба предлагают множество функциональных возможностей. Также есть SonarCloud, Testsigma, CloudQA и многие другие.

В этой статье я расскажу о своем любимом и наиболее часто используемом инструменте — SauceLabs.

SauceLabs

SauceLabs, на мой взгляд, является лучшей платформой для автоматического тестирования. Он предоставляет все перечисленные функциональные возможности, имеет отличную документацию и работает над новыми интеграциями и сервисами. Услуги SauceLabs стоят недешево, но если вы работаете над серьезным проектом автоматизации, вам стоит обратить на него внимание. Если вас по каким-то причинам не устраивает Sauces, попробуйте BrowserStack.

Кроме того, я интегрирую свой локальный проект для работы в облаке SauceLabs. Что это значит? Это значит, что, когда я нажму кнопку «Запустить» на своей локальной машине, браузер не будет запускаться локально. Браузер запустится в облаке SauceLabs на только что созданной виртуальной машине, начнет запись видео, выведет сообщение в консоль, запустит тест и, наконец, выключит виртуальную машину. О чем еще можно просить!

Структура проекта

Для начала расскажу о структуре проекта и технологиях.

e793cf37506efdb777d1d1b5c81fe1cd.png

О чем же это нам говорит? Давайте разложим по полочкам.

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

Я подключу свой локальный проект к SauceLabs — и тесты будут выполняться не локально, а в облаке SauceLabs. Облако SauceLabs обеспечит передачу видео в реальном времени, воспроизведение видеозаписи и вывод сообщений консоль. Потрясающе.

Теперь давайте поговорим о GitHub. Как видно на изображении, GitHub выполняет довольно много тяжелой работы. Я создам workflow-файл, который будет запускать тесты на SauceLabs, и поставлю его в график (schedule). Кроме того, поскольку я использую Gradle и Cucumber в своем локальном проекте, GitHub предоставит консольный вывод Gradle и Cucumber. Наконец, через GitHub workflow-файл я создам подключение к Slack, чтобы видеть уведомления (pass/fail) сразу после завершения работы автоматических тестов. Теперь можно расслабиться и позволить тестам работать за вас.

Я считаю, что это все. Давайте начнем кодить!

Подключение к SauceLabs

Итак, мой локальный проект — это Selenium с Cucumber и Java. Когда мы добавим интеграцию с SauceLabs, наш Java-класс Driver (Hooks) будет выглядеть следующим образом:

public class Hooks {
   public static RemoteWebDriver driver;
   public static WebDriverWait wait;
   public Config config;

   @Before
   public void setup(Scenario scenario) throws IOException {

       config = new Config();
       String browserName = config.getBrowser();
       String username = System.getenv("your sauce username");
       String accessKey = System.getenv("your sauce access key");
       String sauceUrl = "https://username:accesskey@ondemand.region.saucelabs.com:443/wd/hub";
       MutableCapabilities capabilities;
       MutableCapabilities sauceOpts = new MutableCapabilities();

       switch (browserName) {

           case "chrome":
               capabilities = new ChromeOptions();
               capabilities.setCapability("browserName", browserName);
               capabilities.setCapability("browserVersion", "latest");
               capabilities.setCapability("platformName", "Windows 10");
               sauceOpts.setCapability("username", username);
               sauceOpts.setCapability("accessKey", accessKey);
               sauceOpts.setCapability("name", "CHROME TEST" + scenario.getName());
               sauceOpts.setCapability("screenResolution" , "1920x1080");
               capabilities.setCapability("sauce:options", sauceOpts);
               URL url = new URL(sauceUrl);
               driver = new RemoteWebDriver(url, capabilities);
               wait = new WebDriverWait(driver, Duration.ofSeconds(70));
               break;


           case "safari":
               capabilities = new SafariOptions();
               capabilities.setCapability("browserName", browserName);
               capabilities.setCapability("browserVersion", "14");
               capabilities.setCapability("platformName", "macOS 11");
               sauceOpts.setCapability("username", username);
               sauceOpts.setCapability("accessKey", accessKey);
               sauceOpts.setCapability("name", "SAFARI TEST: " + scenario.getName());
               sauceOpts.setCapability("screenResolution", "1600x1200");
               capabilities.setCapability("sauce:options", sauceOpts);
               url = new URL(sauceUrl);
               driver = new RemoteWebDriver(url, capabilities);
               wait = new WebDriverWait(driver, Duration.ofSeconds(70));
               break;

           case "chromeMac":
               capabilities = new ChromeOptions();
               capabilities.setCapability("browserName", "chrome");
               capabilities.setCapability("browserVersion", "latest");
               capabilities.setCapability("platformName", "macOS 12");
               sauceOpts.setCapability("username", username);
               sauceOpts.setCapability("accessKey", accessKey);
               sauceOpts.setCapability("name","CHROME MAC TEST: " + scenario.getName());
               sauceOpts.setCapability("screenResolution" , "1600x1200");
               capabilities.setCapability("sauce:options", sauceOpts);
               url = new URL(sauceUrl);
               driver = new RemoteWebDriver(url, capabilities);
               wait = new WebDriverWait(driver, Duration.ofSeconds(70));
               break;

           default:
               capabilities = new ChromeOptions();
               capabilities.setCapability("browserName", browserName);
               capabilities.setCapability("browserVersion", "latest");
               capabilities.setCapability("platformName", "Windows 10");
               sauceOpts.setCapability("username", username);
               sauceOpts.setCapability("accessKey", accessKey);
               sauceOpts.setCapability("name", "CHROME TEST: " + scenario.getName());
               sauceOpts.setCapability("screenResolution" , "1920x1080");
               capabilities.setCapability("sauce:options", sauceOpts);
               url = new URL(sauceUrl);
               driver = new RemoteWebDriver(url, capabilities);
               wait = new WebDriverWait(driver, Duration.ofSeconds(70));
               break;
       }
   }

  @After
   public void teardown (){
       driver.quit();
   }


   @AfterStep
   public void afterStep (Scenario scenario) {

       if(scenario.isFailed()){
           driver.executeScript("sauce:job-result=" + false);
       }
       else driver.executeScript("sauce:job-result=" + true);}

}

Кода много, поэтому я разобью его на части.

Грубо говоря, класс Hooks в основном предназначен для создания драйвера для SauceLabs. Драйвер больше не будет WebDriver, он будет RemoteWebDriver, потому что мы подключаемся к удаленному облаку SauceLabs через URL. URL состоит из имени пользователя SauceLabs, ключа доступа и региона. В моем примере эти значения скрыты из соображений безопасности.

Я использую хуки Cucumber «Before» и «After». Весь код внутри хука Before будет выполняться до запуска тестовых сценариев. Наконец, код в хуке After будет выполняться в конце прогона тестовых сценариев. Нужно, чтобы соединение с Sauce было установлено до запуска сценариев, поэтому я поместил его в хук Before.

После запуска тестовых сценариев нужно завершить тест и остановить драйвер, поэтому это находится в хуке After.

Хук AfterStep также предоставляется Cucumber. Этот хук очень важен. В этом хуке есть простой цикл «if», который переводит результаты сценария в состояние pass или fail. Это возможно благодаря тому, что Cucumber может выдавать результаты после каждого шага, так что мы просто добавляем скрипт драйвера для SauceLabs, и вуаля.

Возможности SauceLabs интуитивно понятны, поэтому я не буду вдаваться в подробности. Мы задаем названия сценариев, названия браузеров, версии браузеров, версии платформ, разрешения и так далее.

39e795957b2353c851733b8effa4655c.png

В самом начале я инициализировал класс Config. Что это такое? Config — это еще один класс Java, который я создал для навигации между браузерами. Почему? Потому что, когда автоматические тесты запускаются по расписанию, они тестируют все необходимые браузеры. Как я его создал? Ну, я сделал это по-своему. Есть множество вариантов, но я покажу вам свой.

Чтобы переключаться между браузерами, нужно создать файл, в который вы будете записывать свойства, в нашем случае — название браузера. Итак, я создал файл в корне проекта и назвал его gradle.properties. Наконец, создал новый Java-класс под названием, как вы уже догадались,  Config.java. Он выглядит примерно так:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class Config{

   public  Properties properties;

   public Config() {
       properties = new Properties();
       try {
           FileInputStream inputStream = new FileInputStream("gradle.properties");
           properties.load(inputStream);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   public  String getBrowser() {
       return properties.getProperty("browser");
   }
}

Как видите, этот класс прост и эффективен. Я использовал метод FileInputStream, указав на ранее созданный файл gradle.properties. Также я создал динамический метод String под названием getBrowser, который будет получать значение «browser» из gradle.properties. Пока что файл gradle.propertiesостанется пустым, потому что мы не хотим запускать наши тесты локально, поэтому мы будем записывать значения браузера с помощью задачи Gradle. Подробнее об этом позже.

Gradle

Для запуска тестов я использую Gradle task. Почему? Ну, потому что ее довольно легко реализовать в среде CI/CD (запускать тесты можно просто с помощью одной команды CLI). Теперь позвольте показать, как я пишу свойства браузера перед запуском тестов. Мой Gradle task для запуска тестов в Safari выглядит примерно так:

task runTestsSafari (type: WriteProperties) {
   outputFile = file('gradle.properties')
   property 'browser', 'safari'
   dependsOn assemble, testClasses
   doLast {
       javaexec {
           main = "io.cucumber.core.cli.Main"
           classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
           args = [
                   '--plugin', 'pretty',
                   '--plugin', 'html:target/cucumber-report.html',
                   '--glue', 'stepMethods',
                   'src/test/resourcesSafari']
       }
   }
}

Тип задачи установлен на WriteProperties, потому что, ну, вы знаете почему. Нам нужно записать свойство и значение «browser». Итак, первый блок кода запишет «browser=safari» в файлgradle.properties. После этого класс Hooks войдет в цикл switch и выберет «case = safari». Теперь остается только создать столько задач, сколько я захочу, для стольких браузеров и платформ, сколько потребуется.

GitHub Actions

Давайте перейдем к той части, которую мы все так долго ждали — я покажу вам, как настроить workflow-файл на GitHub. Прежде всего, загружу свой проект на GitHub. После этого создам новую папку в корне проекта под названием .github. В папку .github я добавлю еще одну папку под названием workflows. Наконец, добавлю пустой файл .ymlи назову его main.

Внутри файла main.ymlпишу примерно это:

name: Java CI

on:
 schedule:
   - cron: '45 9 * * 1-5'

jobs:
 build:
   runs-on: ubuntu-latest

   steps:
     - uses: actions/checkout@v3
     - name: Set up JDK 17
       uses: actions/setup-java@v3
       with:
         java-version: '17'
         distribution: 'adopt'
     - name: Validate Gradle wrapper
       uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b

- name: Build with Gradle
 uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
 with:
   arguments: runTestsSafari

Давайте разберем на части. В первой строке я просто дал своему workflow основное имя — Java CI. Следующие строки показывают, что я поместил этот workflow в schedule cron. Больше о синтаксисе cron можно узнать на сайте crontab.guru. Мой график (schedule) установлен каждый день с понедельника по пятницу в 9:45 утра. Будьте внимательны с часовыми поясами, потому что GitHub использует UTC. Я даю своему пайплайну задание (сборку) и настраиваю его на запуск на последней версии Ubuntu. В разделе steps я указал несколько формальных технологий и версий Java для использования пайплайном. После проверки обёртки Gradle мы, наконец, можем начать сборку с помощью Gradle и запустить задание. Настроенные таким образом, тесты запускаются ежедневно в 9:45 и тестируют Safari. На данный момент я вижу вывод Gradle и Cucumber, предоставленный GitHub, и вижу видео запуска теста на SauceLabs. Но мне этого недостаточно, я слишком ленив. Давайте соединим все со Slack, чтобы мгновенно получать уведомления о прохождении или провале тестов.

Интеграция Slack с GitHub Actions

Чтобы связать проект GitHub со Slack, я буду использовать определенный runner. Я пробовал много разных, но мне понравился только один — от компании rtCamp, его можно найти здесь.

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

Далее откройте GitHub и нажмите на secrets. Нам нужно создать новый секрет на GitHub. Итак, нажимаем создать новый секрет и называем его; в моем случае это SLACK_WEBHOOK_URL. Вставьте скопированное значение вебхука из предыдущего шага. После сохранения GitHub зашифрует вебхук и сохранит его, как вы уже догадались, в секрете.

И наконец, теперь мы можем отредактировать GitHub workflow-файл в формате .yml:

- name: Send success
 if: success()
 uses: rtCamp/action-slack-notify@v2
 env:
   SLACK_CHANNEL: automated-tests-notifications
   SLACK_LINK_NAMES: true
   SLACK_COLOR: ${{ job.status }}
   SLACK_ICON: https://github.com/rtCamp.png?size=48
   SLACK_TITLE: Your tests have successfully passed
   SLACK_USERNAME: roBot
   SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL}}

- name: Send failure
 if: failure()
 uses: rtCamp/action-slack-notify@v2
 env:
   SLACK_CHANNEL: automated-tests-notifications
   SLACK_LINK_NAMES: true
   SLACK_COLOR: ${{ job.status }}
   SLACK_ICON: https://github.com/rtCamp.png?size=48
   SLACK_TITLE: Danger, danger, automated tests have failed
   SLACK_USERNAME: roBot
   SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL}}

Это может показаться очевидным, но у меня было много проблем со связью между GitHub Actions и Slack. Постарайтесь сделать так же, как и я, потому что даже самые незначительные изменения могут ее нарушить.

Как видите, я создал два if. Оба if обрабатывают вывод Gradle runner. Теперь переходим к самой лучшей части Gradle. Не будем забывать, что наши тесты выполняются с помощью задач Gradle. Поэтому, когда задача Gradle завершает работу, она создает выходные данные, которые также может быть прочитаны с помощью нашего yaml файла. Все просто, но мне потребовалось время, чтобы понять, насколько это мощный и полезный инструмент. Теперь .yml-файл знает, какой цикл if вводить, основываясь на результатах тестирования. Отлично.

Как видите, здесь также присутствует SLACK_WEBHOOK. Мы можем получить любое зашифрованное значение (секрет) из наших секретов GitHub, используя следующий синтаксис:  " class="formula inline">{{secrets.YOUR_SECRET_NAME}}.

Зная это, мы можем использовать GitHub secrets для сокрытия любой конфиденциальной информации. Попробуйте самим.

Заключение

Это было еще одно краткое объяснение того, как соединить все наши технологии, чтобы они работали вместе как одна большая машина.

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

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

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

— Познакомимся с системой автоматизации развёртывания и управления приложениями Docker.
— Рассмотрим основные термины и определения экосистемы Docker.
— Научимся использовать некоторые базовые команды Docker CLI. Попробуем «упаковать» тесты в Docker-контейнер.

Записаться на урок можно на странице онлайн-курса.

© Habrahabr.ru