-
-
Save steipete/1e34ed697d116b3319eca2dd81d616ed to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
public enum TestWaitResult { | |
case wait, success | |
} | |
public enum WaitTimeoutCondition { | |
case fail, skip | |
} | |
public func waitFor(interval: TimeInterval) { | |
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, interval, false) | |
} | |
public func waitForAnimations() { | |
waitFor(interval: 0.3) | |
} | |
public func waitUntil(_ closure: () -> Bool, timeoutCondition: WaitTimeoutCondition = .fail, file: StaticString = #file, line: UInt = #line) { | |
waitOrSkip(timeoutCondition: timeoutCondition, file: file, line: line, block: { closure() ? .success : .wait }) | |
} | |
public func waitFor(timeout: TimeInterval = 10, message: String = "Timeout waiting for condition", file: StaticString = #file, line: UInt = #line, block: () -> TestWaitResult) { | |
waitOrSkip(timeoutCondition: .fail, timeout: timeout, message: message, file: file, line: line, block: block) | |
} | |
public func waitOrSkip(timeoutCondition: WaitTimeoutCondition, | |
timeout: TimeInterval = 10, | |
message: String = "Timeout waiting for condition", | |
file: StaticString = #file, | |
line: UInt = #line, | |
block: () -> TestWaitResult, | |
successBlock: (() -> Void)? = nil) { | |
let timeoutMs = UInt(timeout * 1000) | |
let startedAt = getAbsoluteTimeMs() | |
var result: TestWaitResult = .wait | |
while (getAbsoluteTimeMs() - startedAt) < timeoutMs { | |
result = block() | |
if result == .success { | |
successBlock?() | |
break | |
} | |
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.1, false) | |
result = block() | |
if result == .success { | |
successBlock?() | |
break | |
} | |
} | |
if timeoutCondition == .fail, result == .wait { | |
XCTFail("\(message)", file: file, line: line) | |
} | |
} | |
private func getAbsoluteTimeMs() -> UInt { | |
var info = mach_timebase_info(numer: 0, denom: 0) | |
mach_timebase_info(&info) | |
let numer = UInt64(info.numer) | |
let denom = UInt64(info.denom) | |
let nanoseconds = (mach_absolute_time() * numer) / denom | |
return UInt(nanoseconds / NSEC_PER_MSEC) | |
} | |
public extension XCUIApplication { | |
func scrollDownTo(_ element: XCUIElement, maxScrolls: Int = 5) { | |
guard !element.isHittable else { return } | |
for _ in 0 ..< maxScrolls { | |
scrollDown() | |
if element.isHittable { | |
break | |
} | |
} | |
} | |
func scrollUpTo(_ element: XCUIElement, maxScrolls: Int = 5) { | |
guard !element.isHittable else { return } | |
for _ in 0 ..< maxScrolls { | |
scrollUp() | |
if element.isHittable { | |
break | |
} | |
} | |
} | |
func scrollDown() { | |
// Drag from about half way down the screen to almost the top to make sure that the drag does not interact with the keyboard. | |
let fromCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.4)) | |
let toCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) | |
fromCoordinate.press(forDuration: 0, thenDragTo: toCoordinate) | |
} | |
func scrollUp() { | |
// Drag from about half way down the screen to near the bottom | |
let fromCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.4)) | |
let toCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.9)) | |
fromCoordinate.press(forDuration: 0, thenDragTo: toCoordinate) | |
} | |
func pullToRefresh() { | |
// Drag from the top quarter of the screen to the bottom quarter. | |
let fromCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.25)) | |
let toCoordinate = windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.75)) | |
fromCoordinate.press(forDuration: 0, thenDragTo: toCoordinate) | |
} | |
func navigateBack() { | |
// The back button tends to be the first button of the navigation bar | |
navigationBars.buttons.element(boundBy: 0).waitAndTap() | |
} | |
func tapKeyboardDoneButton() { | |
buttons["Done"].firstMatch.waitAndTap() | |
keyboards.element.waitUntilDoesntExist() | |
} | |
func tapPickerDoneButton() { | |
buttons["Done"].firstMatch.waitAndTap() | |
pickers.element.waitUntilDoesntExist() | |
} | |
} | |
public extension XCUIElement { | |
func waitUntilExists(timeout: TimeInterval = 10, file: StaticString = #file, line: UInt = #line) { | |
waitFor(timeout: timeout, file: file, line: line) { exists ? .success : .wait } | |
} | |
func waitUntilDoesntExist(file: StaticString = #file, line: UInt = #line) { | |
waitFor(file: file, line: line) { !exists ? .success : .wait } | |
} | |
func waitUntilEnabled(file: StaticString = #file, line: UInt = #line) { | |
waitUntilExists(file: file, line: line) | |
waitFor(file: file, line: line) { isEnabled ? .success : .wait } | |
} | |
func waitUntilDisabled(file: StaticString = #file, line: UInt = #line) { | |
waitUntilExists(file: file, line: line) | |
waitFor(file: file, line: line) { !isEnabled ? .success : .wait } | |
} | |
func waitAndTap(file: StaticString = #file, line: UInt = #line) { | |
waitUntilExists(file: file, line: line) | |
waitFor(file: file, line: line) { isEnabled && isHittable ? .success : .wait } | |
tap() | |
} | |
func waitForFocus(file: StaticString = #file, line: UInt = #line) { | |
waitFor(file: file, line: line) { accessibilityElementIsFocused() ? .wait : .success } | |
} | |
func waitForFocusAndType(_ text: String, file: StaticString = #file, line: UInt = #line) { | |
waitForFocus(file: file, line: line) | |
waitFor(file: file, line: line) { accessibilityElementIsFocused() ? .wait : .success } | |
waitFor(file: file, line: line) { XCUIApplication().keyboards.isEmpty ? .wait : .success } | |
typeText(text) | |
} | |
func tapAndType(_ text: String, file: StaticString = #file, line: UInt = #line) { | |
if !isHittable { | |
XCUIApplication().scrollDownTo(self) | |
if !isHittable { | |
XCUIApplication().scrollUpTo(self) | |
if !isHittable { | |
XCTFail("\(self) is not hittable and it wasn't possible to drag it into view.", file: file, line: line) | |
} | |
} | |
} | |
tap() | |
waitFor(file: file, line: line) { accessibilityElementIsFocused() ? .wait : .success } | |
waitFor(file: file, line: line) { XCUIApplication().keyboards.isEmpty ? .wait : .success } | |
typeText(text) | |
} | |
func waitAndPerformIfExists(timeout: TimeInterval = 10, file: StaticString = #file, line: UInt = #line, block: @escaping () -> Void) { | |
waitOrSkip(timeoutCondition: .skip, timeout: timeout, file: file, line: line, block: { exists ? .success : .wait }, successBlock: block) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment