Тестирование мобильных приложений на Flutter с использованием Python + Appium

от автора

Тестирование нативных мобильных приложений для Android и iOS обычно обеспечивается библиотеками UiAutomator2 и XCUITest, но сейчас, в связи с появлением мобильных приложений, разработанных с использованием Flutter Framework, использовать их для тестирования становится затруднительно, поскольку семантическая информация, публикуемая Flutter, в большинстве случаев недостаточна для однозначной идентификации виджетов и взаимодействия с ними. В этой статье мы рассмотрим возможности драйвера appium-flutter-driver для тестирования flutter-приложений, разберемся с использованием виджетов описания семантики и разработаем несложные тесты на Python с использованием Appium.

Если мы используем Appium 2 (appium@next), то нас доступна установка драйвера как расширения appium.

appium driver install --source=npm appium-flutter-driver

при использовании Appium 1 драйвер устанавливается как отдельный пакет в системе:

npm i -g appium-flutter-driver

Для корректного выполнения тестов нужно включить расширение FlutterDriverExtension в Flutter-приложении и собрать приложение в режиме отладки или профилирования. Создадим простое приложение на Flutter и последовательно разберемся, какие селекторы будут необходимы для выбора виджета на экране, как отправлять команды (действия) виджету, как проверять корректность изменения значений или видимости виджетов. За основу возьмем шаблонное приложение счетчика и добавим к нему необходимую семантическую информацию. Создадим проект командой flutter create (у вас должен быть установлен Flutter SDK версии 3.0 или выше, получить его можно по ссылке):

flutter create counter cd counter

Для поддержки тестов добавим в pubspec.yaml зависимость flutter_driver в dev_dependencies. Установим зависимости через flutter pub get.

flutter_driver:   sdk: flutter

и разрешим использование расширения перед запуском Flutter-приложения (в main):

import 'package:flutter_driver/driver_extension.dart';  void main() {   enableFlutterDriverExtension();   runApp(const MyApp()); }

Дополнительно можно определять реализации классов FinderExtension (для поиска виджетов по особым признакам) и CommandExtension (для выполнения дополнительных действий с виджетами или иными объектами). Соберем наше приложение в debug apk (для Android) или ipa (для iOS, в этом случае для запуска будет использовать Runner.zip):

flutter build apk --debug mv build/app/outputs/flutter-apk/app-debug.apk /tmp

Для запуска приложения в режиме отладки или профилирования (с расширением оно будет доступно для внешнего управления) создадим реализацию автоматического теста на Appium с использованием драйвера flutter.

from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy  caps = {} caps["platformName"] = "Android" caps["appium:app"] = "/tmp/app-debug.apk" caps["appium:automationName"] = "flutter" caps["appium:retryBackoffTime"] = 500  driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)  # test  driver.quit()

Если при запуске возникает ошибка, то это может быть связано с тем, что Appium Flutter Driver не смог найти observatory URL. В этом случае можно запустить приложение вручную, скопировать адрес из Debug service listening (ws://…) и подключиться к существующему приложению:

from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy  caps = {} caps["appium:observatoryWsUri"] = "ws://127.0.0.1:61985/D2nsixa5UkE=/ws" caps["platformName"] = "Android" caps["appium:automationName"] = "flutter" caps["appium:retryBackoffTime"] = 500  driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)  # test  driver.quit()

Если при запуске возникает ошибка, то это может быть связано с тем, что Appium Flutter Driver не смог найти observatory URL. В этом случае можно запустить приложение вручную, скопировать адрес из Debug service listening (ws://…) и подключиться к существующему приложению:

from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy  caps = {} caps["appium:observatoryWsUri"] = "ws://127.0.0.1:61985/D2nsixa5UkE=/ws" caps["platformName"] = "Android" caps["appium:automationName"] = "flutter" caps["appium:retryBackoffTime"] = 500  driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)  # test  driver.quit()

Flutter-приложение будет доступно как гибридное приложение, в котором контекст FLUTTER будет предоставлять возможности обнаружения элементов по Flutter-ключам (например, ValueKey) и типам виджетов, NATIVE_APP — поиск нативных View для Android/iOS, либо могут быть доступны дополнительные контексты для WebView.

