Стабильность автотестов: ожидания, локаторы, flakiness и отладка
Зачем эта тема в курсе
В прошлых статьях вы собрали рабочую цепочку
pytest → Appium → устройство и начали выстраивать архитектуру через Page Object и единые ожидания. На практике следующий “болевой” этап почти у всех одинаковый: тесты начинают
иногда падать без реальных багов.
Такое поведение называют flakiness: тест нестабилен и даёт ложные падения. В мобильной UI-автоматизации это особенно распространено из-за асинхронности интерфейса, анимаций, фоновых задач, сети и особенностей устройств.
Цель этой статьи:
научиться выбирать правильные ожидания вместо sleep
делать локаторы устойчивыми к изменениям UI
понимать типовые причины flakiness и как их лечить
собирать достаточно данных для быстрой отладки падений!Схема слоёв и типовых мест, где появляется нестабильность
Что такое flakiness и как отличить её от настоящего бага
Flaky-тест может падать по причинам, не связанным с дефектом в приложении:
элемент появился чуть позже из-за анимации или сети
локатор нашёл “не тот” элемент
открылась системная модалка (разрешения, уведомления)
приложение было в неправильном состоянии (не сбросили данные, остался логин)Практичные признаки flakiness:
повторный запуск без изменений часто проходит
падения “прыгают” по разным местам сценария
падения зависят от скорости машины, нагрузки CI, версии устройстваВажно: flaky-тест всё равно должен считаться проблемой качества автотестов, иначе он быстро обесценит прогон.
Ожидания: главный инструмент борьбы с нестабильностью
Мобильный UI редко синхронен: экран открылся, но элементы догружаются; кнопка видна, но ещё не кликабельна; запрос ушёл, но ответ не пришёл. Поэтому тесту нужен способ “подождать правильное состояние”.
Рекомендуемая база по ожиданиям: WebDriver waits (Selenium)
Почему time.sleep() почти всегда вреден
time.sleep(2) делает тест:
медленнее на быстрых средах
всё равно нестабильным на медленных средах
хуже диагностируемым (неясно, что именно ждём)Единственный разумный сценарий для sleep в UI-тестах: очень редкие технические обходы, когда нет события, которое можно дождаться. Даже в этом случае лучше попытаться заменить на ожидание условия.
Виды ожиданий в Appium-проектах на Python
В связке Appium + Python чаще всего встречаются:
Implicit wait (неявное ожидание): драйвер будет ждать элемент при каждом find_element.
Explicit wait (явное ожидание): вы явно ждёте конкретное условие (видимость, кликабельность, исчезновение).Практическое правило для мобильной автоматизации:
держите implicit wait выключенным или минимальным
используйте explicit wait как стандартный подходПричина: implicit wait может “размазывать” задержки по всем операциям и усложнять разбор таймаутов.
Какие условия ждать на мобильном UI
Чаще всего вам нужны не “найти элемент”, а дождаться конкретного состояния:
элемент присутствует в дереве UI
элемент видим
элемент кликабелен
элемент исчез (например, лоадер)
у элемента появился нужный текст или атрибутПример расширения BasePage ожиданиями (продолжение архитектуры из прошлой статьи):
Таймауты: как выбрать “нормальные” значения
Слишком маленькие таймауты дают flakiness. Слишком большие таймауты превращают падения в долгие “подвисания”.
Практичная стратегия:
базовый таймаут для большинства UI-операций: 10–15 секунд
отдельные, явно названные ожидания для долгих операций (например, логин, первая загрузка)
фиксируйте таймауты в одном месте (например, utils/timeouts.py), чтобы команда не “размазывала” магические числаЧастая ошибка: ждать видимость, когда элемент уже видим, но перекрыт
На мобильном UI элемент может быть видимым, но не кликабельным:
поверх него лоадер
поверх открыта клавиатура
идёт анимация переходаПоэтому клик лучше строить через wait_clickable, а не через wait_visible.
Локаторы: как сделать поиск элементов устойчивым
Локатор — это ваш “контракт” с UI. Flakiness очень часто начинается с локатора: тест нажал не туда, нашёл другой элемент, или не нашёл элемент из-за изменения структуры.
Приоритет стратегий локаторов в мобильных приложениях
Обычно порядок предпочтения такой:
ACCESSIBILITY_ID (лучший вариант для стабильности)
Android ID (обычно resource-id)
iOS IOS_PREDICATE или CLASS_CHAIN (когда нет стабильных id)
текст (только если текст стабилен и не локализуется)
XPATH как последний вариантИнструмент для проверки локаторов: Appium Inspector
Почему Accessibility ID почти всегда лучший выбор
Accessibility-идентификатор:
чаще всего уникален и осознанно задан
меньше зависит от верстки и вложенности
одновременно полезен для доступности (это повышает шанс, что команда будет поддерживать его стабильным)Практика для команды: договориться с разработкой, что ключевые элементы имеют стабильные accessibility id.
Когда XPath неизбежен и как уменьшить ущерб
XPath становится хрупким, потому что зависит от структуры дерева UI. Если без него никак:
избегайте абсолютных путей вида /hierarchy/.../android.widget...
старайтесь привязаться к стабильным атрибутам (content-desc, resource-id, name)
делайте XPath как можно короче и более “семантичным”“Один локатор на два элемента” — скрытый источник флаков
Если локатор находит несколько элементов, Appium может вернуть “первый попавшийся”. На разных устройствах и версиях ОС порядок может отличаться.
Правило:
локатор должен быть максимально уникальным
если элементов несколько по дизайну (например, список), выбирайте элемент по индексу только после стабилизации сценария и понимания, что индекс детерминированМобильные причины flakiness и способы лечения
Ниже — типовые причины нестабильности именно в мобильном UI.
Анимации и переходы
Симптомы:
элемент найден, но click не срабатывает
ошибка Element not interactable или похожиеЛечение:
ожидать кликабельность
ждать исчезновение лоадеров и оверлеев
уменьшать длительность анимаций настройками приложения, если это возможно в тестовой сборкеКлавиатура перекрывает элементы
Симптомы:
кнопка “Login” не нажимается после вводаЛечение:
явный шаг скрытия клавиатуры (если платформа/приложение это поддерживает)
кликать по области вне инпута, если это часть UX
в Page Object держать это как часть метода ввода/логина, а не размазывать по тестамСистемные диалоги и разрешения
Симптомы:
на чистом устройстве тест падает на первом запускеЛечение:
предусмотреть обработку разрешений как отдельный компонент/экран
использовать настройки capabilities, которые помогают автопринятию разрешений (там, где это уместно), но помнить, что поведение может различатьсяДанные и состояние приложения
Симптомы:
тест иногда стартует “не с того экрана”Лечение:
в фикстуре драйвера обеспечивать предсказуемый старт: чистые данные, нужная активити/экран
не зависеть от результатов прошлых тестов
если тест требует сложного состояния, создавайте его через API/бэкенд (где возможно), а UI оставляйте для критического путиСеть и нестабильный бекенд
Симптомы:
разные падения на загрузке списков, таймаутыЛечение:
отделять UI-проверку от проверки данных: “экран открылся и элементы доступны” и “данные корректны”
использовать тестовые стенды с предсказуемыми данными
логировать сетевые ошибки на уровне приложения (если есть доступ) и собирать логи устройстваРетраи: когда можно, а когда нельзя
Иногда команды добавляют “перезапуск упавших тестов” как костыль. В
pytest для этого есть плагин:
pytest-rerunfailuresПример запуска:
Правильное отношение к ретраям:
ретраи можно использовать как временную страховку для CI
ретраи не должны заменять исправление ожиданий, локаторов и состоянияПрактика: если тест “лечится” ретраем, заведите задачу на стабилизацию и добавьте диагностику, иначе вы просто спрячете проблему.
Отладка падений: что собирать и как быстро локализовать причину
Когда тест упал, вам нужно быстро ответить на два вопроса:
это баг в приложении или flakiness?
если flakiness, то в чём причина: ожидание, локатор, состояние, окружение?Минимальный набор артефактов на падении
Минимум, который реально ускоряет разбор:
скриншот
page source (дерево UI)
логи тестаЭто вы уже частично делали через pytest_runtest_makereport в прошлой статье.
Логи устройства
Для Android очень часто помогает
logcat:
документация: Logcat (Android)Быстрые команды для ручной диагностики:
Практика: если тест упал “в UI”, а на скриншоте всё выглядит нормально, logcat часто показывает крэш, проблемы рендера, сетевые ошибки, ошибки авторизации.
Логи Appium Server
Если вы запускаете Appium локально в отдельном терминале, вы уже видите лог команд. Для CI полезно сохранять Appium-логи в файл.
Идея для диагностики:
если элемент не найден, в логах Appium обычно видно, каким локатором искали и сколько времени заняло ожиданиеAppium Inspector как инструмент воспроизведения
Если тест не находит элемент, не пытайтесь чинить “вслепую”. Откройте экран в
Appium Inspector и:
проверьте, что локатор реально существует в текущей сборке
проверьте, не меняется ли дерево UI после действий
убедитесь, что вы в нужном контексте (Native/WebView), если приложение гибридноеТехника “сужения проблемы”
Чтобы быстрее понять причину, действуйте так:
Перезапустите один упавший тест изолированно.
Если падает только в наборе, проверьте зависимость от состояния (данные, авторизация, порядок тестов).
Если падает только на конкретном устройстве/ОС, сравните локаторы и системные диалоги.
Если падение связано со временем, замените find_element на ожидание конкретного условия.Стандартизация логов действий в Page Object
Flaky-падение легче чинить, когда в логах видно шаги.
Практика: логируйте действия на уровне BasePage.
В связке с Allure это особенно удобно, потому что в отчёте видно последовательность действий.
Документация плагина: allure-pytest
Чек-лист стабилизации одного теста
Если тест нестабилен, пройдите короткий чек-лист:
Локатор уникален и основан на ACCESSIBILITY_ID или ID, а не на структуре.
Действия выполняются через методы Page Object с explicit waits.
Нет sleep, кроме редких технических обходов.
На падении сохраняются скриншот и page source.
Тест не зависит от результатов других тестов и запускается на “чистом” состоянии.Что дальше
После стабилизации ожиданий и локаторов следующий шаг в развитии фреймворка обычно такой:
обёртки для жестов (свайп, скролл) с предсказуемыми ожиданиями
более сильная работа с состоянием (быстрый логин, подготовка данных)
запуск в CI с артефактами и разумной матрицей устройствДля этого фундамент уже есть: pytest-фикстуры, хуки для артефактов и архитектура Page Object.