admin 30 мая 2019

Тестируем мобильные приложения с AWS Device Farm

Задумывались над автоматизацией тестирования? Узнайте, как использовать общедоступное облако AWS Device Farm и интегрировать в процесс сборки.


Тестируем мобильные приложения с AWS Device Farm

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

Знакомо? ;)

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

В этой статье используем Java, Cucumber и Appium, но концепции применимы и к другим технологиям.

☕ Подтянуть свои знания по Java вы можете на нашем телеграм-канале «Библиотека Java для собеса»


Выполнение на стороне сервера

При проведении тестов AWS (как минимум в общедоступном облаке) использует выполнение на стороне сервера. Это значит, что тестовый код работает на их инфраструктуре. В качестве альтернативы другие провайдеры, такие как Browserstack или Saucelabs, применяют выполнение на стороне клиента, когда тестовый код запускается на компьютере пользователя и взаимодействует с облачной службой через веб-сервис.

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

Подход AWS слегка сложнее, так как требует дополнительных шагов: упаковки и загрузки всего необходимого для запуска тестов на серверах. Это связано с некоторыми проблемами и ловушками, о которых рассказываем.

Выполнение тестов через веб-консоль AWS Device Farm

Самый простой способ запустить тесты в AWS Device Farm – через веб-консоль. Это утомительный процесс для частого запуска, поэтому скоро поговорим об использовании API. Но знакомство с веб-консолью даёт понимание этапов процесса.

Войдите в AWS и откройте панель управления Device Farm:

aws device farm

Создайте новый проект:

aws device farm

Первый запуск:

aws device farm

Поскольку тестируем нативное Android-приложение (StackOverflow), наш первый шаг – загрузить APK-файл:

Тестируем мобильные приложения с AWS Device Farm

Выберите тип теста. Используйте Appium Java JUnit:

Тестируем мобильные приложения с AWS Device Farm

С использованием Maven создаём тестовый пакет в виде zip-файла, следуя инструкциям в документации. Не пугайтесь утверждения, что поддерживается только Java 8. Поговорим об этом позже. И, конечно, вместо этого будем использовать Java 12.

В этой демонстрации работаем с фреймворком JustTestLah! для тестирования с использованием Java, Cucumber и Appium.

Загрузите тестовый пакет по ссылке или создайте демо самостоятельно с помощью Maven (zip-файл будет создан в justtestlah-demos/target):

git clone git@github.com:martinschneider/justtestlah.git
mvn -f justtestlah/pom.xml -pl justtestlah-demos package -Paws -Djusttestlah.propertes=justtestlah-demos/demos/stackoverflow_aws.properties

После загрузки zip в AWS всплывает вопрос: использовать стандартную среду или создать пользовательскую? Выбираем последнее.

Спецификация теста – это YAML-файл, в котором содержится список команд оболочки, что запускаются на разных этапах выполнения теста. Это даёт некоторый контроль над способом выполнения тестов в AWS.

aws device farm

Пока что замените спецификацию по умолчанию следующим:

version: 0.1

phases:
  install:
    commands:
      # AWS пока поддерживает только Java 8, поэтому устанавливаем Java 12 сами
      # https://forums.aws.amazon.com/thread.jspa?threadID=299604
      - wget -q https://download.java.net/java/GA/jdk12/GPL/openjdk-12_linux-x64_bin.tar.gz
      - tar -xzf openjdk-12_linux-x64_bin.tar.gz
      - export JAVA_HOME=$(pwd)/jdk-12
      - export PATH=$(pwd)/jdk-12/bin:$PATH
      - which java
      - java -version

      # Требуется для распознавания изображений, но AWS пока не поддерживает (https://forums.aws.amazon.com/thread.jspa?threadID=300720)
      #- npm i -g opencv4nodejs

      - export APPIUM_VERSION=1.9.1
      - avm $APPIUM_VERSION

  pre_test:
    commands:
      - echo "whoami `whoami`"
      - aws --version

      # Запуск сервера Appium
      - echo "Starting appium server"
      - >-
        appium --log-timestamp --chromedriver-executable $DEVICEFARM_CHROMEDRIVER_EXECUTABLE  >> $DEVICEFARM_LOG_DIR/appiumlog.txt 2>&1 &
      - >-
        start_appium_timeout=0;
        while [ true ];
        do
            if [ $start_appium_timeout -gt 30 ];
            then
                echo "appium server never started in 30 seconds. Exiting";
                exit 1;
            fi;
            grep -i "Appium REST http interface listener started on 0.0.0.0:4723" $DEVICEFARM_LOG_DIR/appiumlog.txt >> /dev/null 2>&1;
            if [ $? -eq 0 ];
            then
                echo "Appium REST http interface listener started on 0.0.0.0:4723";
                break;
            else
                echo "Waiting for appium server to start. Sleeping for 1 second";
                sleep 1;
                start_appium_timeout=$((start_appium_timeout+1));
            fi;
        done;
  test:
    commands:
      # Подготовка тестового кода
      - cd $DEVICEFARM_TEST_PACKAGE_PATH
      - echo "Extracting JAR files"
      - mkdir classes
      - cd classes
      # Распаковываем всё в `classes` (хак!)
      - unzip -o ../\*.jar
      - cd ..
      # Копируем файлы объектов в `src/test/resources` (поэтому не нужно изменять параметр `features.directory`)
      - mkdir -p src/test/resources/features
      - cp -r classes/features src/test/resources
      # Удаляем `justtestlah.properties` из classpath, чтобы избежать конфликтов свойств
      - rm classes/justtestlah.properties

      - echo "Writing justtestlah.properties"
      # Кодируем в base64 конфигурацию запуска (`justtestlah.properties`) в
      # этот шаблон (смотрите
      # io.github.martinschneider.justtestlah.awsdevicefarm.TestSpecFactory).
      # При выполнении декодируем её обратно в файл, который затем передаётся на исполнение
      # теста. Альтернативой будет поместить его
      # в тестовый пакет, но мы не хотим повторно загружать его для каждого 
      # изменения конфигурации.
      - echo "I2p1c3R0ZXN0bGFoIHByb3BlcnRpZXMKI1N1biBNYXkgMDUgMDk6MTY6MTggU0dUIDIwMTkKZmVhdHVyZXMuZGlyZWN0b3J5PXNyYy90ZXN0L3Jlc291cmNlcy9mZWF0dXJlcy9zdGFja292ZXJmbG93CnBsYXRmb3JtPWFuZHJvaWQKb3BlbmN2Lm1vZGU9c2VydmVyCmN1Y3VtYmVyLnJlcG9ydC5kaXJlY3Rvcnk9dGFyZ2V0L3JlcG9ydC9jdWN1bWJlcgpwYWdlcy5wYWNrYWdlPWlvLmdpdGh1Yi5tYXJ0aW5zY2huZWlkZXIuanVzdHRlc3RsYWguZXhhbXBsZXMuc3RhY2tvdmVyZmxvdy5wYWdlcwp0YWdzPW5vdCBAb3BlbmN2CnN0ZXBzLnBhY2thZ2U9aW8uZ2l0aHViLm1hcnRpbnNjaG5laWRlci5qdXN0dGVzdGxhaC5leGFtcGxlcy5zdGFja292ZXJmbG93LnN0ZXBzCmFuZHJvaWQuYXBwQWN0aXZpdHk9Y29tLnN0YWNrZXhjaGFuZ2Uuc3RhY2tvdmVyZmxvdy5NYWluQWN0aXZpdHkKYW5kcm9pZC5hcHBQYWNrYWdlPWNvbS5zdGFja2V4Y2hhbmdlLnN0YWNrb3ZlcmZsb3cKbW9iaWxlLmFwcGl1bVVybD1odHRwXDovLzEyNy4wLjAuMVw6NDcyMy93ZC9odWIK" | base64 --decode > justtestlah.properties

      # Устанавливаем путь к пакету приложения (нас не волнует платформа, мы 
      # просто устанавливаем оба и игнорируем остальное).
      - echo "android.appPath=$DEVICEFARM_APP_PATH" >> justtestlah.properties
      - echo "ios.appPath=$DEVICEFARM_APP_PATH" >> justtestlah.properties

      # При выполнении кода на AWS `cloudprovider = local` 
      # Только когда запускаем тесты на нашей машине, устанавливаем
      # `cloudprovider=aws`.
      - echo "cloudprovider=local" >> justtestlah.properties
      - cat justtestlah.properties

      # Наконец...
      - echo "Executing test scenarios"
      - java -Dappium.screenshots.dir=$DEVICEFARM_SCREENSHOT_PATH -Djusttestlah.properties=$(pwd)/justtestlah.properties -Dlogback.configurationFile=classes/logback-aws.xml -cp classes:dependency-jars/* org.junit.runner.JUnitCore TestRunner

  post_test:
    commands:

artifacts:
  - $DEVICEFARM_LOG_DIR

К подробностям этой конфигурации вернёмся позже, а пока сохраните и переходите к выбору устройства:

aws device farm

AWS использует концепцию под названием пулы устройств. Каждое выполнение теста будет запускать все тесты на всех устройствах в пуле. Для начала хватит одного устройства, поэтому создаём новый пул устройств с единственным Google Pixel:

aws device farm

Далее следует шаг «Укажите состояние устройства» (Specify device state), который пока пропускаем (оставляем значения по умолчанию). На последней странице видим запуск тестов. Сократим таймаут, чтобы не тратить драгоценные минуты устройства, если дело пойдёт не так:

aws device farm

🧩☕ Интересные задачи по Java для практики можно найти на нашем телеграм-канале «Библиотека задач по Java»


Запуск тестов в AWS Device Farm

Запускайте выполнение теста:

aws device farm

Видим запуск на информационной панели. Нажимайте и выбирайте тестовое устройство (Google Pixel), чтобы просмотреть логи в реальном времени и прямую трансляцию устройства (настройка займёт пару минут):

aws device farm

Тестируем сценарий:

Feature: Search and tags 
@web @android @ios
Scenario Outline: Use the search function 
	Given I am on the homepage 
	When I search for "<tag>"
	And I select the first question 
	Then the question is tagged with "<tag>"
	Examples:
		| tag |
		| selenium |
		| appium |

Если пойдёт по плану, вы получите сообщение об успешном прохождении теста на странице результатов:

aws device farm

Примечание: обратите внимание на продолжительность выполнения теста. Видим немалые накладные расходы на запуск и завершение устройства (и на выполнение спецификации теста и инициализации WebDriver), которые стоит учитывать при планировании выполнения теста. Из-за этих издержек AWS, как правило, гораздо лучше подходит для запуска больших наборов тестов за один раз, чем меньших за пару прогонов. В некоторой степени это справедливо для всех облачных решений, но размер накладных расходов зависит от провайдера. И, следовательно, играет роль при выборе.

Тестируем мобильные приложения с AWS Device Farm

Что дальше?

Поздравляем! Вы справились с первыми тестами Appium на AWS Device Farm. Рассмотрим некоторые проблемы и то, как использование специальной тестовой спецификации облегчает жизнь. Затем покажем, как использовать API для автоматизации составления расписания тестов.

1. Повторно загружаем тестовый пакет после каждого изменения

Удобно повторно использовать тестовые пакеты, уже загруженные в AWS Device Farm. Останется только упаковать и загрузить их снова, если будут изменения. В теории это звучит нормально, однако эти пакеты включают в себя всё необходимое для запуска тестов. Не только изменения во фреймворке или зависимостях, а также изменения в тестах, тестовых данных или тестовой конфигурации. Изменение одного параметра (например, указание тестов для запуска) требует нового тестового пакета.

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

Примитивный подход к решению этой проблемы в Device Farm заключается в создании 10 тестовых пакетов для каждого поднабора тестов, каждый из которых настроен на выполнение 10 различных тестов. Однако, для этого требуется упаковать и загрузить несколько больших, почти одинаковых zip-файлов. Теперь предположим, что в следующий раз захотим распределить тесты по 20 устройствам или выполнить другой набор тестов. Придётся постоянно создавать и загружать новые тестовые пакеты заново.

Так разработчики использовали Device Farm, прежде чем появились пользовательские среды, и это было болезненно. Постоянная сборка и загрузка zip-файлов размером больше 100 МБ добавляла дополнительных хлопот к процессу сборки.

Пользовательские среды для спасения

Если ещё раз посмотреть на спецификацию пользовательской среды, приведенную выше, увидим, как с помощью этого инструмента снять ограничение. Информация в кодировке base64 включает нашу тестовую конфигурацию. Благодаря этому, чтобы настроить запуск тестов, загружаем относительно маленький YAML-файл (спецификацию теста), а не заново создаём гораздо больший тестовый пакет. Кодируем конфигурацию при создании тестовой спецификации. При выполнении декодируем её обратно в файл, который затем передаётся на исполнение в качестве параметра.

- echo "I2p1c3R0ZXN0bGFoIHByb3BlcnRpZXMKI1N1biBNYXkgMDUgMDk6MTY6MTggU0dUIDIwMTkKZmVhdHVyZXMuZGlyZWN0b3J5PXNyYy90ZXN0L3Jlc291cmNlcy9mZWF0dXJlcy9zdGFja292ZXJmbG93CnBsYXRmb3JtPWFuZHJvaWQKb3BlbmN2Lm1vZGU9c2VydmVyCmN1Y3VtYmVyLnJlcG9ydC5kaXJlY3Rvcnk9dGFyZ2V0L3JlcG9ydC9jdWN1bWJlcgpwYWdlcy5wYWNrYWdlPWlvLmdpdGh1Yi5tYXJ0aW5zY2huZWlkZXIuanVzdHRlc3RsYWguZXhhbXBsZXMuc3RhY2tvdmVyZmxvdy5wYWdlcwp0YWdzPW5vdCBAb3BlbmN2CnN0ZXBzLnBhY2thZ2U9aW8uZ2l0aHViLm1hcnRpbnNjaG5laWRlci5qdXN0dGVzdGxhaC5leGFtcGxlcy5zdGFja292ZXJmbG93LnN0ZXBzCmFuZHJvaWQuYXBwQWN0aXZpdHk9Y29tLnN0YWNrZXhjaGFuZ2Uuc3RhY2tvdmVyZmxvdy5NYWluQWN0aXZpdHkKYW5kcm9pZC5hcHBQYWNrYWdlPWNvbS5zdGFja2V4Y2hhbmdlLnN0YWNrb3ZlcmZsb3cKbW9iaWxlLmFwcGl1bVVybD1odHRwXDovLzEyNy4wLjAuMVw6NDcyMy93ZC9odWIK" | base64 --decode > justtestlah.properties
- java -Djusttestlah.properties=$(pwd)/justtestlah.properties -Dlogback.configurationFile=classes/logback-aws.xml -cp classes:dependency-jars/* org.junit.runner.JUnitCore TestRunner

Когда аргументов мало, это лишнее, но для большего набора значений конфигурации строка в кодировке base64 – надёжный и элегантный способ, который также избавляет от головной боли при экранировании специальных символов в YAML.

JustTestLah! использует Cucumber, поэтому, например, запустим разные сценарии, передав отдельные теги Cucumber:

platform=android
tags=@regression and @stable and not @skip

или же

platform=ios
tags=@regression and @unstable and @skip

Тестовый пакет одинаковый для каждого запуска, изменяются только спецификации теста.

Дальнейшие улучшения

Пойдём дальше и избавимся от тестового пакета. Вместо этого настроим пользовательскую среду для получения необходимого кода и конфигурации из внешнего источника и соберём его целиком на экземпляре AWS.

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

2. Контролируем среду исполнения

Когда выполняем тесты в своей инфраструктуре, то полностью контролируем используемые инструменты и их версии. С AWS Device Farm это не так просто. Среда по умолчанию по-прежнему использует Java 8 (отрыв в четыре выпуска от текущего JDK 12). У других библиотек схожие проблемы, но для Java доступно исправление. Загрузим и установим его, добавив строки в конфигурацию среды:

phases:
  install:
    commands:
      # AWS пока поддерживает только Java 8, поэтому устанавливаем Java 12 сами
      # https://forums.aws.amazon.com/thread.jspa?threadID=299604
      - wget -q https://download.java.net/java/GA/jdk12/GPL/openjdk-12_linux-x64_bin.tar.gz
      - tar -xzf openjdk-12_linux-x64_bin.tar.gz
      - export JAVA_HOME=$(pwd)/jdk-12
      - export PATH=$(pwd)/jdk-12/bin:$PATH
      - which java
      - java -version

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

Выполнение тестов через API AWS Device Farm

Теперь, когда прошли этапы создания теста вручную, посмотрим, как использовать превосходный AWS Java SDK (также есть SDK для других языков программирования) для автоматизации процесса.

Для этого нужно создать пользователя AWS с доступом к Device Farm и сгенерировать ключ доступа и секретный ключ. Рекомендуем также настроить интерфейс командной строки AWS и сконфигурировать вызовом:

aws configure

Команда попросит доступ и секретные ключи, сгенерированные раньше. В качестве региона установим us-west-2. Ключи будут храниться в домашнем хранилище (в папке ~/.aws/credentials), и дефолтный поставщик учётных данных, используемый в SDK, заберёт их оттуда. Будьте осторожны, храните эти данные как можно дальше от исходного кода.

Как только это будет сделано, посмотрим на код JAVA. Полный исход хранится на GitHub. Для этой демонстрации вызываем AWS из класса JUnit (смотрите метод run). Обратите внимание, что вместо пула устройств используем концепцию, называемую фильтрами устройств, для определения используемого тестового устройства.

package io.github.martinschneider.justtestlah.awsdevicefarm;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.devicefarm.AWSDeviceFarm;
import com.amazonaws.services.devicefarm.AWSDeviceFarmClient;
import com.amazonaws.services.devicefarm.AWSDeviceFarmClientBuilder;
import com.amazonaws.services.devicefarm.model.AWSDeviceFarmException;
import com.amazonaws.services.devicefarm.model.CreateUploadRequest;
import com.amazonaws.services.devicefarm.model.GetUploadRequest;
import com.amazonaws.services.devicefarm.model.GetUploadResult;
import com.amazonaws.services.devicefarm.model.Upload;
import com.amazonaws.services.devicefarm.model.UploadType;
import io.github.martinschneider.justtestlah.utils.FileEntity;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Служебный класс для взаимодействия с AWS, по сути wrapper {@link AWSDeviceFarm}. */
public class AWSService {

