В XCUITests, как дождаться существования любого из двух элементов пользовательского интерфейса

Глядя на XCTWaiter (). Wait (...) Я считаю, что мы можем дождаться, пока множественные ожидания станут реальностью, используя этот код.

let notHittablePredicate = NSPredicate(format: "hittable == false")
let myExpectation = XCTNSPredicateExpectation(predicate: notHittablePredicate, object: element)
let result = XCTWaiter().wait(for: [myExpectation], timeout: timeout)
//for takes array of expectations

Но здесь используется как AND среди предоставленных ожиданий. Есть ли способ сделать ИЛИ среди предоставленных ожиданий.

Например, у меня есть вариант использования при входе в систему, когда после нажатия кнопки «Отправить» я хочу дождаться одного из двух элементов. Первый элемент: «Вы уже вошли в систему на другом устройстве. Если вы продолжите, любые несохраненные данные на другом устройстве будут потеряны?». И второй элемент - это главный экран после входа в систему. Так что может появиться любой. В настоящее время я сначала жду первого элемента, пока не истечет время ожидания, а затем второго элемента. Но я хочу оптимизировать время здесь и двигаться дальше, как только любой из двух элементов существует == true. Затем я проверю, существует ли element1, затем нажмите ДА, а затем дождитесь главного экрана, иначе просто подтвердите существование element2.

Прокомментируйте, если в вопросе что-то неясно. Спасибо


person Hasaan Ali    schedule 19.12.2017    source источник


Ответы (4)


На основе http://masilotti.com/ui-testing-tdd/#3-native-asyncronous-assertions, вам не нужно полагаться на XCTWaiter. Вы можете просто запустить цикл и проверить, существует ли один из них.

/// Waits for either of the two elements to exist (i.e. for scenarios where you might have
/// conditional UI logic and aren't sure which will show)
///
/// - Parameters:
///   - elementA: The first element to check for
///   - elementB: The second, or fallback, element to check for
/// - Returns: the element that existed
@discardableResult
func waitForEitherElementToExist(_ elementA: XCUIElement, _ elementB: XCUIElement) -> XCUIElement? {
    let startTime = NSDate.timeIntervalSinceReferenceDate
    while (!elementA.exists && !elementB.exists) { // while neither element exists
        if (NSDate.timeIntervalSinceReferenceDate - startTime > 5.0) {
            XCTFail("Timed out waiting for either element to exist.")
            break
        }
        sleep(1)
    }

    if elementA.exists { return elementA }
    if elementB.exists { return elementB }
    return nil
}

тогда вы могли бы просто сделать:

let foundElement = waitForEitherElementToExist(elementA, elementB)
if foundElement == elementA {
    // e.g. if it's a button, tap it
} else {
    // element B was found
}
person lagoman    schedule 22.12.2017
comment
Оно работает. В состоянии while необходимо просто логическое изменение. Используйте while (! A.exists &&! B.exists), потому что нам нужно запустить цикл, пока ни один из обоих элементов не существует, т.е. ни a существует, ни b не существует. С || цикл повторяется вечно, даже если возникает один из двух элементов. Помимо этой небольшой настройки, БОЛЬШОЕ СПАСИБО :) - person Hasaan Ali; 02.01.2018
comment
О, полностью согласен. Одобрено редактировать :) Рад, что помог. - person lagoman; 02.01.2018
comment
Хороший ответ; Я обновил его, чтобы сделать его более универсальным и допускающим копирование / вставку. - person iwasrobbed; 31.08.2018

Ответ лагомана абсолютно правильный и отличный. Однако мне нужно было подождать более двух возможных элементов, поэтому я настроил его код, чтобы поддерживать Array из XCUIElement вместо двух.

@discardableResult
func waitForAnyElement(_ elements: [XCUIElement], timeout: TimeInterval) -> XCUIElement? {
    var returnValue: XCUIElement?
    let startTime = Date()
    
    while Date().timeIntervalSince(startTime) < timeout {
        if let elementFound = elements.first(where: { $0.exists }) {
            returnValue = elementFound
            break
        }
        sleep(1)
    }
    return returnValue
}

который можно использовать как

let element1 = app.tabBars.buttons["Home"]
let element2 = app.buttons["Submit"]
let element3 = app.staticTexts["Greetings"]
foundElement = waitForAnyElement([element1, element2, element3], timeout: 5)

// do whatever checks you may want
if foundElement == element1 {
     // code
}
person DBD    schedule 11.12.2020

NSPredicate также поддерживает предикаты OR.

Например, я написал что-то подобное, чтобы убедиться, что мое приложение полностью завершило запуск, прежде чем я начну пытаться взаимодействовать с ним в тестах пользовательского интерфейса. Это проверка наличия в приложении различных ориентиров, которые, как я знаю, однозначно присутствуют в каждом из возможных начальных состояний после запуска.

extension XCTestCase {
  func waitForLaunchToFinish(app: XCUIApplication) {
    let loginScreenPredicate = NSPredicate { _, _ in
      app.logInButton.exists
    }

    let tabBarPredicate = NSPredicate { _, _ in
      app.tabBar.exists
    }

    let helpButtonPredicate = NSPredicate { _, _ in
      app.helpButton.exists
    }

    let predicate = NSCompoundPredicate(
      orPredicateWithSubpredicates: [
        loginScreenPredicate,
        tabBarPredicate,
        helpButtonPredicate,
      ]
    )

    let finishedLaunchingExpectation = expectation(for: predicate, evaluatedWith: nil, handler: nil)
    wait(for: [finishedLaunchingExpectation], timeout: 30)
  }
}

В консоли во время выполнения теста выполняется серия повторяющихся проверок наличия различных кнопок, которые я хочу проверить, с переменным промежутком времени между каждой проверкой.

t = 13,76 с Подождите, пока com.myapp.name перейдет в режим ожидания

t = 18.15s Проверка наличия кнопки My Tab Bar

t = 18,88 с Проверка наличия кнопки справки

t = 20.98с Проверка наличия кнопки входа в систему

t = 22,99 с Проверка наличия кнопки My Tab Bar

t = 23,39 с Проверка наличия кнопки справки

t = 26,05 с Проверка наличия кнопки входа в систему

t = 32,51 с Проверка наличия кнопки My Tab Bar

t = 16,49 с Проверка наличия кнопки входа в систему

И вуаля, теперь вместо того, чтобы ждать каждый элемент по отдельности, я могу делать это одновременно.

Конечно, это очень гибко, так как вы можете добавлять столько элементов, сколько захотите, с любыми условиями. И если вам нужна комбинация предикатов OR и AND, вы также можете сделать это с помощью NSCompoundPredicate. Это можно легко адаптировать к более универсальной функции, которая принимает массив таких элементов:

func wait(for elements: XCUIElement...) { … }

Можно даже передать параметр, определяющий, использует ли он ИЛИ или И.

person shim    schedule 30.06.2021

Привет, другая альтернатива, которая работает для нас. Я надеюсь помочь другим тоже.

 XCTAssert(
            app.staticTexts["Hello Stack"]
                .waitForExistence(timeout: 10) || app.staticTexts["Hi Stack"]
                .waitForExistence(timeout: 10)
        )
person N..    schedule 02.07.2021