Параллельный запуск appium тестов при использовании pytest

Доброго времени суток, друзья.
Регрессия мобильных приложений уже больше, чем могут позволить себе руки, поэтому встал вопрос о тестировании билда при помощи Appium. В виду того, что примерно 120 девайсов дают примерно 90% покрытия основного рынка Android (информация могла уже устареть), такой парк живых девайсов нужно тестировать по-возможности в параллели.

Есть ли у вас опыт параллельного запуска тестов pytest xdist и appium? Как в этом случае будет выглядеть setup фикстура? В моем понимании я должен передать в фикстуру генератор, где каждый запуск теста на каждый тред - будет получать свое имя девайса (из adb devices списка).

В интернете и на форуме не нашел актуального работающего решения для pytest и xdist

def pytest_addoption(parser):
    parser.addoption("--mockgps", action="store", default="moscow_center",
                     help="Provide the lon, lat '37.6134591,55.7535942' ")
    parser.addoption("--device", action="store", default="emulator-5554",
                     help="Provide the device name listed in >adb devices for test")

Представим, что мы из командной строки получили строку, которая представляет список всех девайсов (реальных, эмуляторов, не важно). Эта строка парсится на список девайсов и из нее делается итератор:

@pytest.fixture(scope='function')
def setup_func(request):
    devices = pytest.config.getoption("--device").split(";")
    devices = iter(devices)
    device = next(devices)

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

С python не работаю, но недавно пришлось решать подобную задачу на java. После разнообразных экспериментов пришел к выводу, что проще и эффективней контроллировать девайсы при помощи независимых от основного тестового кода сервисов. Фактически был создан REST сервис, умеющий определять список подключенных девайсов, поднимать грид с аппиум нодами, скачивать последние версии артефактов, управлять установкой приложений и т.п. При этом, сам фреймворк посредством клиентского API общался с сервисом, формируя пул девайсов, и распределяя тесты по указанным платформам. Грубо говоря, в условном BeforeSuite сформировали пул девайсов, в BeforeMethod достали нужный девайс из пула, в AfterMethod положили обратно. Ну а параллелизм осуществлялся средствами юнит фреймворка.

Насколько я понял задача в следующем:

  • Есть набор тестов
  • Есть набор девайсов (хостов доступа)
  • Нужно набор тестов запустить на каждом девайсе. При этом тесты на каждом девайсе должны бежать параллельно.

Использовать xdist, насколько я знаю, в таком варианте не получается (хотя возможно если пошаманить над хуками pytest’a и xdist’a, и получится), вот по каким причинам:

  • xdist распределяет по потокам сколлекченные тесты. Т.о. на момент коллекта каждый тест должен быть параметризован девайсом. В принципе этого можно добиться через How to parametrize fixtures and test functions — pytest documentation. Но нужно сделать так, чтобы на слейве также было известно про эту параметризацию, т.е. нужно пробросить в коллект слэйва список девайсов - думаю это тоже можно заимплементить через хук.
  • распределение тестов в xdist довольно простое, кажется round-robin, и если не ошибаюсь, реализовано где-то здесь https://github.com/pytest-dev/pytest-xdist/blob/master/xdist/dsession.py#L387. То есть нет возможности гарантировать, что тесты для одного девайса попадут в один поток. Насколько я знаю, вот issue на это, где в том числе обсуждается механизм шедулинга Non-deterministic test collection order for deterministic fixtures makes xdist fail · Issue #920 · pytest-dev/pytest · GitHub.

Вариант решения
Я сталкивался с похожей проблемой. Решить можно через CI. Если вы используете Jenkins, то можно создать Matrix Job, и в качестве вектора параметризации указать список девайсов (их хостов и т.п.), причем поддерживается передача динамического списка н-р из другой джобы. Подджобы внутри MatrixJob будут запущены параллельно и гарантированно на каждом девайсе. В pytest хост легко пробросить, т.к. Jenkins установит глобальную переменную, которую можно указать в качестве аргумента, типа py.test --device-host ${SOME_JENKINS_VAR} my_appium_tests.
Кстати, в таком случае рекомендую через хук pytest_collection_modifyitems добавить к имени теста имя девайса, чтобы в общем отчете (н-р allure) разделять тесты. Кстати allure умеет делать агрегированный отчет из Jenkins Matrix Job.

Сергей, да, все правильно понял.

И как понимаю, фактически отойти от идеи того, что это можно достичь из pytest’a чистого, thread concurrency и использовать Дженкинс на отдельные запуски pytest процессов с отдельными передаваемыми аргументами?

Решение с CI в конечном счете похоже на предложенный выше вариант: что нужно переложить ответственность с тестового фреймворка и менеджмента запуска на плечи других. Спасибо, попробую вариант с Jenkins. Отпишусь, как получилось.

Я думаю да, поскольку из коробки это сделать не получается. К сожалению xdist предназначен для распределения существующих тестов, но не создания матрицы все-на-все, да и распределение, судя по issue, довольно примитивное: тест-менеджмент для шедулинга не поддерживается.

В данной задаче параллелизация - это лишь верхушка айсберга. Основная проблема кроется как раз в поддержке инфраструктуры. Для запуска аппиум нодов в параллели вам нужно подсовывать независимые json configs под каждое устройство. Автоматизировать процесс распределения портов (по паре на каждый девайс). При этом, json так же должен знать дополнительную информацию о udid / plarform / version и т.п. Уметь по запросу все это дело перезапустить одной командой. Еще одна проблема кроется в периодически отваливающихся девайсах, которые сами по себе не переподключатся… Невозможности сделать full reset ios приложения средствами аппиума на реальном устройстве (опять-таки, работа с cmd). И таких мелочей - уйма. Даже если вы что-то вынесете на jenkins, это не избавит вас от множества других нюансов.