  private static final Logger LOG = LoggerFactory.getLogger(AWSService.class);

  private AWSDeviceFarm aws;

  /** Конструктор */
  public AWSService() {
    aws = buildClient();
  }

  /**
   * Конструктор
   *
   * @param awsConfiguration map ключ-значение, содержит конфигурацию AWS
   */
  public AWSService(Map<String, String> awsConfiguration) {

    if (awsConfiguration == null) {
      // используем дефолтный провайдер учётных данных
      aws = buildClient();
    } else {
      String awsAccessKey = awsConfiguration.get("accessKey");
      String awsSecretKey = awsConfiguration.get("secretKey");
      String awsRegion = awsConfiguration.get("awsRegion");
      if (awsAccessKey == null || awsSecretKey == null || awsRegion == null) {
        // если какое-либо из значений не установлено, используем дефолтный провайдер 
        // учётных данных
        aws = buildClient();
      } else {
        aws = buildClient(awsAccessKey, awsSecretKey, awsRegion);
      }
    }
  }

  /**
   * Конструктор
   *
   * @param awsAccessKey ключ доступа AWS
   * @param awsSecretKey секретный ключ AWS
   * @param awsRegion AWS регион
   */
  public AWSService(String awsAccessKey, String awsSecretKey, String awsRegion) {
    aws = buildClient(awsAccessKey, awsSecretKey, awsRegion);
  }

