Skip to content

Commit

Permalink
Add 'waitFor' and deprecate 'await' (#152)
Browse files Browse the repository at this point in the history
* Add 'waitFor' and deprecate 'await'

* Update TABTestKit/Classes/Protocols/Element.swift

Co-authored-by: Riccardo Cipolleschi <87520685+tl-riccardo-Cipolleschi@users.noreply.github.com>

* [Project] Update CI yml file

* [Project] Update CI yml file

* Update ci.yml

* [Project] Update podspec version

* [Project] Update changelog

* [Project] Update readme

Co-authored-by: Riccardo Cipolleschi <87520685+tl-riccardo-Cipolleschi@users.noreply.github.com>
  • Loading branch information
theblixguy and tl-riccardo-Cipolleschi committed Sep 30, 2021
1 parent 77bdc69 commit 4050c52
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 52 deletions.
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
test_xcode10_ios12:
name: Run tests on Xcode 10 and iOS 12
runs-on: macOS-latest
runs-on: macos-10.15

steps:
- name: Checkout
Expand Down Expand Up @@ -54,17 +54,17 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Set Xcode version to 12.1
run: sudo xcode-select -switch /Applications/Xcode_12.1.app
- name: Set Xcode version to 12.5
run: sudo xcode-select -switch /Applications/Xcode_12.5.app
- name: Build for testing
run: xcodebuild build-for-testing -workspace Example/TABTestKit.xcworkspace -scheme TABTestKit-Example -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.1'
- name: Test on iPhone 11
run: xcodebuild test-without-building -workspace Example/TABTestKit.xcworkspace -scheme TABTestKit-Example -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.1'
run: xcodebuild build-for-testing -workspace Example/TABTestKit.xcworkspace -scheme TABTestKit-Example -destination 'platform=iOS Simulator,name=iPhone 12,OS=14.5'
- name: Test on iPhone 12
run: xcodebuild test-without-building -workspace Example/TABTestKit.xcworkspace -scheme TABTestKit-Example -destination 'platform=iOS Simulator,name=iPhone 12,OS=14.5'
- name: Archive tests results
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: Test-TABTestKit-Xcode12.1-iOS14.xcresult
name: Test-TABTestKit-Xcode12-iOS14.xcresult
path: /Users/runner/Library/Developer/Xcode/DerivedData/*/Logs/Test/*.xcresult

build_spm:
Expand All @@ -78,14 +78,14 @@ jobs:
run: sudo xcode-select -switch /Applications/Xcode_11.7.app
- name: Build Swift Package Manager
run: xcodebuild -workspace package.xcworkspace -scheme TABTestKit -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.7'
- name: Set Xcode version to 12.1
run: sudo xcode-select -switch /Applications/Xcode_12.1.app
- name: Set Xcode version to 12.5
run: sudo xcode-select -switch /Applications/Xcode_12.5.app
- name: Build Swift Package Manager
run: xcodebuild -workspace package.xcworkspace -scheme TABTestKit -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.1'
run: xcodebuild -workspace package.xcworkspace -scheme TABTestKit -destination 'platform=iOS Simulator,name=iPhone 12,OS=14.5'

build_carthage:
name: Ensure Carthage builds
runs-on: macOS-latest
runs-on: macos-10.15

steps:
- name: Checkout
Expand All @@ -105,7 +105,7 @@ jobs:

build_cocoapods:
name: Ensure Cocoapods builds
runs-on: macOS-latest
runs-on: macos-10.15

steps:
- name: Checkout
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# CHANGELOG

## Pending
## 1.8.0

- Obsoleted the `await` function in Swift 5.5 and added a `waitFor` function because using `await` in Swift 5.5 will lead to ambiguity errors with the `await` keyword. No code changes are required for clients, unless they're on Swift 5.5 and are calling the `await` function in their code. In that case, they will need to update it to `waitFor`.
- Upgraded GitHub actions:
- Added Xcode 12.1 as part of the job for validating SwiftPM, Carthage and Cocoapods
- Temporary allow Cocoapods to valid a library with warning for Xcode 12
Expand Down
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ struct ProfileScreen: Screen {

A `Screen` has one required property for you to implement, which is its `trait`. A
`trait` can be any [Element](#elements) that consistently, and uniquely,
identifies the screen, and is used to `await` for it to appear on-screen during tests when using [contexts](#contexts).
identifies the screen, and is used to `await`/`waitFor` for it to appear on-screen during tests when using [contexts](#contexts).

#### Elements

Expand Down Expand Up @@ -411,11 +411,19 @@ struct ProfileScreen: Screen {
Once you've created your screen, you can use it in tests to interact with the elements:

```swift
// If using Swift 5.4 or below
let profileScreen = ProfileScreen()
profileScreen.await() // Makes sure the screen is visible before going any further by waiting for its trait
profileScreen.logOutButton.tap() // You can't call tap on an element that isn't `Tappable`, but `Button` is!
```

```swift
// If using Swift 5.5
let profileScreen = ProfileScreen()
profileScreen.waitFor() // Makes sure the screen is visible before going any further by waiting for its trait
profileScreen.logOutButton.tap() // You can't call tap on an element that isn't `Tappable`, but `Button` is!
```

All elements conform to the [`Element`](#element) protocol, which ensures that every element
has a parent element (defaults to the `App`), underlying `type`, `index` (defaults to `0`),
and has an optional ID.
Expand Down Expand Up @@ -822,9 +830,15 @@ you can use anywhere in your tests called `keyboard`.
This is useful for a number of things, like checking if the keyboard is visible:

```swift
// If using Swift 5.4 or below
keyboard.await(.visible)
```

```swift
// If using Swift 5.5
keyboard.waitFor(.visible)
```

Checking if the current softare keyboard is the expected type:

```swift
Expand Down Expand Up @@ -1047,10 +1061,16 @@ You can, however, assert the states of the buttons, like checking if the buttons
enabled:

```swift
// If using Swift 5.4 or below
stepper.decrementButton.await(not: .enabled, timeout: 1) // Waits a max of 1 second for the button to be disabled
```

You can learn more about `await(not:)` and other `Element` methods in the
```swift
// If using Swift 5.5
stepper.decrementButton.waitFor(not: .enabled, timeout: 1) // Waits a max of 1 second for the button to be disabled
```

You can learn more about `await(not:)`/`waitFor(not:)` and other `Element` methods in the
documentation for [`Element`](#element).

#### SegmentedControl
Expand Down Expand Up @@ -1724,10 +1744,17 @@ that anything conforming to `Element` will have access to.
You can wait for the element to be (or not be) in a particular state:

```swift
// If using Swift 5.4 or below
button.await(.visible, .enabled, timeout: 10) // You can provide more than one state to wait for :)
button.await(not: .enabled, timeout: 10)
```

```swift
// If using Swift 5.5
button.waitFor(.visible, .enabled, timeout: 10) // You can provide more than one state to wait for :)
button.waitFor(not: .enabled, timeout: 10)
```

If the element doesn't become the expected state within the timeout, the test will fail.

If you're not using **TABTestKit** [contexts](#contexts), it is extremely advisable to
Expand Down
2 changes: 1 addition & 1 deletion TABTestKit.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TABTestKit'
s.version = '1.7.1'
s.version = '1.8.0'
s.summary = 'Strongly typed Swift wrapper around XCTest / XCUI, enabling you to write BDD-style automation tests, without writing much code at all.'
s.homepage = 'https://github.com/theappbusiness/TABTestKit'
s.license = { :type => 'MIT', :file => 'LICENSE' }
Expand Down
12 changes: 10 additions & 2 deletions TABTestKit/Classes/Contexts/InteractionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ public extension InteractionContext {
}

func state(of element: Element, is states: ElementAttributes.State...) {
states.forEach { element.await($0) }
#if swift(>=5.5)
states.forEach { element.waitFor($0) }
#else
states.forEach { element.await($0) }
#endif
}

func state(of element: Element, isNot states: ElementAttributes.State...) {
states.forEach { element.await(not: $0) }
#if swift(>=5.5)
states.forEach { element.waitFor(not: $0) }
#else
states.forEach { element.await(not: $0) }
#endif
}

func scroll(_ element: Scrollable, _ direction: ElementAttributes.Direction, until otherElement: Element, is states: ElementAttributes.State..., maxTries: Int = 10) {
Expand Down
12 changes: 10 additions & 2 deletions TABTestKit/Classes/Contexts/NavigationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,22 @@ public extension NavigationContext {
///
/// - Parameter element: The element to await.
func see(_ element: Element) {
element.await(.exists, .visible)
#if swift(>=5.5)
element.waitFor(.exists, .visible)
#else
element.await(.exists, .visible)
#endif
}

/// Asserts that an element does not exist, by waiting for it to not exist.
///
/// - Parameter element: The element to await.
func doNotSee(_ element: Element) {
element.await(not: .exists)
#if swift(>=5.5)
element.waitFor(not: .exists)
#else
element.await(not: .exists)
#endif
}

/// Completes one or more things that knows how to complete itself.
Expand Down
6 changes: 5 additions & 1 deletion TABTestKit/Classes/Elements/Keyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ public struct Keyboard: Element {
/// The current keyboard type.
/// Attempting to access this before the keyboard is visible will fail the test.
public var keyboardType: KeyboardType {
await(.exists, .visible)
#if swift(>=5.5)
waitFor(.exists, .visible)
#else
await(.exists, .visible)
#endif
guard let type = KeyboardType.allCases.first(where: expectedKeysExist) else { XCTFatalFail("Unable to determine keyboard type") }
return type
}
Expand Down
6 changes: 5 additions & 1 deletion TABTestKit/Classes/Elements/TabBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ public struct TabBar: Element {
extension TabBar {

public var numberOfTabs: Int {
await(.exists, .hittable)
#if swift(>=5.5)
waitFor(.exists, .hittable)
#else
await(.exists, .hittable)
#endif
return underlyingXCUIElement.buttons.count
}

Expand Down
4 changes: 2 additions & 2 deletions TABTestKit/Classes/Protocols/Completable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import Foundation
///
/// This works particularly well with NavigationContext.
public protocol Completable {

func await()
func complete()

}
6 changes: 5 additions & 1 deletion TABTestKit/Classes/Protocols/Dismissable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public protocol Dismissable {
public extension Element where Self: Dismissable {

func await() {
await(.exists, .hittable)
#if swift(>=5.5)
waitFor(.exists, .hittable)
#else
await(.exists, .hittable)
#endif
}

}
12 changes: 10 additions & 2 deletions TABTestKit/Classes/Protocols/Editable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@ public protocol Editable {
public extension Element where Self: Editable {

func type(_ text: String) {
await(.exists, .hittable)
#if swift(>=5.5)
waitFor(.exists, .hittable)
#else
await(.exists, .hittable)
#endif
underlyingXCUIElement.typeText(text)
}

func delete(numberOfCharacters: Int) {
await(.exists, .hittable)
#if swift(>=5.5)
waitFor(.exists, .hittable)
#else
await(.exists, .hittable)
#endif
let deletionCharacters = String(repeating: XCUIKeyboardKey.delete.rawValue, count: numberOfCharacters)
underlyingXCUIElement.typeText(deletionCharacters)
}
Expand Down
71 changes: 52 additions & 19 deletions TABTestKit/Classes/Protocols/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,60 @@ public extension Element {
///
/// - Parameter states: The states to wait for.
/// - Parameter timeout: The timeout. Defaults to 30 seconds.
@available(swift, introduced: 5.0, obsoleted: 5.5, renamed: "waitFor", message: "This method has been obsoleted to avoid conflicts with the new await concurrency keyword")
func await(_ states: ElementAttributes.State..., timeout: TimeInterval = 30) {
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(state, timeout: timeout), "Failed awaiting element to be \(state) with timeout \(timeout)")
}
}

/// Converse to the `await(_ states...` function, this waits for the element to _not_ be in the states
/// provided.
/// For example, you could use this to wait for an element that you're expecting to become not hittable:
/// `await(not: .hittable)`
///
/// - Parameters:
/// - states: The states to wait for the element to _not_ be in.
/// - timeout: The timout. Defaults to 30 seconds.
func await(not states: ElementAttributes.State..., timeout: TimeInterval = 30) {
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(not: state, timeout: timeout), "Failed awaiting element to not be \(state) with timeout \(timeout)")
}
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(state, timeout: timeout), "Failed awaiting element to be \(state) with timeout \(timeout)")
}
}

/// Converse to the `await(_ states...` function, this waits for the element to _not_ be in the states
/// provided.
/// For example, you could use this to wait for an element that you're expecting to become not hittable:
/// `await(not: .hittable)`
///
/// - Parameters:
/// - states: The states to wait for the element to _not_ be in.
/// - timeout: The timout. Defaults to 30 seconds.
@available(swift, introduced: 5.0, obsoleted: 5.5, renamed: "waitFor", message: "This method has been obsoleted to avoid conflicts with the new await concurrency keyword")
func await(not states: ElementAttributes.State..., timeout: TimeInterval = 30) {
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(not: state, timeout: timeout), "Failed awaiting element to be \(state) with timeout \(timeout)")
}
}

/// Waits for the provided states to be true with a max timeout.
/// Unlike the standard `determine` function which returns the state after a max duration, this function will fail the test if any of the states do not become true before the timeout.
///
/// You can provide multiple states, like `waitFor(.exists, .hittable)`
///
/// - Parameter states: The states to wait for.
/// - Parameter timeout: The timeout. Defaults to 30 seconds.
@available(swift, introduced: 5.5)
func waitFor(_ states: ElementAttributes.State..., timeout: TimeInterval = 30) {
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(state, timeout: timeout), "Failed awaiting element to be \(state) with timeout \(timeout)")
}
}

/// Converse to the `waitFor(_ states...` function, this waits for the element to _not_ be in the states
/// provided.
/// For example, you could use this to wait for an element that you're expecting to become not hittable:
/// `waitFor(not: .hittable)`
///
/// - Parameters:
/// - states: The states to wait for the element to _not_ be in.
/// - timeout: The timout. Defaults to 30 seconds.
@available(swift, introduced: 5.5)
func waitFor(not states: ElementAttributes.State..., timeout: TimeInterval = 30) {
guard !states.isEmpty else { XCTFatalFail("You must provide at least one state!") }
states.forEach { state in
XCTAssertTrue(determine(not: state, timeout: timeout), "Failed awaiting element to not be \(state) with timeout \(timeout)")
}
}

/// Determines the sates for an element, within a a maximum duration.
/// If the element becomes (or already is) in the correct state this function will exit early,
Expand Down
6 changes: 5 additions & 1 deletion TABTestKit/Classes/Protocols/Screen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public protocol Screen {
public extension Screen {

func await() {
trait.await(.exists, .hittable, .visible)
#if swift(>=5.5)
trait.waitFor(.exists, .hittable, .visible)
#else
trait.await(.exists, .hittable, .visible)
#endif
}

}
6 changes: 5 additions & 1 deletion TABTestKit/Classes/Protocols/Scrollable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public protocol Scrollable {
public extension Element where Self: Scrollable {

func scroll(_ direction: ElementAttributes.Direction) {
await(.exists, .hittable)
#if swift(>=5.5)
waitFor(.exists, .hittable)
#else
await(.exists, .hittable)
#endif
switch direction {
case .upwards:
scroll(from: .topThird, to: .middle)
Expand Down
Loading

0 comments on commit 4050c52

Please sign in to comment.