Detox и Appium: автоматизированный тест интерфейса в React Native



    Незнакомая мобильная среда


    Я, возможно, также как и вы, пришел к React Native как разработчик JavaScript нежели как разработчик нативных мобильных приложений. Абсолютно новый мир со своими нюансами и хитростями.

    Одной из самых важных тем для изучения станет тестирование. Когда все более или менее понятно с модульными тестами (unit), что делать с тестами интерфейса и сквозными тестами (end-to-end)? iOS. Android. На рынке смесь разных типов устройств.

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

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

    Appium


    Использующая за кулисами Selenium WebDriver, Appium это мощный фреймворк с огромным сообществом разработчиков нативных мобильных приложений. Вышедший еще до React.js, это лидер и равных ему нет.

    Начать работу с Appium довольно легко. С помощью npm устанавливаем пакеты “appium” и “appium-doctor”, можем глобально, можем как часть проекта. Команда “appium-doctor” расскажет нам, что еще нужно установить и настроить прежде чем приступать к работе, и, если возможно, поможет исправить недочеты. Когда все решено, пакеты установлены и конфигурация Jest на месте, можем запускать сервер Appium и тесты.

    Не буду углубляться в подробности настройки, но вот как выглядит простой тест с конфигурацией (добавлены комментарии):

    /* клиент selenium webdriver для node
    */
    import wd from 'wd'
    
    /* 60 секунд таймаут, после которых тест остановится, если застрянет
     */
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000
    
    /* адрес сервера Appium. Запускаем с нашего компьютера, поэтому localhost
    */
    const URL = 'localhost'
    const PORT = 4723
    
    /* создаем объект webdriver
    */
    const driver = wd.promiseChainRemote(URL, PORT)
    
    /* Вожможности сервера.
    * инструкция для сервера Appium,
    * как запускать тесты, другими словами настройки.
    */
    const capabilities = {
     platformName: 'iOS', // или Android
     platformVersion: '12.1', // версия ОС
     deviceName: 'iPhone 8', // или “Android Emulator” или точное название устройства
     automationName: 'XCUITest', // фреймворк платформы (UIAutomator2 для Android)
     app: '/path/to/.app' // расположение файла .app (для Android это .apk)
    }
    
    beforeAll(async () => {
     try { // до того, как запустить тест
       await driver.init(capabilities) // запускаем драйвер
       await driver.sleep(4000) // да уж, вручную ставим таймер и ждем загрузку приложения, вот она хрупкость!
     } catch(err) {
       console.log(err) // если что, мы хотим знать, что не так
     }
    })
    
    afterAll(async () => {
     try {
       await driver.quit() // конец сессии
     } catch(err) {
       console.error(err)
     }
    });
    
    /* Jest, делаем что хотим, что позволяет Appium!
    * в данном примере мы проверяем, соответствует ли текст
    * 'topLabel' и 'subLabel' заданному
    * Рекомендую ознакомиться с документацией на сайте Appium
    */
    describe("Home Screen landing", () => {
     test("render search screen", async () => {
        let topLabel = await driver.elementById('topLabel')
        let subLabel = await driver.elementById('subLabel')
        expect(await topLabel.text()).toBe("OK")
        expect(await subLabel.text()).toBe("главный экран")
     })
    })
    

    Сам тест, это последние несколько строк, которые проверяют, если на экране текст “OK” и “главный экран”. Как видите, в тесте ничего особенного, тот же самый Jest. Документация на сайте Appium описывает все возможности фреймворка включая также примеры на JavaScript.

    Неприязнь только к строке await driver.sleep(4000). К сожалению, тесты понятия не имеют, что происходит в приложении. Так называемый “черный ящик” или Blackbox. Представьте, если бы вы писали код на Node, и перед запросом http, вы бы ставили таймер вместо использования promise или callback. Вот она, хрупкость UI тестов.

    В этом простом тесте мы ждем 4 секунды для запуска приложения. Со временем и с увеличением количества тестов, мы будем устанавливать таймеры чаще — запросы http, анимация, сам React Native — мост между нативным кодом и JavaScript только усложняет ситуацию.

    «Черный ящик», у нас есть сборка приложения без доступа к внутренним структурам.


    Что нравится в Appium

    • 7+ лет в индустрии.
    • Широкие возможности API.
    • Легко найти помощь (это также минус, список ниже)
    • Поддержка разных языком, в том числе JavaScript.
    • Знакомая для разработчика JavaScript среда Jest.
    • Используется для сквозных тестов в MS AppCenter, BrowserStack и AWS DeviceFarm.
    • Возможность теста на настоящих устройствах.

    Что не нравится в Appium

    • Поиск в сети выдает результаты для разных языков программирования, большинство из них это Java.
    • Тестирование “чёрного ящика” (тесты не знают о процессах внутри приложения).
    • Нет синхронности с приложением, хрупкость, еще больше проблем создает React Native.
    • testID по какой-то причине не работает на Android.


    Заметьте три таба: логи сервера Appium, пакет metro bundler и сам тест.


    Detox


    Detox от компании Wix работает схоже с Appium. Главное отличие, это тестирование по стратегии «серого ящика». Одной из задач разработчиков Detox было решение проблем с хрупкостью — задача в приложении не будет начата, пока не закончилась предыдущая и пока приложение не будет свободно. Это стало возможно благодаря другому фреймворку созданному под названием EarlGrey.

    Так же как и с Appium, устанавливаем настройки.

    /* Дополнительная настройка в файле package.json, ниже пример
    */
    const detox = require("detox");
    const config = require("./package.json").detox;
    
    /* адаптер Jest
    */
    const adapter = require("detox/runners/jest/adapter");
    
    /* Таймаут,
     * использование адаптера Jest
     */
    jest.setTimeout(120000);
    jasmine.getEnv().addReporter(adapter);
    
    beforeAll(async () => {
     /* Запускаем сервер
     */
     await detox.init(config);
    });
    
     /* beforeEach и afterEach для тестов Detox, 
      * используем от Jest
      * чистим после тестов
      */
    beforeEach(async function() {
     await adapter.beforeEach();
    });
    
    afterAll(async () => {
     await adapter.afterAll();
     await detox.cleanup();
    });
    

    И настройка в package.json:

    "detox": {
       "configurations": {
         "ios.detox": { // настройки для iOS (запускается командой detox test -c ios.detox)
           "binaryPath": "path/to/.app",
           "build": "xcodebuild -workspace ios/app.xcworkspace -scheme scheme -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", // файл workspace или project. В данном случае создаем пакет debug вместо production (release).
           "type": "ios.simulator",
           "name": "iPhone 8" // название симулятора
         },
         "android.detox": { // настройки для Android (запускается командой detox test -c android.detox)
           "binaryPath": "path/to/.apk",
           "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", // В данном случае создаем пакет debug вместо production (release).
           "type": "android.emulator",
           "name": "Pixel_2_API_28" // название симулятора. “adb devices” покажет список доступных устройств Android
         }
       },
       "test-runner": "jest",
       "runner-config": {
        "setupTestFrameworkScriptFile" : "./detox.init.js", // пример выше   
        "testEnvironment": "node",
        "testRegex": "(.*|\\.(ui))\\.(ts|tsx)$" // регулярное выражение, где искать тесты интерфейса
     }
       "specs": "./__tests__/" // расположение тестов интерфейса
     }
    
    

    Тесты писать также легко, как и для Appium, но с использованием возможностей и ограничений Detox.

    «Серый ящик», у нас есть сборка приложения и доступ к внутренним структурам.


    Что мне нравится в Detox

    • Создан Wix для React Native.
    • Сфокусирован на JavaScript.
    • Тест по стратегии «серого ящика».
    • Работает синхронно с приложением.

    Что не нравится в Detox

    • Возможности не такие широкие как у Appium.
    • Маленькое сообщество.

    Хрупкость


    Несмотря на то, что Detox использует принцип «серого ящика», хрупкость все еще присутствует. Тест с вводом текста и свайпом не срабатывал как надо в 1 случае из 10. Нельзя быть уверенным на 100% в тестах интерфейса.

    Скорость


    Appium “тормозит” таймеры “.sleep” установленные в ручную, Detox в этом случае выигрывает, так как все синхронно. В целом я бы не делал еще каких-либо выводов со своей стороны, так как не писал большого кол-ва одинаковых тестов на обеих платформах. В 30-секундных тестах и простом тесте созданном для этой статьи, Detox справился на секунды быстрее. Если смотреть на две разные платформы, iOS и Android, тесты заняли +- одно и то же время. Главное, следует помнить, что тесты интерфейса занимают значительно больше времени модульных тестов.

    Что выбрать


    Я по прежнему изучаю оба фреймворка и понадобится какое-то время, чтобы понять все их преимущества, но на данный момент, как разработчик JavaScript, я выбираю Detox.

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

    Тесты интерфейса в команде разработчиков — для разработчиков, пробуйте Detox. Более сложные сквозные тесты — возможно, лучше присмотреться к Appium с его богатыми возможностями API и поддержкой на платформах BrowserStack, MS AppCenter и AWS DeviceFarm.

    Ссылки


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

    Appium

    Detox
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 4

      0
      Как решаете проблемы с тестирование на андроиде?
      Возможно достучаться до элемента, если вместо testID вставлять accessabilityLabel. Но при таком подходе уже нельзя использовать testID для ios и приходится писать «platform specific code», где в элемент вставляется либо testID, либо accessabilityLabel.
      Но даже в этом случае не соблюдается полная уникальность, так как идентификаторы наследуются в финальном дереве представления.
        0
        Добрый вечер.

        Проблемы только с Appium, в Detox testID работает на обоих платформах. Appium и Android работают как надо с accessabilityLabel, поэтому стоит использовать его, или, как вы указали, «platform specific code».
        0
        beforeAll(async () => {
        try { // до того, как запустить тест
        await driver.init(capabilities) // запускаем драйвер
        await driver.sleep(4000) // да уж, вручную ставим таймер и ждем загрузку приложения, вот она хрупкость!
        } catch(err) {
        console.log(err) // если что, мы хотим знать, что не так
        }


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

        > Со временем и с увеличением количества тестов, мы будем устанавливать таймеры чаще — запросы http, анимация, сам React Native — мост между нативным кодом и JavaScript только усложняет ситуацию.

        А нельзя делать поллинг, раз в N секунд проверяя появление какого-то элемента? Так делают в браузерах.
          0

          Добрый день.


          Вы правы, можно добавить строку driver.quit(), можно попробовать вместо try...catch использовать try...finally.


          Под поллингом вы имеете ввиду "Implicit Wait"? Если честно, не пробовал, но похоже, что это возможно. Так или иначе, мы "ждем".


          Также хочу добавить, что лучше отделить настройки Appium от тестов, как я показал на примере Detox. Потом сделать два отдельных скрипта, вроде, yarn test:ui:ios и yarn test:ui:android.

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

        Самое читаемое