  /** @return {@link AWSDeviceFarm} */
  public AWSDeviceFarm getAws() {
    return aws;
  }

  private AWSDeviceFarm buildClient() {
    LOG.debug("Building AWS Device Farm client using default credentials provider");
    return AWSDeviceFarmClientBuilder.standard().build();
  }

  private AWSDeviceFarm buildClient(String awsAccessKey, String awsSecretKey, String awsRegion) {
    LOG.debug("Building AWS Device Farm client");
    LOG.debug("awsAccessKey={}", awsAccessKey);
    LOG.debug("awsSecretKey={}", awsSecretKey);
    LOG.debug("awsRegion={}", awsRegion);
    return AWSDeviceFarmClient.builder()
        .withCredentials(
            new AWSStaticCredentialsProvider(new BasicAWSCredentials(awsAccessKey, awsSecretKey)))
        .withRegion(awsRegion)
        .build();
  }

  /**
   * Загрузка файла в AWS Device Farm (изменено с
   * https://github.com/awslabs/aws-device-farm-jenkins-plugin)
   *
   * @param file файл для загрузки
   * @param projectArn  ARN проекта Device Farm
   * @param uploadType {@link UploadType}
   * @param synchronous true, если выполнение ожидает успешного завершения загрузки
   * @return {@link Upload}
   * @throws InterruptedException {@link InterruptedException}
   * @throws IOException {@link IOException}
   * @throws AWSDeviceFarmException {@link AWSDeviceFarmException}
   */
  public Upload upload(File file, String projectArn, UploadType uploadType, Boolean synchronous)
      throws InterruptedException, IOException, AWSDeviceFarmException {
    CreateUploadRequest appUploadRequest =
        new CreateUploadRequest()
            .withName(UUID.randomUUID() + "_" + file.getName())
            .withProjectArn(projectArn)
            .withContentType("application/octet-stream")
            .withType(uploadType.toString());
    Upload upload = aws.createUpload(appUploadRequest).getUpload();

    CloseableHttpClient httpClient = HttpClients.createSystem();
    HttpPut httpPut = new HttpPut(upload.getUrl());
    httpPut.setHeader("Content-Type", upload.getContentType());

    FileEntity entity = new FileEntity(file);
    httpPut.setEntity(entity);

    LOG.debug("AWS S3 upload URL: {}", upload.getUrl());

    Thread thread =
        new Thread() {
          public void run() {
            HttpResponse response = null;
            try {
              response = httpClient.execute(httpPut);
            } catch (IOException exception) {
              throw new AWSDeviceFarmException(
                  String.format("Error uploading file to AWS: %s", exception.getMessage()));
            }
            if (response.getStatusLine().getStatusCode() != 200) {
              throw new AWSDeviceFarmException(
                  String.format(
                      "Upload returned non-200 responses: %d",
                      response.getStatusLine().getStatusCode()));
            }
          }
        };
    thread.start();
    int progress = 0;
    while (thread.isAlive()) {
      int newProgress = entity.getProgress();
      if (newProgress > progress) {
        LOG.info("{}% completed {}", progress, file.getAbsolutePath());
        progress = newProgress;
      }
      Thread.sleep(500);
    }

    if (synchronous) {
      while (true) {
        GetUploadRequest describeUploadRequest = new GetUploadRequest().withArn(upload.getArn());
        GetUploadResult describeUploadResult = aws.getUpload(describeUploadRequest);
        String status = describeUploadResult.getUpload().getStatus();

        if ("SUCCEEDED".equalsIgnoreCase(status)) {
          LOG.info("Uploading {} succeeded: {}", file.getName(), describeUploadRequest.getArn());
          break;
        } else if ("FAILED".equalsIgnoreCase(status)) {
          LOG.info(
              "Error message from device farm: '{}'",
              describeUploadResult.getUpload().getMetadata());
          throw new AWSDeviceFarmException(String.format("Upload %s failed!", upload.getName()));
        } else {
          try {
            LOG.info(
                "Waiting for upload {} to be ready (current status: {})", file.getName(), status);
            Thread.sleep(5000);
          } catch (InterruptedException exception) {
            LOG.info("Thread interrupted while waiting for the upload to complete");
            throw exception;
          }
        }
      }
    }
    return upload;
  }
}

