Einstein is an UITest framework which integrates the business logic across the Project and UITest through AccessibilityIdentifier. And on UITest, using EasyPredict and Extensions to better support UITest code writing
Comparative sample
in XCTestCase
, type the phone number to login
👍 Use Einstein ↓
LoginAccessID.SignIn.phoneNumber.element .assertBreak(predicate: .exists(true))? .clearAndType(text: "MyPhoneNumber")
😵 without Einstein ↓
let element = app.buttons["LoginAccessID_SignIn_phoneNumber"] let predicate = NSPredicate(format: "exists == true") let promise = self.expectation(for: predicate, evaluatedWith: element, handler: nil) let result = XCTWaiter().wait(for: [promise], timeout: 10) if result == XCTWaiter.Result.completed { let stringValue = (element.value as? String) ?? "" let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined() element.typeText(deleteString) element.typeText("MyPhoneNumber") } else { assertionFailure("LoginAccessID_SignIn_phoneNumber element is't existe") }
File structures
─┬─ Einstein
├─┬─ Identifier: -> `UIKit`
│ └─── AccessibilityIdentifier.swift
│
└─┬─ UITest: -> `Einstein/Identifier` & `XCTest` & `Then`
├─┬─ Model
│ ├─── EasyPredicate.swift
│ └─── Springboard.swift
└─┬─ Extensions
├─── RawRepresentable+helpers.swift
├─── PrettyRawRepresentable+helpers.swift
├─── XCTestCase+helpers.swift
├─── XCUIElement+helpers.swift
└─── XCUIElementQuery+helpers.swift
Install
required
iOS >= 9.0
Swift5.0
with Cocoapods
target 'XXXProject' do
# in project target
pod 'Einstein/Identifier'
target 'XXXProjectUITests' do
# in UITest target
pod 'Einstein'
end
end
Using
- AccessibilityIdentifier
- Project target
- UITest target
- Apply in UITest
- EasyPredicate
- Extensions
1. AccessibilityIdentifier
Note:
all the UIKit’s accessibilityIdentifier is a preperty of the protocolUIAccessibilityIdentification
and all enum’s rawValue is default to followRawRepresentable
- 1.1 Define the enums
- set rawValue in String
- append PrettyRawRepresentable if need
- 1.2 set UIKit’s accessibilityIdentifier by enums’s rawValue
- method1: infix operator
- method2: UIAccessibilityIdentification’s extension
- 1.3 Apply in UITest target
1.1 Define the enums
struct LoginAccessID {
enum SignIn: String {
case signIn, phoneNumber, password
}
enum SignUp: String {
case signUp, phoneNumber
}
enum Forget: String, PrettyRawRepresentable {
case phoneNumber // and so on
}
}
I highly recommend adding PrettyRawRepresentable
protocol on enums, then you will get the RawValue string with the property path to avoid accessibilityIdentifier be samed in diff pages.
// for example:
let str1 = LoginAccessID.SignIn.phoneNumber
let str2 = LoginAccessID.SignUp.phoneNumber
let str3 = LoginAccessID.Forget.phoneNumber // had add PrettyRawRepresentable
str1 == "phoneNumber"
str2 == "phoneNumber"
str3 == "LoginAccessID_Forget_phoneNumber"
see more: PrettyRawRepresentable
1.2 set UIKit’s accessibilityIdentifier by enums’s rawValue
// system way
signInPhoneTextField.accessibilityIdentifier = "LoginAccessID_SignIn_phoneNumber"
// define infix operator <<<
forgetPhoneTextField <<< LoginAccessID.Forget.phoneNumber
print(forgetPhoneTextField.accessibilityIdentifier)
// "LoginAccessID_Forget_phoneNumber"
1.3. Apply in UITest target
Note:
Firstly Import the defined enums file in UITest
- Method 1: Set it’s
target membership
as true both in XXXProject and XXXUITest- Method 2: Import project files in UITest with @testable Link: how to set
@testable import XXXPreject
// extension the protocol RawRepresentable and it's RawValue == String
typealias SignInPage = LoginAccessID.SignIn
// type the phone number
SignInPage.phoneNumber.element.waitUntilExists().clearAndType(text: "myPhoneNumber")
// type passward
SignInPage.password.element.clearAndType(text: "******")
// start login
SignInPage.signIn.element.assert(predicate: .isEnabled(true)).tap()
2. EasyPredicate
Note:
EasyPredicate’s RawValue isPredicateRawValue
(a another enum to manage logic and convert NSPredicate).
public enum EasyPredicate: RawRepresentable {
case exists(_ exists: Bool)
case isEnabled(_ isEnabled: Bool)
case isHittable(_ isHittable: Bool)
case isSelected(_ isSelected: Bool)
case label(_ comparison: Comparison, _ value: String)
case identifier(_ identifier: String)
case type(_ type: XCUIElement.ElementType)
case other(_ ragular: String)
}
Although NSPredicate
is powerful, the developer program interface is not good enough, we can try to convert the hard code style into the object-oriented style. and this is what EasyPredicate do
// use EasyPredicate
let targetElement = query.filter(predicate: .label(.beginsWith, "abc")).element
// use NSPredicate
let predicate = NSPredicate(format: "label BEGINSWITH 'abc'")
let targetElement = query.element(matching: predicate).element
EasyPredicate Merge
// "elementType == 0 && exists == true && label BEGINSWITH 'abc'"
let predicate: EasyPredicate = [.type(.button), .exists(true), .label(.beginsWith, "abc")].merged()
// "elementType == 0 || exists == true || label BEGINSWITH 'abc'"
let predicate: EasyPredicate = [.type(.button), .exists(true), .label(.beginsWith, "abc")].merged(withLogic: .or)
Author
XcodeYang, xcodeyang@gmail.com
License
Einstein is available under the MIT license. See the LICENSE file for more info.