EchoUITests/UITestSupport.swift
Rama main · réplica de lectura del repositorio original.
import XCTest
/// What the UI tests share: the launch arguments nearly all of them start from, finding a piece
/// of text on screen, and the conversation list's rows.
extension XCUIApplication {
/// A launch that lands on the chat: setup done, no voice mode at launch, no app lock.
static let straightToChat = ["-setupDone", "YES", "-openToVoiceScreen", "NO", "-listenOnOpen", "NO", "-requireBiometrics", "NO"]
/// The conversation list on Chats; without it, it reopens on whichever section was used last.
static let chatsSection = ["-conversations.section", "sessions"]
/// The first text on screen that contains `fragment`.
func text(containing fragment: String) -> XCUIElement {
staticTexts.containing(NSPredicate(format: "label CONTAINS %@", fragment)).firstMatch
}
}
extension XCUIElementQuery {
/// The first match that can be tapped, once there is one. A conversation's title is also the
/// header of the open conversation, pushed off screen behind the iPhone's list, and a row's
/// Delete is also a swipe action laid out off screen: the one wanted is the one on screen.
func hittable(within seconds: TimeInterval) -> XCUIElement? {
let deadline = Date().addingTimeInterval(seconds)
repeat {
if let found = (0 ..< count).map({ element(boundBy: $0) }).first(where: \.isHittable) { return found }
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
} while Date() < deadline
return nil
}
}
extension XCUIApplication {
/// Deletes a conversation through its row's menu in the list (open already). A swipe to the
/// left on the iPhone closes the list instead. False when there was no such row.
@discardableResult
func deleteConversation(titled title: String) -> Bool {
let rows = staticTexts.matching(identifier: title)
guard let row = rows.hittable(within: 10) else { return false }
let before = rows.count
row.press(forDuration: 1.0)
guard let delete = buttons.matching(NSPredicate(format: "label == %@", "Delete")).hittable(within: 5) else { return false }
delete.tap()
let deadline = Date().addingTimeInterval(5)
while rows.count >= before, Date() < deadline { RunLoop.current.run(until: Date().addingTimeInterval(0.2)) }
return rows.count < before
}
}
extension XCUIApplication {
/// Opens one of Settings' pages (Appearance, Writing, Voice, Siri, Agent) from its first
/// screen, which is open already.
func openSettingsPage(_ title: String, file: StaticString = #filePath, line: UInt = #line) {
let row = buttons.matching(NSPredicate(format: "label == %@", title)).firstMatch
for _ in 0 ..< 6 where !(row.exists && row.isHittable) { swipeUp() }
XCTAssertTrue(row.exists && row.isHittable, "No \(title) in Settings", file: file, line: line)
row.tap()
XCTAssertTrue(navigationBars[title].waitForExistence(timeout: 5), "Settings → \(title) didn't open", file: file, line: line)
}
}