{"id":21700703,"url":"https://github.com/zhipingyang/einstein","last_synced_at":"2025-04-12T13:34:39.791Z","repository":{"id":53064912,"uuid":"199754050","full_name":"ZhipingYang/Einstein","owner":"ZhipingYang","description":"Einstein is an UITest framework that integrates the logic across the Project and UITest through AccessibilityIdentified. And in UITest, using it to better support test code writing.","archived":false,"fork":false,"pushed_at":"2021-04-09T08:33:50.000Z","size":378,"stargazers_count":16,"open_issues_count":4,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-26T08:08:31.568Z","etag":null,"topics":["rawrepresentable-extension","swift","swift5","ui-testing","uikit-accessibilityidentifier","uitest","uitesting","xctest","xcuielement","xcuitest"],"latest_commit_sha":null,"homepage":"https://zhipingyang.github.io/Einstein/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZhipingYang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-31T01:24:57.000Z","updated_at":"2024-12-02T20:44:14.000Z","dependencies_parsed_at":"2022-08-21T04:20:36.478Z","dependency_job_id":null,"html_url":"https://github.com/ZhipingYang/Einstein","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhipingYang%2FEinstein","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhipingYang%2FEinstein/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhipingYang%2FEinstein/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhipingYang%2FEinstein/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZhipingYang","download_url":"https://codeload.github.com/ZhipingYang/Einstein/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248573763,"owners_count":21126898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rawrepresentable-extension","swift","swift5","ui-testing","uikit-accessibilityidentifier","uitest","uitesting","xctest","xcuielement","xcuitest"],"created_at":"2024-11-25T20:16:24.419Z","updated_at":"2025-04-12T13:34:39.765Z","avatar_url":"https://github.com/ZhipingYang.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg width=150 src=\"https://user-images.githubusercontent.com/9360037/62184933-ecbe4380-b392-11e9-82dd-802b6b2e8b82.png\"\u003e\n\u003c/p\u003e\n\n\n\u003cbr\u003e\n\u003cp align=\"center\"\u003e\n\t\u003ca href=\"https://zhipingyang.github.io/Einstein\"\u003e\n        \u003cimg alt=\"Documentation\" src=\"http://img.shields.io/badge/read_the-docs-2196f3.svg\"\u003e\n\t\u003c/a\u003e\n\t\u003ca href=\"http://cocoapods.org/pods/Einstein\"\u003e\n\t\t\u003cimage alt=\"Version\" src=\"https://img.shields.io/cocoapods/v/Einstein.svg?style=flat\"\u003e\n\t\u003c/a\u003e\n\t\u003cimage alt=\"CI Status\" src=\"https://img.shields.io/badge/Swift-5.0-orange.svg\"\u003e\n\t\u003ca href=\"http://cocoapods.org/pods/Einstein\"\u003e\n\t\t\u003cimage alt=\"License\" src=\"https://img.shields.io/cocoapods/l/Einstein.svg?style=flat\"\u003e\n\t\u003c/a\u003e\n\t\u003ca href=\"http://cocoapods.org/pods/Einstein\"\u003e\n\t\t\u003cimage alt=\"Platform\" src=\"https://img.shields.io/cocoapods/p/Einstein.svg?style=flat\"\u003e\n\t\u003c/a\u003e\n\t\u003ca href=\"https://travis-ci.com/ZhipingYang/Einstein\"\u003e\n\t\t\u003cimage alt=\"CI Status\" src=\"https://www.travis-ci.com/ZhipingYang/Einstein.svg?branch=master\"\u003e\n\t\u003c/a\u003e\n\u003c/p\u003e\n\n\n\u003e **Einstein** is an UITest framework which integrates the business logic across the Project and UITest through [AccessibilityIdentifier](https://github.com/ZhipingYang/Einstein/blob/master/Class/Identifier/AccessibilityIdentifier.swift). And on UITest, using [EasyPredict](https://github.com/ZhipingYang/Einstein/blob/master/Class/UITest/Model/EasyPredicate.swift) and [Extensions](https://github.com/ZhipingYang/Einstein/tree/master/Class/UITest/Extensions) to better support UITest code writing\n\n### Comparative sample\n\nin `XCTestCase`, type the phone number to login\n\n\u003e 👍 Use Einstein ↓\n\u003e\n\u003e ```swift\n\u003e LoginAccessID.SignIn.phoneNumber.element\n\u003e\t.assertBreak(predicate: .exists(true))?\n\u003e\t.clearAndType(text: \"MyPhoneNumber\")\n\u003e ```\n\u003e 😵 without Einstein ↓\n\u003e \n\u003e ```swift \n\u003e let element = app.buttons[\"LoginAccessID_SignIn_phoneNumber\"]\n\u003e let predicate = NSPredicate(format: \"exists == true\")\n\u003e let promise = self.expectation(for: predicate, evaluatedWith: element, handler: nil)\n\u003e let result = XCTWaiter().wait(for: [promise], timeout: 10)\n\u003e if result == XCTWaiter.Result.completed {\n\u003e     let stringValue = (element.value as? String) ?? \"\"\n\u003e     let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined()\n\u003e     element.typeText(deleteString)\n\u003e     element.typeText(\"MyPhoneNumber\")\n\u003e } else {\n\u003e     assertionFailure(\"LoginAccessID_SignIn_phoneNumber element is't existe\")\n\u003e }\n\u003e ```\n\n### File structures\n\n```\n─┬─ Einstein\n ├─┬─ Identifier: -\u003e `UIKit`\n │ └─── AccessibilityIdentifier.swift\n │\n └─┬─ UITest: -\u003e `Einstein/Identifier` \u0026 `XCTest` \u0026 `Then`\n   ├─┬─ Model\n   │ ├─── EasyPredicate.swift\n   │ └─── Springboard.swift\n   └─┬─ Extensions\n     ├─── RawRepresentable+helpers.swift\n     ├─── PrettyRawRepresentable+helpers.swift\n     ├─── XCTestCase+helpers.swift\n     ├─── XCUIElement+helpers.swift\n     └─── XCUIElementQuery+helpers.swift\n```\n\n### Install\n\n\u003e required `iOS \u003e= 9.0` `Swift5.0` with [Cocoapods](https://cocoapods.org/)\n\n```ruby\ntarget 'XXXProject' do\n\n  # in project target\n  pod 'Einstein/Identifier' \n  \n  target 'XXXProjectUITests' do\n    # in UITest target\n    pod 'Einstein'\n  end\nend\n```\n\n# Using\n\n- AccessibilityIdentifier\n\t- Project target\n\t- UITest target\n\t- Apply in UITest\n- EasyPredicate\n- Extensions\n\n## 1. AccessibilityIdentifier\n\n\u003e **Note:** \u003cbr\u003e\n\u003e all the UIKit's accessibilityIdentifier is a preperty of the protocol `UIAccessibilityIdentification` and all enum's rawValue is default to follow `RawRepresentable`\n\u003e\u003cblockquote\u003e\n\n\u003cdetails\u003e\u003csummary\u003e Expand for steps details \u003c/summary\u003e\n\u003cbr\u003e\n\t\n- 1.1 Define the enums\n\t- set rawValue in String\n\t- append PrettyRawRepresentable if need\n- 1.2 set UIKit's accessibilityIdentifier by enums's rawValue\n\t- method1: infix operator\n\t- method2: UIAccessibilityIdentification's extension\n- 1.3 Apply in UITest target\n\u003c/details\u003e\u003c/blockquote\u003e\n\n### 1.1 Define the enums\n\n```swift \nstruct LoginAccessID {\n    enum SignIn: String {\n        case signIn, phoneNumber, password\n    }\n    enum SignUp: String {\n        case signUp, phoneNumber\n    }\n    enum Forget: String, PrettyRawRepresentable {\n        case phoneNumber // and so on\n    }\n}\n```\n\nI 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.\n\n```swift\n// for example:\n\nlet str1 = LoginAccessID.SignIn.phoneNumber\nlet str2 = LoginAccessID.SignUp.phoneNumber\nlet str3 = LoginAccessID.Forget.phoneNumber // had add PrettyRawRepresentable\n\nstr1 == \"phoneNumber\"\nstr2 == \"phoneNumber\" \nstr3 == \"LoginAccessID_Forget_phoneNumber\"\n```\n[see more: PrettyRawRepresentable](https://github.com/ZhipingYang/Einstein/blob/master/Class/share/AccessibilityIdentifier.swift#L45)\n\n### 1.2 set UIKit's accessibilityIdentifier by enums's rawValue\n\n```swift\n// system way\nsignInPhoneTextField.accessibilityIdentifier = \"LoginAccessID_SignIn_phoneNumber\"\n\n// define infix operator \u003c\u003c\u003c\nforgetPhoneTextField \u003c\u003c\u003c LoginAccessID.Forget.phoneNumber\n\nprint(forgetPhoneTextField.accessibilityIdentifier)\n// \"LoginAccessID_Forget_phoneNumber\"\n```\n\n### 1.3. Apply in UITest target\n\n\u003e **Note:** \u003cbr\u003e\n\u003e Firstly\n\u003e Import the defined enums file in UITest\n\u003e \n\u003e - Method 1: Set it's `target membership` as true both in XXXProject and XXXUITest\n\u003e - Method 2: Import project files in UITest with @testable [Link: how to set](https://stackoverflow.com/questions/32008403/no-such-module-when-using-testable-in-xcode-unit-tests)\n\u003e \n\u003e ```swift\n\u003e @testable import XXXPreject\n\u003e ```\n\n```swift\n// extension the protocol RawRepresentable and it's RawValue == String\n\ntypealias SignInPage = LoginAccessID.SignIn\n\n// type the phone number\nSignInPage.phoneNumber.element.waitUntilExists().clearAndType(text: \"myPhoneNumber\")\n\n// type passward\nSignInPage.password.element.clearAndType(text: \"******\")\n\n// start login\nSignInPage.signIn.element.assert(predicate: .isEnabled(true)).tap()\n```\n\n## 2. EasyPredicate\n\u003e **Note:** \u003cbr\u003e\n\u003e EasyPredicate's RawValue is `PredicateRawValue` (a another enum to manage logic and convert NSPredicate). \u003cbr\u003e\n\u003e\u003cblockquote\u003e\n\n\u003cdetails\u003e\u003csummary\u003e Expand for EasyPredicate's cases \u003c/summary\u003e\n\u003cbr\u003e\n\n```swift\npublic enum EasyPredicate: RawRepresentable {   \n    case exists(_ exists: Bool)\n    case isEnabled(_ isEnabled: Bool)\n    case isHittable(_ isHittable: Bool)\n    case isSelected(_ isSelected: Bool)\n    case label(_ comparison: Comparison, _ value: String)\n    case identifier(_ identifier: String)\n    case type(_ type: XCUIElement.ElementType)\n    case other(_ ragular: String)\n}\n```\n\u003c/details\u003e\u003c/blockquote\u003e\n\nAlthough `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\n\n```swift\n// use EasyPredicate\nlet targetElement = query.filter(predicate: .label(.beginsWith, \"abc\")).element\n\n// use NSPredicate\nlet predicate = NSPredicate(format: \"label BEGINSWITH 'abc'\")\nlet targetElement = query.element(matching: predicate).element\n```\n\nEasyPredicate Merge\n\n```swift\n// \"elementType == 0 \u0026\u0026 exists == true \u0026\u0026 label BEGINSWITH 'abc'\"\nlet predicate: EasyPredicate = [.type(.button), .exists(true), .label(.beginsWith, \"abc\")].merged()\n\n// \"elementType == 0 || exists == true || label BEGINSWITH 'abc'\"\nlet predicate: EasyPredicate = [.type(.button), .exists(true), .label(.beginsWith, \"abc\")].merged(withLogic: .or)\n\n```\n\n\n## 3. UITest Extensions\n\n### 3.1 extension String\n\n```swift\n/*\n Note: string value can be a RawRepresentable and String at the same time\n for example:\n `let element: XCUIElement = \"SomeString\".element`\n */\nextension String: RawRepresentable {\n    public var rawValue: String { return self }\n    public init?(rawValue: String) {\n        self = rawValue\n    }\n}\n```\n\u003cbr\u003e\n\n### 3.2 extension RawRepresentable\n\n\u003cdetails open\u003e\n  \u003csummary\u003e Expand for Sequence where Element: RawRepresentable \u003c/summary\u003e\n\n```swift\npublic extension Sequence where Element: RawRepresentable, Element.RawValue == String {\n    \n    /// get the elements which match with identifiers and predicates limited in timeout\n    ///\n    /// - Parameters:\n    ///   - predicates: predicates as the match rules\n    ///   - logic: relation of predicates\n    ///   - timeout: if timeout == 0, return the elements immediately otherwise retry until timeout\n    /// - Returns: get the elements\n    func elements(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType, timeout: Int) -\u003e [XCUIElement] {}\n    \n    /// get the first element was matched predicate\n    func anyElement(predicate: EasyPredicate) -\u003e XCUIElement? {}\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e Expand for RawRepresentable extension \u003c/summary\u003e\n\n```swift\n/*\n Get the `XCUIElement` from RawRepresentable's RawValue which also been used as accessibilityIdentifier\n */\npublic extension RawRepresentable where RawValue == String {\n    var element: XCUIElement {}\n    var query: XCUIElementQuery {}\n    var count: Int {}\n    subscript(i: Int) -\u003e XCUIElement {}   \n    func queryFor(identifier: Self) -\u003e XCUIElementQuery {}\n}\n```\n\u003c/details\u003e\n\u003cbr\u003e\n\n### 3.3 extension XCUIElement\n\n\u003cdetails open\u003e\n  \u003csummary\u003e Expand for XCUIElement (Base) \u003c/summary\u003e\n\n```swift\npublic extension PredicateBaseExtensionProtocol where Self == T {\n\n    /// create a new preicate with EasyPredicates and LogicalType to judge is it satisfied on self\n    ///\n    /// - Parameters:\n    ///   - predicates: predicates rules\n    ///   - logic: predicates relative\n    /// - Returns: tuple of result and self\n    @discardableResult\n    func waitUntil(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and, timeout: TimeInterval = 10, handler: XCTNSPredicateExpectation.Handler? = nil) -\u003e (result: XCTWaiter.Result, element: T) {\n        if predicates.count \u003c= 0 { fatalError(\"predicates cannpt be empty!\") }\n        \n        let test = XCTestCase().then { $0.continueAfterFailure = true }\n        let promise = test.expectation(for: predicates.toPredicate(logic), evaluatedWith: self, handler: handler)\n        let result = XCTWaiter().wait(for: [promise], timeout: timeout)\n        return (result, self)\n    }\n    \n    /// assert by new preicate with EasyPredicates and LogicalType, if assert is passed then return self or return nil\n    ///\n    /// - Parameters:\n    ///   - predicates: rules\n    ///   - logic: predicates relative\n    /// - Returns: self or nil\n    @discardableResult\n    func assertBreak(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e T? {\n        if predicates.first == nil { fatalError(\"❌ predicates can't be empty\") }\n        \n        let filteredElements = ([self] as NSArray).filtered(using: predicates.toPredicate(logic))\n        if filteredElements.isEmpty {\n            let predicateStr = predicates.map { \"\\n \u003c\\($0.rawValue.regularString)\u003e\" }.joined()\n            assertionFailure(\"❌ \\(self) is not satisfied logic:\\(logic) about rules: \\(predicateStr)\")\n        }\n        return filteredElements.isEmpty ? nil : self\n    }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e Expand for XCUIElement base extensioin \u003c/summary\u003e\n\n```swift\n\n// MARK: - wait\n@discardableResult\nfunc waitUntil(predicate: EasyPredicate, timeout: TimeInterval = 10, handler: XCTNSPredicateExpectation.Handler? = nil) -\u003e (result: XCTWaiter.Result, element: XCUIElement) {}\n\n@discardableResult\nfunc waitUntilExists(timeout: TimeInterval = 10) -\u003e (result: XCTWaiter.Result, element: XCUIElement) {}\n\n@discardableResult\nfunc wait(_ s: UInt32 = 1) -\u003e XCUIElement {}\n\n// MARK: - assert\n@discardableResult\nfunc assertBreak(predicate: EasyPredicate) -\u003e XCUIElement? {}\n\n@discardableResult\nfunc assert(predicate: EasyPredicate) -\u003e XCUIElement {}\n\n@discardableResult\nfunc waitUntilExistsAssert(timeout: TimeInterval = 10) -\u003e XCUIElement {}\n\n@discardableResult\nfunc assert(predicate: EasyPredicate, timeout: TimeInterval = 10) -\u003e XCUIElement {}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e Expand for XCUIElement custom extensioin \u003c/summary\u003e\n\n```swift\n// MARK: - Extension\npublic extension XCUIElement {\n    \n    /// get the results in the descendants which matching the EasyPredicates\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate's rules\n    ///   - logic: rule's relate\n    /// - Returns: result target\n    @discardableResult\n    func descendants(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElementQuery {}\n    @discardableResult\n    func descendants(predicate: EasyPredicate) -\u003e XCUIElementQuery {}\n    \n    /// Returns a query for direct children of the element matching with EasyPredicates\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate rules\n    ///   - logic: rules relate\n    /// - Returns: result query\n    @discardableResult\n    func children(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElementQuery {}\n    @discardableResult\n    func children(predicate: EasyPredicate) -\u003e XCUIElementQuery {}\n    \n    /// Wait until it's available and then type a text into it.\n    @discardableResult\n    func tapAndType(text: String, timeout: TimeInterval = 10) -\u003e XCUIElement {}\n    \n    /// Wait until it's available and clear the text, then type a text into it.\n    @discardableResult\n    func clearAndType(text: String, timeout: TimeInterval = 10) -\u003e XCUIElement {}\n    \n    @discardableResult\n    func hidenKeyboard(inApp: XCUIApplication) -\u003e XCUIElement {}\n    \n    @discardableResult\n    func setSwitch(on: Bool, timeout: TimeInterval = 10) -\u003e XCUIElement  {}\n    \n    @discardableResult\n    func forceTap(timeout: TimeInterval = 10) -\u003e XCUIElement {}\n    \n    @discardableResult\n    func tapIfExists(timeout: TimeInterval = 10) -\u003e XCUIElement {}\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e Expand for Sequence: XCUIElement \u003cXCUIElement\u003e extension \u003c/summary\u003e\n\n```swift\nextension Sequence where Element: XCUIElement {\n    \n    /// get the elements which match with identifiers and predicates limited in timeout\n    ///\n    /// - Parameters:\n    ///   - predicates: predicates as the match rules\n    ///   - logic: relation of predicates\n    ///   - timeout: if timeout == 0, return the elements immediately otherwise retry until timeout\n    /// - Returns: get the elements\n    func elements(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType, timeout: Int) -\u003e [Element] {}\n    \n    /// get the first element was matched predicate\n    func anyElement(predicate: EasyPredicate) -\u003e Element? {}\n}\n```\n\u003c/details\u003e\n\n\u003cbr\u003e\n\n### 3.4 extension XCUIElementQuery\n\n\u003cdetails open\u003e\n  \u003csummary\u003e Expand for XCUIElementQuery extension \u003c/summary\u003e\n\n```swift\npublic extension XCUIElementQuery {\n    /// safe to get index\n    ///\n    /// - Parameter index: index\n    /// - Returns: optional element\n    func element(safeIndex index: Int) -\u003e XCUIElement? {    }\n    \n    /// asset empty of query\n    ///\n    /// - Parameter empty: bool value\n    /// - Returns: optional query self\n    func assertEmpty(empty: Bool = false) -\u003e XCUIElementQuery? {    }\n\n    /// get the results which matching the EasyPredicates\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate's rules\n    ///   - logic: rules relate\n    /// - Returns: ElementQuery\n    func matching(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElementQuery {    }\n    func matching(predicate: EasyPredicate) -\u003e XCUIElementQuery {    }\n    \n    /// get the taget element which matching the EasyPredicates\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate's rules\n    ///   - logic: rule's relate\n    /// - Returns: result target\n    func element(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElement {    }\n    func element(predicate: EasyPredicate) -\u003e XCUIElement {    }\n\n    /// get the results in the query's descendants which matching the EasyPredicates\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate's rules\n    ///   - logic: rule's relate\n    /// - Returns: result target\n    func descendants(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElementQuery {    }\n    func descendants(predicate: EasyPredicate) -\u003e XCUIElementQuery {    }\n\n    /// filter the query by rules to create new query\n    ///\n    /// - Parameters:\n    ///   - predicates: EasyPredicate's rules\n    ///   - logic: rule's relate\n    /// - Returns: result target\n    func containing(predicates: [EasyPredicate], logic: NSCompoundPredicate.LogicalType = .and) -\u003e XCUIElementQuery {    }\n    func containing(predicate: EasyPredicate) -\u003e XCUIElementQuery {    }\n}\n```\n\n\u003c/details\u003e\n\u003cbr\u003e\n\n### 3.5 extension XCTestCase\n\n\u003cdetails open\u003e\n  \u003csummary\u003e Expand for XCTestCase (runtime) \u003c/summary\u003e\n\n```swift\n/**\n associated object\n */\npublic extension XCTestCase {\n    private struct XCTestCaseAssociatedKey { \n    \tstatic var app = 0 \n    }\n    var app: XCUIApplication {\n        set {\n            objc_setAssociatedObject(self, \u0026XCTestCaseAssociatedKey.app, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)\n        }\n        get {\n            let _app = objc_getAssociatedObject(self, \u0026XCTestCaseAssociatedKey.app) as? XCUIApplication\n            guard let app = _app else { return XCUIApplication().then { self.app = $0 } }\n            return app\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e Expand for XCTestCase extension \u003c/summary\u003e\n\n```swift\n\npublic extension XCTestCase {\n    \n    // MARK: - methods\n    func isSimulator() -\u003e Bool {}\n    func takeScreenshot(activity: XCTActivity, name: String = \"Screenshot\") {}\n    func takeScreenshot(groupName: String = \"--- Screenshot ---\", name: String = \"Screenshot\") {}\n    func group(text: String = \"Group\", closure: (_ activity: XCTActivity) -\u003e ()) {}\n    func hideAlertsIfNeeded() {}\n    func setAirplane(_ value: Bool) {}\n    func deleteMyAppIfNeed() {}\n    \n    /// Try to force launch the application. This structure tries to ovecome the issues described at https://forums.developer.apple.com/thread/15780\n    func tryLaunch\u003cT: RawRepresentable\u003e(arguments: [T], count counter: Int = 10, wait: UInt32 = 2) where T.RawValue == String {}\n    \n    func tryLaunch(count counter: Int = 10) {}\n    \n    func killAppAndRelaunch() {}\n    \n    /// Try to force closing the application\n    func tryTearDown(wait: UInt32 = 2) {}\n}\n```\n\u003c/details\u003e\n\n## Author\n\nXcodeYang, xcodeyang@gmail.com\n\n## License\n\nEinstein is available under the MIT license. See the LICENSE file for more info.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhipingyang%2Feinstein","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhipingyang%2Feinstein","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhipingyang%2Feinstein/lists"}