Для поиска элементов во Flutter в Python можно использовать следующие методы (из модуля appium_flutter_finder.flutter_finder (объект класса FlutterFinder):

  • by_text(string) — поиск по отображаемому тексту

  • by_tooltip (string) — по всплывающей подсказке

  • by_ancestor(finder, matching) — поиск среди родителей обнаруженных в finder виджетов

  • by_descendart(finder, matching) — поиск среди потомков обнаруженных в finder виджетов

  • by_value_key(string) — по ключу (при создании виджета указывается как именованный параметр конструктора key: ValueKey(‘string’)

  • by_semantics_label(string) — по семантической метке (может быть добавлена через атрибут semanticsLabel у стандартных виджетов или виджет-обертку Semantics(label: ‘метка’, child: …)

  • by_type(string) — поиск по типу (названию класса виджета)

  • page_back() — возврат на предыдущую страницу

Для обнаруженного элемента (FlutterElement, наследуется от WebElement) можно использовать стандартные действия Appium:

  • click() — выполнить однократное нажатие на элемент

  • driver.execute('flutter:longTap’, element, {'durationInMilliseconds’:time}) — выполнить долгое нажатие на виджет

  • clear() — очистить ранее введенный текст

  • send_keys(string) — ввести текст

  • is_selected(), is_enabled(), is_displayed() — проверить состояние виджета

  • text — получить текущее значение (для текстовых виджетов)

Также можно использовать W3C Actions для описания произвольных жестов (через цепочку действий с указателем). Дополнительные возможности также доступны через выполнение команд (driver.execute_command):

  • flutter:forceGC — выполнить сборку мусора в Dart

  • flutter:getBottomLeft, flutter:getBottomRight, flutter:getTopLeft, flutter:getTopRight, flutter: flutter:getCenter — получить координаты точек для указанного FlutterElement (в аргументе)

  • flutter:getRenderTree — возвращает дерево Render Objects

  • flutter:getSemanticsId — получить id из Semantics для указанного FlutterElement (в аргументе)

  • flutter:scroll — выполнить прокрутку списка или любого прокручиваемого виджета (SingleChildScrollView, CustomScrollView), первым аргументом указывается FlutterElement, вторым — словарь с конфигурацией (dx, dy, durationMillisconds)

  • flutter:scrollIntoView — прокрутить экран до отображения указанного элемента

  • flutter:scrollUntilVisible — прокрутить до появления элемента на экране

  • flutter:scrollUntilTapable — прокрутить до активации возможности нажатия на элемент

Через команды также доступна синхронизация теста и изменений интерфейса:

  • flutter:waitFor — ожидание появления виджета (FlutterElement указывается в аргументе)

  • flutter:waitForAbsent — ожидание исчезновения указанного виджета

  • flutter:waitForTapable — ожидание возможности нажать на виджет 

  • flutter:waitForFirstFrame — ожидание отображения первого кадра

Для создания скриншота можно использовать driver.save_screenshot(filename). Дополнительно можно взаимодействовать с Dart VM и запрашивать информацию о текущем состоянии driver.execute('flutter:getVMInfo'), получать текущий изолят driver.execute('flutter:getIsolate').

Для нашего простого приложения тест может выглядеть следующим образом:

from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy from appium_flutter_finder.flutter_finder import FlutterElement, FlutterFinder  caps = {} caps["appium:observatoryWsUri"] = "ws://127.0.0.1:63079/c5ODoTGB0Wg=/ws" # caps["appium:appPackage"] = "com.example.counter"  # можно посмотреть в android/app/src/main/AndroidManifest.xml # caps["appium:appActivity"] = "MainActivity" caps["appium:noReset"] = True caps["platformName"] = "Android" caps["appium:automationName"] = "flutter" caps["appium:retryBackoffTime"] = 500  driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps) driver.execute("flutter:waitForFirstFrame") finder = FlutterFinder() counter = finder.by_value_key("Counter")  # нужно добавить key: ValueKey('Counter') к виджету счетчика нажатий assert (counter.text == '0') fab: FlutterElement = finder.by_type("FloatingActionButton") fab.click() assert (counter.text == '1')  driver.quit()

Использование драйвера appium-flutter-driver позволяет выполнять автоматическое тестирование flutter-приложений и создавать отчеты о тестировании через привычный инструмент тестирования мобильных приложений Appium.

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


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/706394/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *