Привет, Хабр! В прошлой статье мы разобрались:

  • Что такое ui-тесты и для чего они нужны;

  • Как настроить окружение для тестов;

  • Как находить ui-элементы в проекте и проставлять им accessibilityidentifier.

В этой статье мы разберем:

  1. Как обращаться и инициализировать ui-элементы в ваших тестах;

  2. Как взаимодействовать с ui-элементами приложения;

  3. Как писать ассерты для проверки в автотесте ожидаемого результата.

Как обращаться и инициализировать ui-элементы

При наличии айдишника у ui-элемента, достаточно указать его при обращении.

XCUIApplication().buttons["Help"]

Если же у вас нет id у элемента, есть способ найти его при помощи XCUIElementQuery. Этот класс позволяет искать элемент несколькими способами.

// Находит все кнопки внутри scroll view (отобразит кнопки только прямого потомка scroll view)
XCUIApplication().scrollViews["Main"].children(matching: .button)

// Находит все кнопки внутри scroll view (отобразит кнопки прямого потомка scroll view, но также и его потомков)
XCUIApplication().scrollViews["Main"].descendants(matching: .button)    

// Находит четвертую кнопку на экране
XCUIApplication().buttons.element(boundBy: 3)

// Находит в scroll view ui-элемент содержащий label = identifier
XCUIApplication().scrollViews["Main"].containing(NSPredicate(format: "label == %@","identifier").element

// Находит первую кнопку на экране
XCUIApplication().buttons.firstMatch

Немного про NSPredicate — это класс, который позволяет фильтровать объекты по нужному вам условию. Статья с хорошим объяснением как использовать NSPredicate.

Пример иницилизации переменной:

let moneyTitle: XCUIElement = XCUIApplication().staticTexts["accessibilityID"]

Взаимодействия с ui-элементами приложения

Нажатие и удержание

Вы можете в своих тестах совершать: нажатие, удержание и drag&drop ui-элементов.

Перечень методов можно посмотреть здесь, раздел — Tapping and Pressing.

// Совершаем нажатие на ui-элемент
XCUIApplication().buttons.element.tap()

// Cовершаем двойное нажатие на ui-элемент
XCUIApplication().buttons.element.doubleTap()

// Удерживаем нажатие в течение времени, которое передали в forDuration
XCUIApplication().buttons.element.press(forDuration: 3)

// Совершаем нажатие на ui-элемент и затем перетаскиваем его к другому ui-элементу
XCUIApplication().buttons.element.press(forDuration: 3, thenDragTo: XCUIApplication().searchFields.element)

Ввод текста

Вы можете вводить текст по букве обращаясь к системной клавиатуре:

XCUIApplication().textFields.element.tap()
XCUIApplication().keys["h"].tap()
XCUIApplication().keys["e"].tap()
XCUIApplication().keys["l"].tap()
XCUIApplication().keys["p"].tap()

Либо вводить целую строку:

XCUIApplication().textFields.element.typeText("help")

Информация по методу typeText

Множественные нажатия

Вы можете совершать множественные нажатия в своих тестах.

// Совершаем нажатие двумя пальцами на ui-элемент
XCUIApplication().buttons.element.twoFingerTap()

/*
     Совершаем нажатие на элемент столько раз сколько передали
     в withNumberOfTaps и столькими "пальцами" сколько передали
     в numberOfTouches
*/
XCUIApplication().buttons.element.tap(withNumberOfTaps: 1, numberOfTouches: 1)

Перечень методов можно посмотреть здесь, раздел — Multiple Taps.

Жесты

Вы можете совершать разные жесты в своих тестах.

// Совершаем свайп в указанном направлении
swipeLeft()
swipeRight()
swipeUp()
swipeDown()

// Совершаем свайп в указанном направлении с заданной скоростью
swipeLeft(velocity: 0.5)
swipeRight(velocity: 0.5)
swipeUp(velocity: 0.5)
swipeDown(velocity: 0.5)

// Совершаем приближения ui-элемента (withScale указываем больше 1)
XCUIApplication().images.element(boundBy: 0).pinch(withScale: 2, velocity: 1) 
// Совершаем отдаления ui-элемента (withScale указываем от 0 до 1)
XCUIApplication().images.element(boundBy: 0).pinch(withScale: 0.5, velocity: 1)

// Совершаем вращение ui-элемента
XCUIApplication().images.element(boundBy: 0).rotate(0.5, withVelocity: 0.5)

Перечень методов можно посмотреть здесь, раздел — Performing Gestures.

Взаимодействие с UISlider

UISlider — это элемент управления для выбора одного значения из диапазона значений.

Когда мы хотим изменить положение ползунка в слайдере, мы не передаем значение, которое хотим установить. Вместо этого мы выбираем число в диапазоне от 0 до 1. Где 0 — это минимальное значение в слайдере, а 1 — максимальное. Представим, что у нас есть слайдер с максимальным значением 100 и нам нужно сдвинуть ползунок на значение 25. Это будет выглядеть так:

XCUIApplication().sliders.element.adjust(toNormalizedSliderPosition: 0.25)

Взаимодействие с UIPickerView и UIDatePicker

UIPickerView и UIDatePicker — это ui-элементы, которые используют "колесики" для выбора необходимых значений.

XCUIElement имеет специальный метод для взаимодействия с UIPickerView и UIDatePicker:

  • Для пикеров с одним колесом, мы можем получить доступ через element(), и указать значение, которое хотим выбрать;

  • Для пикеров с несколькими колесами, мы можем обратиться к нужному колесу по индексу и указать значение, которое хотим выбрать.

// Пикер с одним колесом
XCUIApplication().pickerWheels.element.adjust(toPickerWheelValue: "BMW")

// Пикер с несколькими колесами
XCUIApplication().pickerWheels.elementBoundByIndex(0).adjust(toPickerWheelValue: "BMW")
XCUIApplication().pickerWheels.elementBoundByIndex(1).adjust(toPickerWheelValue: "X6")

Взаимодействие с системным алертом

Системный алерт — это объект, отображающий предупреждающее сообщение для пользователя.

Чтобы взаимодействовать с ним, вам понадобится использовать метод addUIInterruptionMonitor(withDescription:handler:)

Где вы передаете:

  • withDescription — заголовок алерта;

  • handler - действие, которое хотите совершить.

Пример использования в тестах:

addUIInterruptionMonitor(withDescription: "Current Location Not Available") { alert in
    alert.buttons["OK"].tap()
    return true
}

Взаимодействие с Navigation Bar

Navigation bar — это панель навигации, отображается в верхней части экрана приложения под status bar и позволяет перемещаться по приложению.

Представим, что у нас есть две кнопки и текст по середине в Navigation Bar.

Вот пример того как можно их иницилизировать и в дальнейшем с ними взаимодействовать:

// Иницилизируем крайнюю левую кнопку в Navigation bar 
let leftNavBarButton = XCUIApplication().navigationBars.children(matching: .button).firstMatch

// Иницилизируем тест посередине в Navigation bar 
let topicNavBar = XCUIApplication().navigationBars.children(matching: .staticTexts).firstMatch

// Иницилизируем крайнюю правую кнопку в Navigation bar
let rightNavBarButton = XCUIApplication().navigationBars.children(matching: .button).element(boundBy: 1)

// Нажимаем на кнопки в Navigation bar 
leftNavBarButton.tap()
rightNavBarButton.tap()

// Проверяем заголовок в Navigation bar 
XCTAssertEqual(topicNavBar.title, "Topic")

Взаимодействие с Tab bar

Tab bar — это панель вкладок, отображается в нижней части экрана приложения. Она даёт возможность быстро переключаться между различными разделами приложения.

Для переключения между вкладками достаточно тапать на индекс элемента в Tab bar.

// Открываем первую вкладку 
XCUIApplication().tabBars.buttons.element(boundBy: 0)
// Открываем третью вкладку
XCUIApplication().tabBars.buttons.element(boundBy: 2)

Создание ассертов:

Ассерты — это проверки необходимого условия.

Рассмотрим несколько вариантов их использования:

// Ассерт, что кнопка отображается на экране
XCTAssertTrue(XCUIApplication().buttons["Warning"].exists)

// Ассерт, что кнопка не выделена
XCTAssertFalse(XCUIApplication().buttons["Warning"].isSelected)

// Ассерт, что title кнопки равен - Buy
XCTAssertEqual(XCUIApplication().buttons.element.title, "Buy")

// Ассерт, что placeholder в textFields не равен - placeHolder
XCTAssertNotEqual(XCUIApplication().textFields.element.placeholderValue, "placeHolder")

// Ассерт, что value в textFields равно - value
XCTAssertEqual(XCUIApplication().textFields.element.value, "value")

Полный перечень возможных ассертов можно посмотреть здесь, раздел Test Assertions

Перечень возможных атрибутов ui-элементов можно посмотреть здесь

Заключение:

Взаимодействовать с ui-элементами во время теста не так сложно, как кажется на первый взгляд. Воспользовавшись примерами выше, можно быстро добавить необходимые методы в свой проект с ui-тестами.

В следующей статье мы расскажем про жизненый цикл тестового приложения:

  • Как делать предусловия и послеусловия;

  • Как сбрасывать статус пермишенов приложения перед запуском тестов (доступ к галерее, фото и так далее);

  • Как запускать приложения по bundle identifier (например запуск сафари, документов и так далее);

  • И многое другое.