{"id":20545489,"url":"https://github.com/eonist/testrunner","last_synced_at":"2026-03-17T20:03:08.442Z","repository":{"id":93635155,"uuid":"213867370","full_name":"eonist/TestRunner","owner":"eonist","description":"Simplifies running UI-tests 🏃","archived":false,"fork":false,"pushed_at":"2025-03-02T20:49:55.000Z","size":1885,"stargazers_count":12,"open_issues_count":14,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-03T18:10:58.132Z","etag":null,"topics":["automation","ios","macos","quality-assurance","swift","test-runner","uitesting","xcode","xcuitest"],"latest_commit_sha":null,"homepage":"","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/eonist.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-10-09T08:48:03.000Z","updated_at":"2025-07-03T06:13:32.000Z","dependencies_parsed_at":"2023-10-16T13:13:10.800Z","dependency_job_id":"ece315b3-dae5-42ca-974e-5c0583baeb44","html_url":"https://github.com/eonist/TestRunner","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/eonist/TestRunner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eonist%2FTestRunner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eonist%2FTestRunner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eonist%2FTestRunner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eonist%2FTestRunner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eonist","download_url":"https://codeload.github.com/eonist/TestRunner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eonist%2FTestRunner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30630030,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["automation","ios","macos","quality-assurance","swift","test-runner","uitesting","xcode","xcuitest"],"created_at":"2024-11-16T01:52:24.242Z","updated_at":"2026-03-17T20:03:08.424Z","avatar_url":"https://github.com/eonist.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![mit](https://img.shields.io/badge/License-MIT-brightgreen.svg)\n![platform](https://img.shields.io/badge/Platform-iOS/macOS-blue.svg)\n![Lang](https://img.shields.io/badge/Language-Swift%205-orange.svg)\n[![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://github.com/apple/swift)\n[![Tests](https://github.com/eonist/TestRunner/actions/workflows/Tests.yml/badge.svg)](https://github.com/eonist/TestRunner/actions/workflows/Tests.yml)\n[![codebeat badge](https://codebeat.co/badges/5ad762ee-862a-4267-a69e-9fd8ed9ffce6)](https://codebeat.co/projects/github-com-eonist-testrunner-master)\n\n# TestRunner 🏃\n\n\u003e Simplifies running UI-tests\n\n## Description:\n`TestRunner` is a Swift package that simplifies running UI tests for iOS and macOS. It allows you to create scenes—series of steps—that can be played and receive notifications upon their completion. You can iterate through scenes in sequences, reuse common scenes like `LoginScene` and `LogoutScene`, and operate asynchronously.\n\n## How does it work\nTestRunner allows you to:\n- Create scenes that you can play and receive notifications upon their completion (asynchronously).\n- Iterate through scenes in sequences.\n- Reuse common scenes such as `LoginScene` and `LogoutScene`.\n- Operate asynchronously.\n\n## Key Concepts:\n- **Step**: A single interaction logic, usually encapsulated in an anonymous closure.\n- **Scene**: A series of steps.\n- **Sequence**: A series of scenes.\n\n## Demo:  \nHere is a QA Test prototype for debugging a playlist feature:  \n  \n\u003cimg width=\"320\" alt=\"img\" src=\"https://github.com/stylekit/img/blob/master/test_af.gif?raw=true\"\u003e\n\n\n## Installation\n\nYou can install TestRunner using Swift Package Manager. Simply add the following line to your `Package.swift` file:\n\n```swift\n.package(url: \"https://github.com/eonist/TestRunner.git\", from: \"1.0.0\")\n```\n\nThen add `TestRunner` as a dependency for your target:\n\n```swift\n.target(\n    name: \"MyTarget\",\n    dependencies: [\n        \"TestRunner\",\n    ]\n),\n```\n\nAlternatively, you can add TestRunner to your project using Xcode. Simply go to `File \u003e Swift Packages \u003e Add Package Dependency` and enter the URL of this repository.\n\n\n## Example:\n```swift\nclass SearchScene: Scene {\n    override func run() {\n        let searchBar = XTElement.findFirst(\"SearchBar\")\n        searchBar.search(\"Eminem\")\n        let searchButton = XTElement.findFirst(\"SearchButton\")\n        searchButton.tap()\n        onComplete() // Notify that the scene has completed\n    }\n}\n\n// Define the sequence of scenes\nlet sequence: [SceneKind.Type] = [LoginScene.self, SearchScene.self, LogoutScene.self]\n\n// Initialize the SceneRunner with the sequence\nlet runner = SceneRunner(sequence: sequence) {\n    Swift.print(\"All scenes completed 🏁\")\n}\n\nrunner.app.launch()    // Launch the application\nrunner.iterate()       // Start running the scenes\n```\n\n**LoginScene example**\n```swift\nimport TestRunner\n\nclass LoginScene: Scene {\n    override func run() async throws {\n        let app = sceneRunner.app\n\n        let usernameField = app.textFields[\"UsernameField\"]\n        XCTAssertTrue(usernameField.exists, \"Username field should exist\")\n        usernameField.tap()\n        usernameField.typeText(\"testuser\")\n\n        let passwordField = app.secureTextFields[\"PasswordField\"]\n        XCTAssertTrue(passwordField.exists, \"Password field should exist\")\n        passwordField.tap()\n        passwordField.typeText(\"password\")\n\n        let loginButton = app.buttons[\"LoginButton\"]\n        XCTAssertTrue(loginButton.exists, \"Login button should exist\")\n        loginButton.tap()\n\n        // Wait for the next screen\n        let homeScreen = app.staticTexts[\"HomeScreen\"]\n        let exists = await homeScreen.waitForExistence(timeout: 5)\n        if !exists {\n            throw NSError(domain: \"LoginSceneError\", code: -1, userInfo: [\n                NSLocalizedDescriptionKey: \"Failed to navigate to home screen\"\n            ])\n        }\n    }\n}\n```\n\n## Dependencies\n\n- [UITestSugar](https://github.com/eonist/UITestSugar): A utility library that simplifies writing UI tests by providing syntactic sugar and helper functions.\n\n  \u003e **Note**: Add `UITestSugar` to your `UITesting` target using Xcode's Swift Package Manager, not the main target.\n\n## Resources:\n- [Using XCTest and XCTestCase for iOS Tests](https://medium.com/tauk-blog/using-xctest-and-xctestcase-for-ios-tests-28828c829b3): A comprehensive guide on utilizing XCTest and XCTestCase for iOS testing.\n- [Using XCTest Extension in a Swift Package](https://dr-rost.medium.com/using-xctest-extension-in-a-swift-package-c954b8ed4d62): An informative post detailing the integration of XCTest extension within a Swift package.\n- [Xcode UI Testing Cheat Sheet](https://www.hackingwithswift.com/articles/148/xcode-ui-testing-cheat-sheet): A handy cheat sheet offering quick reference for Xcode UI testing.\n\n### Todo:\n- Add example project (See TabNav-project, playlist-project, UITesting-project, or make a new one) 👈👈👈\n- Maybe add ideas from AccessRunner project, might have advanced ways of doing things etc 👈\n- Maybe use semaphore to make async -\u003e sync ? 👈\n- Create a test project in SwiftUI\n- Error Handling and Logging in Scene Execution\nThe Scene class's run method currently uses a placeholder implementation that simply throws a fatal error if not overridden. This could be improved by adding error handling capabilities, which would allow for better debugging and error logging.\n- SwiftUI Migration: The AppDelegate in the TestRunnerApp uses UIKit and has a comment suggesting migration to SwiftUI. This could improve maintainability and modernize the codebase.\n- Testing Enhancements: The SceneRunner class could include more robust testing features, such as support for launch options to customize the app's launch configuration. This would allow for more flexible and comprehensive testing scenarios.\n- SwiftLint Integration: The GitHub Actions workflow mentions an issue with SwiftLint integration. Addressing this could help maintain code quality and consistency across the project.\n- UI Testing Enhancements: The UI tests could be expanded to cover more scenarios and utilize the capabilities of UITestSugar more effectively. This would ensure that the UI components work as expected under various conditions.\n- Upgrade to swift 6.0 (Might be a bit tricky with TestRunner)\n- Introduce Async/Await Support\nTo better handle asynchronous operations, you can utilize Swift's native concurrency features.\n```swift\npublic protocol SceneKind {\n    var sceneRunner: SceneRunnerKind { get }\n    func run() async throws\n    init(sceneRunner: SceneRunnerKind)\n}\nopen class Scene: SceneKind {\n    public var sceneRunner: SceneRunnerKind\n\n    public required init(sceneRunner: SceneRunnerKind) {\n        self.sceneRunner = sceneRunner\n    }\n\n    open func run() async throws {\n        throw NSError(domain: \"SceneErrorDomain\", code: -1, userInfo: [\n            NSLocalizedDescriptionKey: \"Must be implemented by subclass\"\n        ])\n    }\n}\npublic func iterate() {\n    Task {\n        while hasNext() {\n            let sceneType = next()\n            let scene = sceneType.init(sceneRunner: self)\n            await run(scene: scene)\n        }\n        complete()\n    }\n}\npublic func run(scene: SceneKind) async {\n    do {\n        try await scene.run()\n    } catch {\n        Logger.error(\"Failed to run scene: \\(error.localizedDescription)\")\n        // Handle error accordingly\n    }\n}\npublic func iterate() {\n    Task {\n        while hasNext() {\n            let sceneType = next()\n            let scene = sceneType.init(sceneRunner: self)\n            await run(scene: scene)\n        }\n        complete()\n    }\n}\npublic func run(scene: SceneKind) async {\n    do {\n        try await scene.run()\n    } catch {\n        Logger.error(\"Failed to run scene: \\(error.localizedDescription)\")\n        // Handle error accordingly\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feonist%2Ftestrunner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feonist%2Ftestrunner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feonist%2Ftestrunner/lists"}