Теперь, даже если тесты выполняются в инфраструктуре AWS, можно обернуть их в локальный JUnit (с некоторыми ограничениями) и выполнить так же, как локальные тесты Appium или с помощью облачного провайдера с выполнением на стороне клиента.

Команды для выполнения тестов AWS Device Farm

Если потянуть JustTestLah! из Github, то запуск тех же тестов, которые настраивали через веб-консоль, выполняется с помощью команды:

mvn -pl justtestlah-demos test -Dtest=TestRunner -Djusttestlah.properties=/demos/stackoverflow_aws.properties

Единственное значение для установки в stackoverflow_aws.properties – это ARN (имя ресурса Amazon) вашего проекта Device Farm. Легко найти его с использованием интерфейса командной строки AWS:

aws devicefarm list-projects

Обратите внимание, что путь к justtestlah.properties – абсолютный (не стесняйтесь внести вклад в JustTestLah! и улучшить это). Демонстрационные конфигурации увидите в justtestlah-demos/demos. С использованием символической ссылки ln -s justtestlah-demos/demos /demos все команды работают так, как показано. Или измените путь по мере необходимости.

Легко пропустить загрузку нового пакета приложения или тестового пакета, указав ARN существующего в файле свойств:

cloudprovider=aws
aws.projectArn=xxx
aws.appPackageArn=yyy
aws.testPackageArn=zzz

