Всем привет! Меня зовут Артур Поляков, я инженер по тестированию в отделе мобильной разработки в компании iSpring. Наша команда работает над iSpring LMS — мобильным приложением для дистанционного обучения сотрудников.
В этой статье я поделюсь опытом автоматизации ручных проверок регресса в iOS-приложении. Хотя материалов об автотестах для iOS на Хабре достаточно, наш подход обладает уникальными особенностями, о которых я подробно расскажу дальше.
Процесс внедрения автотестов начался с анализа текущей ситуации. Мы выделили несколько ключевых факторов, которые подтолкнули нас к автоматизации:
Приложение разрослось и стало большим, регресс приложения занимал около 3 часов 30 минут
Две платформы: Android и iOS
Релизы почти каждую неделю
Проверка регресса — монотонный, рутинный процесс с высокой зависимостью от человеческого фактора
В такой конфигурации ручное тестирование перестало масштабироваться: оно замедляло релизы и повышало риск пропуска ошибок. Поэтому мы решили автоматизировать регресс.
Осознав масштаб проблемы, мы перешли к поиску инструментов. Нам было нужно решение, которое можно быстро внедрить в текущий процесс разработки и без лишних накладных расходов встроить в инфраструктуру проекта.
Рассмотрели множество вариантов и решили остановиться на нативном XCTest, встроенном в среду разработки Xcode. Это обеспечило нам максимальную интеграцию с проектом.
Все автотесты хранятся непосредственно в Git-репозитории проекта, что позволяет запускать их из любой ветки. Такой подход делает тесты доступными как для QA-инженеров, так и для разработчиков. Мы начали автоматизацию со smoke-тестов, чтобы гарантировать стабильность критического функционала и разгрузить ручное тестирование.
Однако мы не ограничились стандартными подходами. В классических E2E-тестах ожидаемым результатом обычно является появление конкретного элемента или текста. Такой подход не позволяет выявлять визуальные дефекты интерфейса. Мы пошли дальше: на каждом ключевом экране создается скриншот. Это позволяет верифицировать не только логику перехода и финальное состояние, но и визуальную корректность всех промежуточных экранов.
Как устроены наши автотесты
Рассмотрим, как это выглядит на примере. Чтобы наглядно показать устройство наших тестов, разберем структуру небольшого сценария для проверки функционала «Дни рождения».
import Foundation import SnapshotTesting import XCTest class BirthdaysFeatureDarkTest: XCTestCase, ISTest { let currentPO = BirthdaysPageObject.shared override func setUp() { super.setUp() feature("Логинация и открытие приложения") { step("Запуск приложения на экране Мои курсы") { launchApp(account: Config.instance.loginDetails.asAutoLoginData, options: [.skipBottomSheets, .clearData], language: .ru, theme: .dark) } } } func testBirthdaysDark() throws { link("ISOAPP-15223") feature("Проверка фичи Дни рождения в темной теме") { // Открываем таб Больше openTab(tab: .more) currentPO.viktorPetrovich.assertExistence() assertScreenshot() // Открываем таб Дни рождения openTab(tab: .birthdays) assertScreenshot() // Открываем таб Прошедшие ДР currentPO.pastBirthdaysTab.checkExistsAndTap() assertScreenshot() } } }
В данном примере приложение запускается с заданными параметрами. Сначала открывается меню «Больше» и создается и сравнивается с эталоном первый скриншот. Затем выполняется переход в раздел «Дни рождения» со вторым скриншотом, и, наконец, проверяется вкладка «Прошедшие». Весь цикл занимает всего 17 секунд, за которые мы полностью проверяем верстку, темную тему, локализацию и кликабельность некоторых элементов на трех экранах.

Для улучшения читаемости тестов мы используем функции:
feature()- Группирует тесты в отчетах Allurelink()- Связывает тесты с задачамиstep()- Создает вложенные шаги в отчетах Allure
Параметры запуска приложения
account: Config.instance.loginDetails.asAutoLoginData — автоматическая авторизация в определенный аккаунт
.clearData — очищает данные приложения до запуска
.skipBottomSheets — отключение нижних оверлеев (bottom sheets) - в приложении при старте
language: .ru — включает русский язык в приложении
theme: .dark — включает темную тему в приложении
Для взаимодействия с UI используем методы
// Навигация по вкладкам openTab(.courses) // Ожидание элементов element.assertExistence(timeout: 10) element.waitForNonExistence(timeout: 10) // Жесты element.tap() element.swipeUp() element.swipeDown() element.swipeLeft() ...
Для обеспечения стабильности результатов мы используем симулятор с фиксированной версией ОС. На момент запуска проекта нашим стандартом стал iPhone 15 (iOS 17.5). На экранах с динамическими данными (дата, время, уникальные ID), скриншоты мы не делаем, за редким исключением, так как допустимая погрешность при сравнении скриншотов составляет 2%. В долгосрочных планах — переход к проверке отдельных UI-компонентов вместо всего экрана.
Работа с визуальными данными требует особого внимания к хранению ресурсов. Чтобы не раздувать размер Git-репозитория тяжелыми графическими файлами, мы храним эталонные скриншоты в DVC. Это позволяет управлять версиями изображений так же эффективно, как и кодом, решая проблему хранения больших объемов данных. В ближайшее время планируем перейти с DVC на Git LFS, так как он прозрачно интегрирован в Git и обеспечивает простоту использования и привычный рабочий процесс без настройки внешних хранилищ.
Сравнение скриншотов выполняется функцией assertScreenshot с двумя ключевыми параметрами: precision (точность) и perceptualPrecision (восприятие человеком). Имя файла для скриншота можно задать вручную или оставить его пустым и тогда оно будет соответствовать названию теста.
assertScreenshot( "myCustomName", precision: 0.98, // Допустимая разница в 2% пикселей perceptualPrecision: 0.95 // Учет восприятия человеком )
Так у нас выглядит рабочий процесс скриншот-тестирования.

Согласно этой схеме, при первом запуске тест считается «упавшим», так как эталон еще не создан. При последующих прогонах система сравнивает текущий результат с эталоном и фиксирует прохождение теста только при отсутствии недопустимых расхождений.
Чтобы обеспечить стабильность и масштабируемость тестов, мы опирались на несколько базовых практик.
Подходы, которые мы используем в автотестах:
Page Object Model — паттерн, при котором каждая страница описывается как отдельный класс, и внутри этого класса указываются локаторы элементов и методы взаимодействия с ними.
Обработка асинхронности. Использование
waitForExistenceдля ожидания элементов вместоsleep.Параметризованные тесты. Тестирование разных сценариев с одними и теми же шагами, но с разными входными данными.
Интеграция с CI/CD. Автоматический запуск автотестов в процессе разработки.
Применение этих паттернов позволило нам эффективно встроить проверки в общую цепочку поставки. Запуск автотестов интегрирован в CI/CD и управляется через тест-план. Каждый тест начинается в изолированной среде («с чистого листа»), что обеспечивает их независимость. Если сценарий предполагает создание контента (например, сообщения с вложением), то по завершении теста созданные данные обязательно удаляются.
Мы прогоняем тесты перед каждым релизом, срочными правками и при крупных изменениях кода. Результаты отображаются в Allure-отчете, где наглядно представлена статистика: общее количество проверок, процент успеха, время выполнения и детализация падений.

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

И обязательно у всех упавших тестов делается видеозапись выполнения теста, которую можно посмотреть, чтобы понять причину падения.
После внедрения такого подхода мы оценили его влияние на процесс тестирования и релизный цикл.
Результаты внедрения автотестов
Время ручного тестирования сократилось на 144 минуты (ранее процесс занимал 210 минут), при этом покрытие регресса достигло 68,57%.
Автотесты успешно выявляют минимальные дефекты в верстке и текстах, которые трудно заметить при ручной проверке.
Проект внедрения занял около года и по трудозатратам 325 рабочих часов при участии двух специалистов: iOS-разработчика и тестировщика.
Планы на будущее
В ближайшее время мы планируем расширить покрытие регресса и автоматизировать проверку новых функций после этапа разработки. Также в планах — интеграция запуска тестов напрямую из TMS. Такая интеграция необходима, чтобы в рамках каждого релиза формировалась единая картина ручных и автоматизированных проверок. TMS (Test Management System) — это система управления тестированием, специализированное ПО для организации, планирования, документирования и контроля процесса тестирования ПО. Она позволяет хранить тест-кейсы, запускать проверки, фиксировать результаты и отчеты в одном месте, объединяя работу ручных и автоматизированных тестировщиков.
Заключение
В итоге комбинация E2E-проверок и визуального контроля позволила сократить время регресса и повысить качество продукта без увеличения нагрузки на разработчиков. Этот подход может быть полезен командам, которые сталкиваются с быстрым ростом продукта и частыми релизами.