На экране редактирования ввод в портретном режиме работает, но после поворота в альбомный черновик оказывается пустым. Или кнопка сохранения остаётся в дереве элементов, но её закрывает клавиатура. Одна сборка на облачном Mac таких ошибок не выявит: UI-тест должен пройти весь путь «ввод — поворот — сохранение — возврат». Разберём воспроизводимую проверку на примере iOS-приложения с экраном редактирования черновика.
Определите состояние, которое должно сохраняться при повороте
Перед тестированием задайте полю редактирования, кнопке сохранения и сообщению о результате стабильные accessibilityIdentifier: draftEditor, saveDraft и savedStatus. Эти идентификаторы задаются в коде приложения; не ищите элементы по локализованному тексту кнопок. Условия теста таковы: поворот не стирает несохранённый ввод; в альбомном режиме кнопку сохранения можно нажать; после сохранения появляется сообщение о результате; после возврата в портретный режим оно остаётся видимым.
existsозначает лишь, что элемент есть в дереве доступности, а не то, что пользователь может до него дотянуться. В тесте поворота проверяйте такжеisHittableи действительно нажимайте кнопку.
Сначала убедитесь, что приложение поддерживает проверяемую ориентацию. Если альбомный режим отключён в настройках поддерживаемых ориентаций устройства, невозможность повернуть экран нельзя считать ошибкой компоновки. Не переносите без проверки ожидания с телефона на планшет: разметка с несколькими колонками, размер клавиатуры и безопасные области могут различаться.
Зафиксируйте симулятор и среду запуска
В графическом сеансе облачного Mac XcodeOps выполните xcrun simctl list devices available, выберите симулятор телефона с установленной средой выполнения и запишите его UDID. В CI не выбирайте устройство только по имени: одинаковые имена могут встречаться у устройств с разными средами выполнения. Передайте UDID задаче через переменную окружения SIMULATOR_UDID и перед запуском ещё раз проверьте, что устройство есть в списке доступных.
Для одного набора базовых запусков зафиксируйте путь к выбранному Xcode, UDID симулятора, тестовые данные приложения и точку входа в тест. Сохраните вместе с записью о задаче вывод xcodebuild -version и xcrun simctl list devices available; не смешивайте с этой базой результаты другого симулятора. UI-тестам также нужен доступный графический сеанс пользователя: подключение по SSH само по себе не гарантирует, что тестовый процесс сможет управлять окном симулятора.
Напишите UI-тест полного цикла
Добавьте следующий тест в UI Test target проекта. Пример предполагает, что экран редактирования виден сразу после запуска и в приложении заданы три указанных идентификатора. Если сначала нужно перейти на экран редактирования, явно выполните переход до ввода текста, а не полагайтесь на экран, оставшийся после предыдущего запуска.
import XCTest
final class RotationTests: XCTestCase {
func testDraftSurvivesRotation() {
let device = XCUIDevice.shared
device.orientation = .portrait
defer { device.orientation = .portrait }
let app = XCUIApplication()
app.launch()
let editor = app.textViews["draftEditor"]
XCTAssertTrue(editor.waitForExistence(timeout: 10))
editor.tap()
editor.typeText("Rotation check 42")
device.orientation = .landscapeLeft
XCTAssertTrue(
NSPredicate(format: "value == %@", "Rotation check 42")
.evaluate(with: editor)
)
let save = app.buttons["saveDraft"]
XCTAssertTrue(save.waitForExistence(timeout: 10))
XCTAssertTrue(save.isHittable)
save.tap()
let status = app.staticTexts["savedStatus"]
XCTAssertTrue(status.waitForExistence(timeout: 10))
device.orientation = .portrait
XCTAssertTrue(status.waitForExistence(timeout: 10))
}
}
Здесь defer нужен, чтобы по возможности вернуть устройство в портретный режим перед следующим тестом; он не заменяет установку начального состояния в каждом тесте. Если при повороте приложение пересоздаёт экран редактирования, запрос элемента обычно может разрешиться заново, но не сохраняйте координаты или frame, полученные из такого запроса. При асинхронном сохранении недостаточно просто дождаться появления сообщения: лучше, чтобы приложение предоставляло однозначное состояние успешного сохранения, значение которого можно проверить, не принимая «сохраняется» за завершённую операцию.
Запустите тест из командной строки и сохраните результаты
Перед запуском из каталога проекта задайте путь к проекту, общий Scheme и ранее записанный UDID. Команда ниже сразу завершится, если необходимая переменная пуста, и запишет результаты в новый каталог пакета результатов. Перед повторным запуском используйте другое имя каталога.
: "${PROJECT:?Set the Xcode project path}"
: "${SCHEME:?Set a shared test scheme}"
: "${SIMULATOR_UDID:?Set an available simulator UDID}"
xcodebuild test \
-project "$PROJECT" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,id=$SIMULATOR_UDID" \
-only-testing:AppUITests/RotationTests/testDraftSurvivesRotation \
-resultBundlePath "$PWD/rotation-run.xcresult"
Имя AppUITests должно совпадать с именем UI Test target в проекте. Если тест завершился с ошибкой, сохраните .xcresult и по шагу сбоя, снимкам экрана и журналу теста определите, сбросился ли ввод или кнопку перекрыл другой элемент. Не ограничивайтесь последней строкой терминала. Если результаты будет читать скрипт, сначала сверьте доступные в текущем Xcode подкоманды через xcrun xcresulttool help, затем выберите подходящий структурированный вывод.
Ищите причину по симптому, а не добавляйте ожидание
| Симптом | Что проверить сначала | Как исправить |
|---|---|---|
| После поворота в альбомный режим черновик пуст | Пересоздаётся ли экран при повороте и где хранится состояние | Хранить состояние редактирования там, где оно переживёт пересоздание экрана |
| Кнопка есть, но её нельзя нажать | Клавиатуру, перекрывающие слои, безопасные области и прокручиваемый контейнер | Исправить ограничения компоновки или предусмотреть явный способ скрыть клавиатуру |
| Ошибка возникает только в CI | Графический сеанс, среду выполнения симулятора и тестовые данные | Зафиксировать целевое устройство и обеспечить одинаковое начальное состояние задачи |
| После возврата в портретный режим сообщение исчезает | Не является ли сообщение кратковременной анимацией | Проверять устойчивый результат сохранения или повторно считывать данные |
Не добавляйте фиксированный sleep при первом же сбое в альбомном режиме. Если интерфейс действительно проходит через переходное состояние, дождитесь проверяемого состояния конкретного элемента. Если клавиатура постоянно перекрывает кнопку, дополнительное ожидание не сделает её доступной для нажатия. Если проблема возникает только на определённом размере экрана, добавьте второй симулятор с другим размером, а не помещайте сразу все устройства в один тест. Сначала добейтесь стабильного прохождения полного цикла на одном зафиксированном устройстве, затем расширяйте покрытие — так записи о сбоях будет с чем сравнивать.
Часто задаваемые вопросы
Достаточно ли симулятора для проверки поворота?
Симулятор подходит для проверки состояния и доступности элементов; поведение датчиков и удобство интерфейса дополнительно проверяют на устройстве.
Почему кнопка найдена, но тест не может её нажать?
Проверьте, не закрывают ли её клавиатура или другой слой, а затем ограничения безопасной области и пересоздание представления.
Настройте облачный Mac для следующей задачи
Выберите Mac mini по объёму памяти или нагрузке сборки. Доступны три конфигурации и аренда на день, неделю, месяц или квартал. Актуальный статус узла отображается в панели управления.