Если хотим запустить те же тесты на Browserstack, передаём другую конфигурацию (задаём адрес электронной почты Browserstack и ключ доступа в stackoverflow_browserstack.properties):

mvn -pl justtestlah-demos test -Dtest=TestRunner -Djusttestlah.properties=/demos/stackoverflow_browserstack.properties

Для локального выполнения используем другую команду. Убедитесь, что Appium запущен, и работает хотя бы один подключенный телефон на Android или эмулятор:

mvn -pl justtestlah-demos test -Dtest=TestRunner -Djusttestlah.properties=/demos/stackoverflow_local.properties

Как видите, использование облачного провайдера для выполнения тестов – не что другое, как значение конфигурации. Легко переключаться между локальным выполнением, AWS Device Farm и Browserstack. Следующий уровень инкапсуляции состоит в том, чтобы поддерживать разные устройства на множественных платформах и позволить фреймворку обрабатывать остальное. То есть выбирать провайдера облачных вычислений для каждого теста, который предлагает запрошенное устройство (и сделать это быстрее).

Конфигурация AWS Device Farm

Помимо фильтров устройств, доступны некоторые дополнительные настройки для выполнения тестов AWS (это настройки, которые пропустили на шаге «Укажите состояние устройства» в веб-консоли):

# Фильтры устройств
aws.minOsVersion=9.0
aws.maxOsVersion=
aws.osVersion=
aws.model=
aws.manufacturer=
aws.formFactor=PHONE
aws.waitForDevice=true

# Конфигурация устройства
aws.deviceLatitude=
aws.deviceLongitude=
aws.bluetooth=
aws.gps=
aws.nfc=
aws.wifi=
# установите true, если используете слоты устройств
aws.runUnmetered=false

# Дополнительная конфигурация AWS Device Farm
aws.accountsCleanup=
aws.appPackagesCleanup=
aws.jobTimeOut=
aws.skipAppResign=

Интеграция Maven

Чтобы иначе интегрировать AWS Device Farm (но с использованием того же Java SDK) , взглянем на самописный плагин Maven. Он предоставляет задачи для каждого шага (загрузка пакета приложения, загрузка тестового пакета, планирование запуска и другое) для возможного их разделения. Например, чтобы загружать новый пакет приложения каждый раз при сборке, и загружать новый тестовый пакет каждый раз при внесении изменений в тестовом коде. Само выполнение теста не зависит от обоих. И повторно использует последние (или другие) приложения и тестовые пакеты.

Плагин Jenkins

Разработчики также используют плагин Jenkins для AWS Device Farm, но он не такой гибкий, как решения, описанные в этой статье.

Резюме

В этой статье рассмотрели, как запускать тесты в AWS Device Farm подобно тому, как это делаем локально или на платформах с выполнением на стороне клиента, вроде Browserstack. Запуск тестов в AWS Device Farm и, в особенности, умелая интеграция их в процесс сборки – непростое дело. Надеемся, что примеры помогут тем, кто столкнулся с подобными задачами.

Как только механизм заработает, AWS будет надёжным облачным провайдером для мобильной автоматизации. Его стоит придерживаться и настраивать под собственные нужды.

Оригинал

 

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Продуктовый аналитик в поддержку
по итогам собеседования
DevOps
Санкт-Петербург, от 150000 RUB до 400000 RUB
Golang разработчик (middle)
от 230000 RUB до 300000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