├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.yaml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── close-inactive-issues.yaml │ ├── lock-closed-issues.yaml │ ├── publish-cli.yaml │ ├── publish-release.yaml │ ├── publish-snapshot.yaml │ ├── test-e2e-prod.yaml │ ├── test-e2e.yaml │ ├── test.yaml │ ├── update-samples.yaml │ └── warn_build_xctestrunner.yaml ├── .gitignore ├── .idea ├── .gitignore └── icon.svg ├── .run ├── cli-version.run.xml └── cli.run.xml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── assets ├── add_contact_android.gif ├── edit_contacts_ios.gif ├── project-dependency-graph.svg └── run-on-robin.png ├── build.gradle.kts ├── debug.keystore ├── detekt.yml ├── e2e ├── .gitignore ├── README.md ├── download_apps ├── install_apps ├── manifest.txt ├── run_tests ├── update_samples └── workspaces │ ├── demo_app │ ├── ai_complex.yaml │ ├── ai_simple.yaml │ ├── commands │ │ ├── assertNotVisible.yaml │ │ ├── assertTrue.yaml │ │ ├── assertVisible.yaml │ │ ├── back.yaml │ │ ├── copyTextFrom.yaml │ │ ├── eraseText.yaml │ │ ├── evalScript.yaml │ │ ├── extendedWaitUntil.yaml │ │ ├── hideKeyboard.yaml │ │ ├── inputRandomEmail.yaml │ │ ├── inputRandomNumber.yaml │ │ ├── inputRandomPersonName.yaml │ │ ├── inputRandomText.yaml │ │ ├── inputText.yaml │ │ ├── killApp.yaml │ │ ├── launchApp.yaml │ │ ├── pasteText.yaml │ │ ├── pressKey.yaml │ │ ├── repeat.yaml │ │ ├── retry.yaml │ │ ├── runFlow.yaml │ │ ├── runScript.js │ │ └── runScript.yaml │ ├── commands_optional_tournee.yaml │ ├── commands_tour.yaml │ ├── fail_fast.yaml │ ├── fail_launchApp.yaml │ ├── fail_launchApp_nonDefault.yaml │ ├── fail_not_found.yaml │ ├── fail_visible.yaml │ ├── fail_visible_extended.yaml │ ├── fill_form.yaml │ ├── long_input_text.yaml │ ├── relatives.yaml │ ├── scrollUntilVisible_timeout.yaml │ └── swipe.yaml │ ├── no-app │ ├── README.md │ └── environment-variables.yaml │ ├── nowinandroid │ ├── bookmarks.yaml │ └── fail.yaml │ └── wikipedia │ ├── android-advanced-flow.yaml │ ├── android-flow.yaml │ ├── ios-advanced-flow.yaml │ ├── ios-flow.yaml │ ├── scripts │ └── getSearchQuery.js │ ├── subflows │ ├── launch-clearstate-android.yaml │ ├── launch-clearstate-ios.yaml │ ├── onboarding-android.yaml │ └── onboarding-ios.yaml │ └── wikipedia-android-advanced │ ├── auth │ ├── login.yml │ └── signup.yml │ ├── dashboard │ ├── copy-paste.yml │ ├── feed.yml │ ├── main.yml │ ├── saved.yml │ └── search.yml │ ├── onboarding │ ├── add-language.yml │ ├── main.yml │ └── remove-language.yml │ ├── run-test.yml │ └── scripts │ ├── fetchTestUser.js │ └── generateCredentials.js ├── example ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Main.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── installLocally.sh ├── logo.png ├── maestro ├── maestro-ai ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── java │ └── maestro │ │ └── ai │ │ ├── AI.kt │ │ ├── CloudPredictionAIEngine.kt │ │ ├── DemoApp.kt │ │ ├── IAPredictionEngine.kt │ │ ├── Prediction.kt │ │ ├── anthropic │ │ ├── Client.kt │ │ ├── Common.kt │ │ ├── Request.kt │ │ └── Response.kt │ │ ├── cloud │ │ └── ApiClient.kt │ │ ├── common │ │ └── Image.kt │ │ └── openai │ │ ├── Client.kt │ │ ├── Request.kt │ │ └── Response.kt │ └── resources │ ├── askForDefects_schema.json │ └── extractText_schema.json ├── maestro-android ├── build.gradle.kts └── src │ ├── androidTest │ ├── AndroidManifest.xml │ └── java │ │ ├── androidx │ │ └── test │ │ │ └── uiautomator │ │ │ └── UiDeviceExt.kt │ │ └── dev │ │ └── mobile │ │ └── maestro │ │ ├── AccessibilityNodeInfoExt.kt │ │ ├── MaestroDriverService.kt │ │ ├── Media.kt │ │ ├── ToastAccessibilityListener.kt │ │ ├── ViewHierarchy.kt │ │ └── location │ │ ├── FusedLocationProvider.kt │ │ ├── LocationManagerProvider.kt │ │ ├── MockLocationProvider.kt │ │ └── PlayServices.kt │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── dev │ │ └── mobile │ │ └── maestro │ │ ├── handlers │ │ ├── AbstractSettingHandler.kt │ │ └── LocaleSettingHandler.kt │ │ └── receivers │ │ ├── HasAction.kt │ │ └── LocaleSettingReceiver.kt │ └── res │ └── values │ └── stub.xml ├── maestro-cli ├── build.gradle.kts ├── gradle.properties └── src │ ├── jreleaser │ └── distributions │ │ └── maestro │ │ └── brew │ │ └── formula.rb.tpl │ ├── main │ ├── java │ │ └── maestro │ │ │ └── cli │ │ │ ├── App.kt │ │ │ ├── CliError.kt │ │ │ ├── Dependencies.kt │ │ │ ├── DisableAnsiMixin.kt │ │ │ ├── ShowHelpMixin.kt │ │ │ ├── analytics │ │ │ └── Analytics.kt │ │ │ ├── api │ │ │ ├── ApiClient.kt │ │ │ └── Chatbot.kt │ │ │ ├── auth │ │ │ └── Auth.kt │ │ │ ├── cloud │ │ │ └── CloudInteractor.kt │ │ │ ├── command │ │ │ ├── BugReportCommand.kt │ │ │ ├── ChatCommand.kt │ │ │ ├── CheckSyntaxCommand.kt │ │ │ ├── CloudCommand.kt │ │ │ ├── DownloadSamplesCommand.kt │ │ │ ├── DriverCommand.kt │ │ │ ├── LoginCommand.kt │ │ │ ├── LogoutCommand.kt │ │ │ ├── PrintHierarchyCommand.kt │ │ │ ├── QueryCommand.kt │ │ │ ├── RecordCommand.kt │ │ │ ├── StartDeviceCommand.kt │ │ │ ├── StudioCommand.kt │ │ │ ├── TestCommand.kt │ │ │ └── UploadCommand.kt │ │ │ ├── db │ │ │ └── KeyValueStore.kt │ │ │ ├── device │ │ │ ├── DeviceCreateUtil.kt │ │ │ ├── PickDeviceInteractor.kt │ │ │ └── PickDeviceView.kt │ │ │ ├── driver │ │ │ ├── DriverBuildConfig.kt │ │ │ ├── DriverBuilder.kt │ │ │ ├── RealIOSDeviceDriver.kt │ │ │ ├── Spinner.kt │ │ │ └── XcodeBuildProcessBuilderFactory.kt │ │ │ ├── graphics │ │ │ ├── AWTUtils.kt │ │ │ ├── LocalVideoRenderer.kt │ │ │ ├── RemoteVideoRenderer.kt │ │ │ ├── SkiaFrameRenderer.kt │ │ │ ├── SkiaTextClipper.kt │ │ │ ├── SkiaUtils.kt │ │ │ └── VideoRenderer.kt │ │ │ ├── insights │ │ │ └── TestAnalysisManager.kt │ │ │ ├── model │ │ │ ├── DeviceStartOptions.kt │ │ │ ├── FlowStatus.kt │ │ │ ├── RunningFlow.kt │ │ │ └── TestExecutionSummary.kt │ │ │ ├── report │ │ │ ├── HtmlAITestSuiteReporter.kt │ │ │ ├── HtmlInsightsAnalysisReporter.kt │ │ │ ├── HtmlTestSuiteReporter.kt │ │ │ ├── JUnitTestSuiteReporter.kt │ │ │ ├── ReportFormat.kt │ │ │ ├── ReporterFactory.kt │ │ │ ├── TestDebugReporter.kt │ │ │ └── TestSuiteReporter.kt │ │ │ ├── runner │ │ │ ├── CliWatcher.kt │ │ │ ├── CommandState.kt │ │ │ ├── CommandStatus.kt │ │ │ ├── FileWatcher.kt │ │ │ ├── MaestroCommandRunner.kt │ │ │ ├── TestRunner.kt │ │ │ ├── TestSuiteInteractor.kt │ │ │ └── resultview │ │ │ │ ├── AnsiResultView.kt │ │ │ │ ├── PlainTextResultView.kt │ │ │ │ ├── ResultView.kt │ │ │ │ └── UiState.kt │ │ │ ├── session │ │ │ ├── MaestroSessionManager.kt │ │ │ └── SessionStore.kt │ │ │ ├── update │ │ │ └── Updates.kt │ │ │ ├── util │ │ │ ├── ChangeLogUtils.kt │ │ │ ├── CiUtils.kt │ │ │ ├── DeviceConfig.kt │ │ │ ├── EnvUtils.kt │ │ │ ├── ErrorReporter.kt │ │ │ ├── FileDownloader.kt │ │ │ ├── FileUtils.kt │ │ │ ├── IOSEnvUtils.kt │ │ │ ├── PrintUtils.kt │ │ │ ├── ResourceUtils.kt │ │ │ ├── ScreenReporter.kt │ │ │ ├── ScreenshotUtils.kt │ │ │ ├── SocketUtils.kt │ │ │ ├── TimeUtils.kt │ │ │ ├── Unpacker.kt │ │ │ └── WorkspaceUtils.kt │ │ │ ├── view │ │ │ ├── ErrorViewUtils.kt │ │ │ ├── ProgressBar.kt │ │ │ ├── TestSuiteStatusView.kt │ │ │ └── ViewUtils.kt │ │ │ └── web │ │ │ └── WebInteractor.kt │ └── resources │ │ ├── ai_report.css │ │ ├── deps │ │ └── applesimutils │ │ ├── logback-test.xml │ │ ├── record-background.jpg │ │ └── tailwind.config.js │ └── test │ ├── kotlin │ └── maestro │ │ └── cli │ │ ├── android │ │ ├── AndroidDeviceProvider.kt │ │ └── AndroidIntegrationTest.kt │ │ ├── driver │ │ ├── DriverBuilderTest.kt │ │ └── RealDeviceDriverTest.kt │ │ ├── report │ │ └── JUnitTestSuiteReporterTest.kt │ │ └── util │ │ └── ChangeLogUtilsTest.kt │ └── resources │ ├── location │ └── assert_multiple_locations.yaml │ └── travel │ └── assert_travel_command.yaml ├── maestro-client ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ ├── java │ │ └── maestro │ │ │ ├── Bounds.kt │ │ │ ├── Capability.kt │ │ │ ├── DeviceInfo.kt │ │ │ ├── Driver.kt │ │ │ ├── Errors.kt │ │ │ ├── Filters.kt │ │ │ ├── FindElementResult.kt │ │ │ ├── KeyCode.kt │ │ │ ├── Maestro.kt │ │ │ ├── Media.kt │ │ │ ├── Platform.kt │ │ │ ├── Point.kt │ │ │ ├── ScreenRecording.kt │ │ │ ├── ScrollDirection.kt │ │ │ ├── SwipeDirection.kt │ │ │ ├── TapRepeat.kt │ │ │ ├── TreeNode.kt │ │ │ ├── UiElement.kt │ │ │ ├── ViewHierarchy.kt │ │ │ ├── android │ │ │ ├── AndroidAppFiles.kt │ │ │ ├── AndroidBuildToolsDirectory.kt │ │ │ ├── AndroidLaunchArguments.kt │ │ │ └── chromedevtools │ │ │ │ ├── AndroidWebViewHierarchyClient.kt │ │ │ │ ├── DadbChromeDevToolsClient.kt │ │ │ │ └── DadbSocket.kt │ │ │ ├── debuglog │ │ │ ├── DebugLogStore.kt │ │ │ └── LogConfig.kt │ │ │ ├── device │ │ │ ├── Device.kt │ │ │ ├── DeviceError.kt │ │ │ ├── DeviceService.kt │ │ │ ├── Platform.kt │ │ │ └── util │ │ │ │ ├── AndroidEnvUtils.kt │ │ │ │ ├── AvdDevice.kt │ │ │ │ ├── CommandLineUtils.kt │ │ │ │ ├── EnvUtils.kt │ │ │ │ ├── PrintUtils.kt │ │ │ │ ├── SimctlList.kt │ │ │ │ └── SystemInfo.kt │ │ │ ├── drivers │ │ │ ├── AndroidDriver.kt │ │ │ ├── IOSDriver.kt │ │ │ └── WebDriver.kt │ │ │ ├── js │ │ │ ├── GraalJsEngine.kt │ │ │ ├── GraalJsHttp.kt │ │ │ ├── Js.kt │ │ │ ├── JsConsole.kt │ │ │ ├── JsEngine.kt │ │ │ ├── JsHttp.kt │ │ │ ├── JsScope.kt │ │ │ └── RhinoJsEngine.kt │ │ │ ├── mockserver │ │ │ └── MockInteractor.kt │ │ │ └── utils │ │ │ ├── BlockingStreamObserver.kt │ │ │ ├── FileUtils.kt │ │ │ ├── HttpUtils.kt │ │ │ ├── LocaleUtils.kt │ │ │ ├── ScreenshotUtils.kt │ │ │ ├── StringUtils.kt │ │ │ └── TemporaryDirectory.kt │ └── resources │ │ ├── maestro-app.apk │ │ ├── maestro-server.apk │ │ └── maestro-web.js │ └── test │ ├── java │ └── maestro │ │ ├── PointTest.kt │ │ ├── UiElementTest.kt │ │ ├── android │ │ ├── AndroidAppFilesTest.kt │ │ ├── AndroidLaunchArgumentsTest.kt │ │ └── chromedevtools │ │ │ └── AndroidWebViewHierarchyClientTest.kt │ │ ├── ios │ │ └── MockXCTestInstaller.kt │ │ ├── utils │ │ ├── HttpUtilsTest.kt │ │ ├── LocaleUtilsTest.kt │ │ └── StringUtilsTest.kt │ │ └── xctestdriver │ │ └── XCTestDriverClientTest.kt │ └── resources │ └── logback-test.xml ├── maestro-ios-driver ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ ├── kotlin │ │ ├── device │ │ │ ├── IOSDevice.kt │ │ │ └── SimctlIOSDevice.kt │ │ ├── hierarchy │ │ │ └── AXElement.kt │ │ ├── util │ │ │ ├── CommandLineUtils.kt │ │ │ ├── IOSDevice.kt │ │ │ ├── IOSLaunchArguments.kt │ │ │ ├── LocalIOSDevice.kt │ │ │ ├── LocalIOSDeviceController.kt │ │ │ ├── LocalSimulatorUtils.kt │ │ │ ├── PrintUtils.kt │ │ │ ├── SimctlList.kt │ │ │ └── XCRunnerCLIUtils.kt │ │ └── xcuitest │ │ │ ├── XCTestClient.kt │ │ │ ├── XCTestDriverClient.kt │ │ │ ├── api │ │ │ ├── DeviceInfo.kt │ │ │ ├── EraseTextRequest.kt │ │ │ ├── Error.kt │ │ │ ├── GetRunningAppIdResponse.kt │ │ │ ├── GetRunningAppRequest.kt │ │ │ ├── InputTextRequest.kt │ │ │ ├── IsScreenStaticResponse.kt │ │ │ ├── KeyboardInfoRequest.kt │ │ │ ├── KeyboardInfoResponse.kt │ │ │ ├── LaunchAppRequest.kt │ │ │ ├── NetworkExceptions.kt │ │ │ ├── OkHttpClientInstance.kt │ │ │ ├── PressButtonRequest.kt │ │ │ ├── PressKeyRequest.kt │ │ │ ├── SetPermissionsRequest.kt │ │ │ ├── SwipeRequest.kt │ │ │ ├── TerminateAppRequest.kt │ │ │ ├── TouchRequest.kt │ │ │ └── ViewHierarchyRequest.kt │ │ │ └── installer │ │ │ ├── IOSBuildProductsExtractor.kt │ │ │ ├── LocalXCTestInstaller.kt │ │ │ └── XCTestInstaller.kt │ └── resources │ │ ├── driver-iPhoneSimulator │ │ ├── Debug-iphonesimulator │ │ │ ├── maestro-driver-ios.zip │ │ │ └── maestro-driver-iosUITests-Runner.zip │ │ └── maestro-driver-ios-config.xctestrun │ │ ├── driver-iphoneos │ │ ├── Debug-iphoneos │ │ │ ├── maestro-driver-ios.zip │ │ │ └── maestro-driver-iosUITests-Runner.zip │ │ └── maestro-driver-ios-config.xctestrun │ │ └── screenrecord.sh │ └── test │ └── kotlin │ ├── DeviceCtlResponseTest.kt │ ├── IOSBuildProductsExtractorTest.kt │ └── IOSLaunchArgumentsTest.kt ├── maestro-ios-xctest-runner ├── .gitignore ├── build-maestro-ios-runner-all.sh ├── build-maestro-ios-runner.sh ├── maestro-driver-ios.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ ├── driver-verification-tests.xcscheme │ │ └── maestro-driver-ios.xcscheme ├── maestro-driver-ios │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── maestro-driver-iosUITests │ ├── Categories │ │ ├── XCAXClient_iOS+FBSnapshotReqParams.h │ │ ├── XCAXClient_iOS+FBSnapshotReqParams.m │ │ ├── XCUIApplication+FBQuiescence.h │ │ ├── XCUIApplication+FBQuiescence.m │ │ ├── XCUIApplication+Helper.h │ │ ├── XCUIApplication+Helper.m │ │ ├── XCUIApplicationProcess+FBQuiescence.h │ │ ├── XCUIApplicationProcess+FBQuiescence.m │ │ └── maestro-driver-iosUITests-Bridging-Header.h │ ├── PrivateHeaders │ │ └── XCTest │ │ │ ├── CDStructures.h │ │ │ ├── NSString-XCTAdditions.h │ │ │ ├── NSValue-XCTestAdditions.h │ │ │ ├── UIGestureRecognizer-RecordingAdditions.h │ │ │ ├── UILongPressGestureRecognizer-RecordingAdditions.h │ │ │ ├── UIPanGestureRecognizer-RecordingAdditions.h │ │ │ ├── UIPinchGestureRecognizer-RecordingAdditions.h │ │ │ ├── UISwipeGestureRecognizer-RecordingAdditions.h │ │ │ ├── UITapGestureRecognizer-RecordingAdditions.h │ │ │ ├── XCAXClient_iOS.h │ │ │ ├── XCActivityRecord.h │ │ │ ├── XCApplicationMonitor.h │ │ │ ├── XCApplicationMonitor_iOS.h │ │ │ ├── XCApplicationQuery.h │ │ │ ├── XCDebugLogDelegate-Protocol.h │ │ │ ├── XCEventGenerator.h │ │ │ ├── XCKeyMappingPath.h │ │ │ ├── XCKeyboardInputSolver.h │ │ │ ├── XCKeyboardKeyMap.h │ │ │ ├── XCKeyboardLayout.h │ │ │ ├── XCPointerEvent.h │ │ │ ├── XCPointerEventPath.h │ │ │ ├── XCSourceCodeRecording.h │ │ │ ├── XCSourceCodeTreeNode.h │ │ │ ├── XCSourceCodeTreeNodeEnumerator.h │ │ │ ├── XCSymbolicationRecord.h │ │ │ ├── XCSymbolicatorHolder.h │ │ │ ├── XCSynthesizedEventRecord.h │ │ │ ├── XCTAXClient-Protocol.h │ │ │ ├── XCTAsyncActivity-Protocol.h │ │ │ ├── XCTAsyncActivity.h │ │ │ ├── XCTAutomationTarget-Protocol.h │ │ │ ├── XCTDarwinNotificationExpectation.h │ │ │ ├── XCTElementSetTransformer-Protocol.h │ │ │ ├── XCTKVOExpectation.h │ │ │ ├── XCTMetric.h │ │ │ ├── XCTNSNotificationExpectation.h │ │ │ ├── XCTNSPredicateExpectation.h │ │ │ ├── XCTNSPredicateExpectationObject-Protocol.h │ │ │ ├── XCTRunnerAutomationSession.h │ │ │ ├── XCTRunnerDaemonSession.h │ │ │ ├── XCTRunnerIDESession.h │ │ │ ├── XCTTestRunSession.h │ │ │ ├── XCTTestRunSessionDelegate-Protocol.h │ │ │ ├── XCTUIApplicationMonitor-Protocol.h │ │ │ ├── XCTWaiter.h │ │ │ ├── XCTWaiterDelegate-Protocol.h │ │ │ ├── XCTWaiterDelegatePrivate-Protocol.h │ │ │ ├── XCTWaiterManagement-Protocol.h │ │ │ ├── XCTWaiterManager.h │ │ │ ├── XCTest.h │ │ │ ├── XCTestCase.h │ │ │ ├── XCTestCaseRun.h │ │ │ ├── XCTestCaseSuite.h │ │ │ ├── XCTestConfiguration.h │ │ │ ├── XCTestContext.h │ │ │ ├── XCTestContextScope.h │ │ │ ├── XCTestDriver.h │ │ │ ├── XCTestDriverInterface-Protocol.h │ │ │ ├── XCTestExpectation.h │ │ │ ├── XCTestExpectationDelegate-Protocol.h │ │ │ ├── XCTestExpectationWaiter.h │ │ │ ├── XCTestLog.h │ │ │ ├── XCTestManager_IDEInterface-Protocol.h │ │ │ ├── XCTestManager_ManagerInterface-Protocol.h │ │ │ ├── XCTestManager_TestsInterface-Protocol.h │ │ │ ├── XCTestMisuseObserver.h │ │ │ ├── XCTestObservation-Protocol.h │ │ │ ├── XCTestObservationCenter.h │ │ │ ├── XCTestObserver.h │ │ │ ├── XCTestProbe.h │ │ │ ├── XCTestRun.h │ │ │ ├── XCTestSuite.h │ │ │ ├── XCTestSuiteRun.h │ │ │ ├── XCTestWaiter.h │ │ │ ├── XCUIApplication.h │ │ │ ├── XCUIApplicationImpl.h │ │ │ ├── XCUIApplicationProcess.h │ │ │ ├── XCUICoordinate.h │ │ │ ├── XCUIDevice.h │ │ │ ├── XCUIElement.h │ │ │ ├── XCUIElementAsynchronousHandlerWrapper.h │ │ │ ├── XCUIElementHitPointCoordinate.h │ │ │ ├── XCUIElementQuery.h │ │ │ ├── XCUIHitPointResult.h │ │ │ ├── XCUIRecorderNodeFinder.h │ │ │ ├── XCUIRecorderNodeFinderMatch.h │ │ │ ├── XCUIRecorderTimingMessage.h │ │ │ ├── XCUIRecorderUtilities.h │ │ │ ├── XCUIScreen.h │ │ │ ├── XCUIScreenDataSource-Protocol.h │ │ │ ├── _XCInternalTestRun.h │ │ │ ├── _XCKVOExpectationImplementation.h │ │ │ ├── _XCTDarwinNotificationExpectationImplementation.h │ │ │ ├── _XCTNSNotificationExpectationImplementation.h │ │ │ ├── _XCTNSPredicateExpectationImplementation.h │ │ │ ├── _XCTWaiterImpl.h │ │ │ ├── _XCTestCaseImplementation.h │ │ │ ├── _XCTestCaseInterruptionException.h │ │ │ ├── _XCTestExpectationImplementation.h │ │ │ ├── _XCTestImplementation.h │ │ │ ├── _XCTestObservationCenterImplementation.h │ │ │ └── _XCTestSuiteImplementation.h │ ├── Routes │ │ ├── Extensions │ │ │ ├── Logger.swift │ │ │ ├── StringExtensions.swift │ │ │ └── XCUIElement+Extensions.swift │ │ ├── Handlers │ │ │ ├── DeviceInfoHandler.swift │ │ │ ├── EraseTextHandler.swift │ │ │ ├── InputTextRouteHandler.swift │ │ │ ├── KeyboardRouteHandler.swift │ │ │ ├── LaunchAppHandler.swift │ │ │ ├── PressButtonHandler.swift │ │ │ ├── PressKeyHandler.swift │ │ │ ├── RunningAppRouteHandler.swift │ │ │ ├── ScreenDiffHandler.swift │ │ │ ├── ScreenshotHandler.swift │ │ │ ├── SetPermissionsHandler.swift │ │ │ ├── StatusHandler.swift │ │ │ ├── SwipeRouteHandler.swift │ │ │ ├── SwipeRouteHandlerV2.swift │ │ │ ├── TerminateAppHandler.swift │ │ │ ├── TouchRouteHandler.swift │ │ │ └── ViewHierarchyHandler.swift │ │ ├── Helpers │ │ │ ├── AppError.swift │ │ │ ├── ScreenSizeHelper.swift │ │ │ ├── SystemPermissionHelper.swift │ │ │ ├── TextInputHelper.swift │ │ │ └── TimeoutHelper.swift │ │ ├── Models │ │ │ ├── AXElement.swift │ │ │ ├── DeviceInfoResponse.swift │ │ │ ├── EraseTextRequest.swift │ │ │ ├── GetRunningAppRequest.swift │ │ │ ├── InputTextRequest.swift │ │ │ ├── KeyboardHandlerRequest.swift │ │ │ ├── KeyboardHandlerResponse.swift │ │ │ ├── LaunchAppRequest.swift │ │ │ ├── PressButtonRequest.swift │ │ │ ├── PressKeyRequest.swift │ │ │ ├── SetPermissionsRequest.swift │ │ │ ├── StatusResponse.swift │ │ │ ├── SwipeRequest.swift │ │ │ ├── TerminateAppRequest.swift │ │ │ ├── TouchRequest.swift │ │ │ └── ViewHierarchyRequest.swift │ │ ├── RouteHandlerFactory.swift │ │ ├── XCTest │ │ │ ├── AXClientSwizzler.swift │ │ │ ├── EventRecord.swift │ │ │ ├── EventTarget.swift │ │ │ ├── KeyModifierFlags.swift │ │ │ ├── PointerEventPath.swift │ │ │ ├── RunnerDaemonProxy.swift │ │ │ └── RunningApp.swift │ │ └── XCTestHTTPServer.swift │ ├── Utilities │ │ ├── AXClientProxy.h │ │ ├── AXClientProxy.m │ │ ├── FBConfiguration.h │ │ ├── FBConfiguration.m │ │ ├── FBLogger.h │ │ ├── FBLogger.m │ │ ├── XCAccessibilityElement.h │ │ ├── XCTestDaemonsProxy.h │ │ └── XCTestDaemonsProxy.m │ ├── ViewHierarchyHandleTests.swift │ ├── maestro_driver_iosUITests.swift │ └── maestro_driver_iosUITestsLaunchTests.swift ├── run-maestro-ios-runner.sh └── test-maestro-ios-runner.sh ├── maestro-ios ├── README.md ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ └── java │ └── ios │ ├── IOSDeviceErrors.kt │ ├── LocalIOSDevice.kt │ ├── devicectl │ └── DeviceControlIOSDevice.kt │ └── xctest │ └── XCTestIOSDevice.kt ├── maestro-orchestra-models ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── java │ │ └── maestro │ │ └── orchestra │ │ ├── Commands.kt │ │ ├── Condition.kt │ │ ├── ElementSelector.kt │ │ ├── ElementTrait.kt │ │ ├── MaestroCommand.kt │ │ ├── MaestroConfig.kt │ │ ├── WorkspaceConfig.kt │ │ └── util │ │ ├── Env.kt │ │ └── InputRandomTextHelper.kt │ └── test │ └── kotlin │ ├── LitmusTest.kt │ └── maestro │ └── orchestra │ └── util │ └── EnvTest.kt ├── maestro-orchestra ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── java │ │ └── maestro │ │ └── orchestra │ │ ├── Orchestra.kt │ │ ├── error │ │ ├── InvalidFlowFile.kt │ │ ├── MediaFileNotFound.kt │ │ ├── NoInputException.kt │ │ ├── SyntaxError.kt │ │ ├── UnicodeNotSupportedError.kt │ │ └── ValidationError.kt │ │ ├── filter │ │ ├── FilterWithDescription.kt │ │ ├── LaunchArguments.kt │ │ └── TraitFilters.kt │ │ ├── geo │ │ └── Traveller.kt │ │ ├── workspace │ │ ├── ExecutionOrderPlanner.kt │ │ ├── Filters.kt │ │ ├── WorkspaceExecutionPlanner.kt │ │ └── YamlCommandsPathValidator.kt │ │ └── yaml │ │ ├── MaestroFlowParser.kt │ │ ├── YamlAction.kt │ │ ├── YamlAddMedia.kt │ │ ├── YamlAssertNoDefectsWithAI.kt │ │ ├── YamlAssertTrue.kt │ │ ├── YamlAssertWithAI.kt │ │ ├── YamlClearState.kt │ │ ├── YamlCommandReader.kt │ │ ├── YamlCondition.kt │ │ ├── YamlConfig.kt │ │ ├── YamlElementSelector.kt │ │ ├── YamlElementSelectorUnion.kt │ │ ├── YamlEraseTextUnion.kt │ │ ├── YamlEvalScript.kt │ │ ├── YamlExtendedWaitUntil.kt │ │ ├── YamlExtractTextWithAI.kt │ │ ├── YamlFluentCommand.kt │ │ ├── YamlInputRandomText.kt │ │ ├── YamlInputText.kt │ │ ├── YamlKillApp.kt │ │ ├── YamlLaunchApp.kt │ │ ├── YamlOnFlowComplete.kt │ │ ├── YamlOnFlowStart.kt │ │ ├── YamlOpenLink.kt │ │ ├── YamlPressKey.kt │ │ ├── YamlRepeatCommand.kt │ │ ├── YamlRetry.kt │ │ ├── YamlRunFlow.kt │ │ ├── YamlRunScript.kt │ │ ├── YamlScrollUntilVisible.kt │ │ ├── YamlSetAirplaneMode.kt │ │ ├── YamlSetLocation.kt │ │ ├── YamlStartRecording.kt │ │ ├── YamlStopApp.kt │ │ ├── YamlSwipe.kt │ │ ├── YamlTakeScreenshot.kt │ │ ├── YamlToggleAirplaneMode.kt │ │ ├── YamlTravelCommand.kt │ │ └── YamlWaitForAnimationToEndCommand.kt │ └── test │ ├── java │ └── maestro │ │ └── orchestra │ │ ├── CommandDescriptionTest.kt │ │ ├── LaunchArgumentsTest.kt │ │ ├── MaestroCommandSerializationTest.kt │ │ ├── MaestroCommandTest.kt │ │ ├── android │ │ ├── AndroidMediaStoreTest.kt │ │ └── DadbExt.kt │ │ ├── workspace │ │ ├── ExecutionOrderPlannerTest.kt │ │ ├── WorkspaceExecutionPlannerErrorsTest.kt │ │ └── WorkspaceExecutionPlannerTest.kt │ │ └── yaml │ │ ├── YamlCommandReaderTest.kt │ │ └── junit │ │ ├── YamlCommandsExtension.kt │ │ ├── YamlFile.kt │ │ └── YamlResourceFile.kt │ └── resources │ ├── YamlCommandReaderTest │ ├── 002_launchApp.yaml │ ├── 003_launchApp_withClearState.yaml │ ├── 008_config_unknownKeys.yaml │ ├── 017_launchApp_otherPackage.yaml │ ├── 018_backPress_string.yaml │ ├── 019_scroll_string.yaml │ ├── 020_config_name.yaml │ ├── 022_on_flow_start_complete.yaml │ ├── 023_image.png │ ├── 023_labels.yaml │ ├── 023_runScript_test.js │ ├── 024_string_non_string_commands.yaml │ ├── 025_killApp.yaml │ ├── 027_waitToSettleTimeoutMs.yaml │ ├── 028_command_descriptions.yaml │ └── flow.zip │ ├── media │ ├── android │ │ ├── add_media_gif.yaml │ │ ├── add_media_jpeg.yaml │ │ ├── add_media_jpg.yaml │ │ ├── add_media_mp4.yaml │ │ ├── add_media_png.yaml │ │ └── add_multiple_media.yaml │ └── ios │ │ ├── add_media_gif.yaml │ │ ├── add_media_jpeg.yaml │ │ ├── add_media_jpg.yaml │ │ ├── add_media_mp4.yaml │ │ ├── add_media_png.yaml │ │ └── add_multiple_media.yaml │ └── workspaces │ ├── .gitignore │ ├── 000_individual_file │ └── flow.yaml │ ├── 001_simple │ ├── flowA.yaml │ ├── flowB.yaml │ └── notAFlow.txt │ ├── 002_subflows │ ├── flowA.yaml │ ├── flowB.yaml │ └── subflows │ │ └── subflow.yaml │ ├── 003_include_tags │ ├── flowA.yaml │ ├── flowB.yaml │ └── flowC.yaml │ ├── 004_exclude_tags │ ├── flowA.yaml │ ├── flowB.yaml │ └── flowC.yaml │ ├── 005_custom_include_pattern │ ├── config.yaml │ ├── featureA │ │ └── flowA.yaml │ ├── featureB │ │ └── flowB.yaml │ ├── featureC │ │ └── flowC.yaml │ └── flowD.yaml │ ├── 006_include_subfolders │ ├── config.yaml │ ├── featureA │ │ └── flowA.yaml │ ├── featureB │ │ └── flowB.yaml │ ├── featureC │ │ └── subfolder │ │ │ └── flowC.yaml │ └── flowD.yaml │ ├── 007_empty_config │ ├── config.yml │ ├── flowA.yaml │ └── flowB.yaml │ ├── 008_literal_pattern │ ├── config.yaml │ ├── featureA │ │ └── flowA.yaml │ └── featureB │ │ └── flowB.yaml │ ├── 009_custom_config_fields │ ├── config.yml │ ├── flowA.yaml │ └── flowB.yaml │ ├── 010_global_include_tags │ ├── config.yaml │ ├── flowA.yaml │ ├── flowA_subflow.yaml │ ├── flowB.yaml │ ├── flowC.yaml │ ├── flowD.yaml │ └── flowE.yaml │ ├── 011_global_exclude_tags │ ├── config.yaml │ ├── flowA.yaml │ ├── flowA_subflow.yaml │ ├── flowB.yaml │ ├── flowC.yaml │ ├── flowD.yaml │ └── flowE.yaml │ ├── 012_local_deterministic_order │ ├── config.yaml │ ├── flowA.yaml │ ├── flowB.yaml │ └── flowC.yaml │ ├── 013_execution_order │ ├── config.yaml │ ├── flowA.yaml │ ├── flowB.yaml │ ├── flowCWithCustomName.yaml │ └── flowD.yaml │ ├── 014_config_not_null │ ├── config.yaml │ ├── config │ │ └── another_config.yaml │ ├── flowA.yaml │ └── flowB.yaml │ ├── e000_flow_path_does_not_exist │ └── error.txt │ ├── e001_directory_does_not_contain_flow_files │ ├── error.txt │ └── workspace │ │ └── dummy │ ├── e002_top_level_directory_does_not_contain_flow_files │ ├── error.txt │ └── workspace │ │ └── subdir │ │ └── Flow.yaml │ ├── e003_flow_inclusion_pattern_does_not_match_any_flow_files │ ├── error.txt │ └── workspace │ │ ├── FlowC.yaml │ │ └── config.yaml │ ├── e004_tags_config_does_not_match_any_flow_files │ ├── error.txt │ ├── excludeTags.txt │ ├── includeTags.txt │ └── workspace │ │ ├── ConfigExclude.yaml │ │ ├── ParameterExclude.yaml │ │ └── config.yaml │ ├── e005_single_flow_does_not_exist │ ├── error.txt │ └── singleFlow.txt │ ├── e006_single_flow_invalid_string_command │ ├── error.txt │ ├── singleFlow.txt │ └── workspace │ │ └── Flow.yaml │ ├── e007_single_flow_malformatted_command │ ├── error.txt │ ├── singleFlow.txt │ └── workspace │ │ └── Flow.yaml │ ├── e008_subflow_invalid_string_command │ ├── error.txt │ └── workspace │ │ ├── Flow.yaml │ │ └── subflow │ │ └── SubFlow.yaml │ ├── e009_nested_subflow_invalid_string_command │ ├── error.txt │ └── workspace │ │ ├── Flow.yaml │ │ └── subflow │ │ ├── SubFlowA.yaml │ │ └── SubFlowB.yaml │ ├── e010_missing_config_section │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e011_missing_dashes │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e012_invalid_subflow_path │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e013_invalid_media_file │ ├── error.txt │ └── workspace │ │ ├── Flow.yaml │ │ └── assets │ │ └── android.png │ ├── e014_invalid_media_file_outside │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e015_array_command │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e016_config_invalid_command_in_onFlowStart │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e017_config_invalid_tags │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e018_config_missing_appId │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e019_invalid_swipe_direction │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e020_missing_command_options │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e021_multiple_command_names │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e022_top_level_option │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e023_empty │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ ├── e023_empty_commands │ ├── error.txt │ └── workspace │ │ └── Flow.yaml │ └── e023_launchApp_empty_string │ ├── error.txt │ └── workspace │ └── Flow.yaml ├── maestro-proto ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ └── proto │ └── maestro_android.proto ├── maestro-studio ├── server │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── maestro │ │ └── studio │ │ ├── AuthService.kt │ │ ├── DeviceService.kt │ │ ├── HttpException.kt │ │ ├── InsightService.kt │ │ ├── KtorUtils.kt │ │ ├── MaestroStudio.kt │ │ ├── MockService.kt │ │ └── Models.kt └── web │ ├── .gitignore │ ├── .npmrc │ ├── .nvmrc │ ├── .storybook │ ├── main.js │ └── preview.js │ ├── build.gradle │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.tsx │ ├── api │ │ ├── api.ts │ │ └── mocks.ts │ ├── components │ │ ├── commands │ │ │ ├── CommandCreator.tsx │ │ │ ├── CommandInput.tsx │ │ │ ├── CommandList.tsx │ │ │ ├── CommandRow.tsx │ │ │ ├── ReplHeader.tsx │ │ │ ├── ReplView.tsx │ │ │ └── SaveFlowModal.tsx │ │ ├── common │ │ │ ├── AuthModal.tsx │ │ │ ├── Banner.tsx │ │ │ ├── ChatGptApiKeyModal.tsx │ │ │ ├── ConfirmationDialog.tsx │ │ │ ├── Header.tsx │ │ │ ├── Modal.tsx │ │ │ ├── PageSwitcher.tsx │ │ │ └── theme.tsx │ │ ├── design-system │ │ │ ├── button.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── icon.tsx │ │ │ ├── input.tsx │ │ │ ├── keyboard-key.tsx │ │ │ ├── link.tsx │ │ │ ├── spinner.tsx │ │ │ ├── tabs.tsx │ │ │ └── utils │ │ │ │ ├── functions.tsx │ │ │ │ └── images.tsx │ │ ├── device-and-device-elements │ │ │ ├── ActionModal.tsx │ │ │ ├── AnnotatedScreenshot.tsx │ │ │ ├── BrowserActionBar.tsx │ │ │ ├── DeviceWrapperAspectRatio.tsx │ │ │ ├── ElementsPanel.tsx │ │ │ ├── InteractableDevice.tsx │ │ │ └── SelectedElementViewer.tsx │ │ └── interact │ │ │ └── InteractPageLayout.tsx │ ├── context │ │ ├── AuthContext.tsx │ │ ├── DeviceContext.tsx │ │ └── ReplContext.tsx │ ├── helpers │ │ ├── commandExample.ts │ │ ├── models.ts │ │ └── sampleElements.ts │ ├── index.tsx │ ├── pages │ │ └── InteractPage.tsx │ ├── react-app-env.d.ts │ ├── setupTests.ts │ ├── storybook │ │ ├── ActionModal.stories.tsx │ │ ├── App.stories.tsx │ │ ├── BrowserActionBar.stories.tsx │ │ ├── ConfirmationDialog.stories.tsx │ │ ├── Header.stories.tsx │ │ ├── InteractPage.stories.tsx │ │ ├── PageSwitcher.stories.tsx │ │ ├── ReplView.stories.tsx │ │ └── SaveFlowModal.stories.tsx │ └── style │ │ ├── fonts │ │ ├── JetBrainsMono-Italic.ttf │ │ └── JetBrainsMono.ttf │ │ └── index.css │ ├── storybook-assets │ ├── mockServiceWorker.js │ └── sample-screenshot.png │ ├── tailwind.config.js │ └── tsconfig.json ├── maestro-test ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── maestro │ │ └── test │ │ └── drivers │ │ ├── FakeDriver.kt │ │ ├── FakeLayoutElement.kt │ │ └── FakeTimer.kt │ └── test │ ├── kotlin │ └── maestro │ │ └── test │ │ ├── GraalJsEngineTest.kt │ │ ├── IntegrationTest.kt │ │ ├── JsEngineTest.kt │ │ └── RhinoJsEngineTest.kt │ └── resources │ ├── 001_assert_visible_by_id.yaml │ ├── 002_assert_visible_by_text.yaml │ ├── 003_assert_visible_by_size.yaml │ ├── 004_assert_no_visible_element_with_id.yaml │ ├── 005_assert_no_visible_element_with_text.yaml │ ├── 006_assert_no_visible_element_with_size.yaml │ ├── 007_assert_visible_by_size_with_tolerance.yaml │ ├── 008_tap_on_element.yaml │ ├── 009_skip_optional_elements.yaml │ ├── 010_scroll.yaml │ ├── 011_back_press.yaml │ ├── 012_input_text.yaml │ ├── 013_launch_app.yaml │ ├── 014_tap_on_point.yaml │ ├── 015_element_relative_position.yaml │ ├── 016_multiline_text.yaml │ ├── 017_swipe.yaml │ ├── 018_contains_child.yaml │ ├── 019_dont_wait_for_visibility.yaml │ ├── 020_parse_config.yaml │ ├── 021_launch_app_with_clear_state.yaml │ ├── 022_launch_app_that_is_not_installed.yaml │ ├── 025_element_relative_position_shortcut.yaml │ ├── 026_assert_not_visible.yaml │ ├── 027_open_link.yaml │ ├── 028_env.yaml │ ├── 029_long_press_on_element.yaml │ ├── 030_long_press_on_point.yaml │ ├── 031_traits.yaml │ ├── 032_element_index.yaml │ ├── 033_int_text.yaml │ ├── 034_press_key.yaml │ ├── 035_refresh_position_ignore_duplicates.yaml │ ├── 036_erase_text.yaml │ ├── 037_unicode_input.yaml │ ├── 038_partial_id.yaml │ ├── 039_hide_keyboard.yaml │ ├── 040_escape_regex.yaml │ ├── 041_take_screenshot.yaml │ ├── 042_extended_wait.yaml │ ├── 043_stop_app.yaml │ ├── 044_clear_state.yaml │ ├── 045_clear_keychain.yaml │ ├── 046_run_flow.yaml │ ├── 047_run_flow_nested.yaml │ ├── 048_tapOn_clickable.yaml │ ├── 049_run_flow_conditionally.yaml │ ├── 051_set_location.yaml │ ├── 052_text_random.yaml │ ├── 053_repeat_times.yaml │ ├── 054_enabled.yaml │ ├── 055_compare_regex.yaml │ ├── 056_ignore_error.yaml │ ├── 057_runFlow_env.yaml │ ├── 057_subflow.yaml │ ├── 057_subflow_override.yaml │ ├── 058_inline_env.yaml │ ├── 058_subflow.yaml │ ├── 059_directional_swipe_command.yaml │ ├── 060_pass_env_to_env.yaml │ ├── 060_subflow.yaml │ ├── 061_launchApp_withoutStopping.yaml │ ├── 062_copy_paste_text.yaml │ ├── 063_js_injection.yaml │ ├── 064_js_files.yaml │ ├── 064_script.js │ ├── 064_script_alt.js │ ├── 064_script_with_args.js │ ├── 064_subflow.yaml │ ├── 065_subflow.yaml │ ├── 065_when_true.yaml │ ├── 066_copyText_jsVar.yaml │ ├── 067_assertTrue_fail.yaml │ ├── 067_assertTrue_pass.yaml │ ├── 068_erase_all_text.yaml │ ├── 069_wait_for_animation_to_end.yaml │ ├── 070_evalScript.yaml │ ├── 071_tapOnRelativePoint.yaml │ ├── 072_searchDepthFirst.yaml │ ├── 073_handle_linebreaks.yaml │ ├── 074_directional_swipe_element.yaml │ ├── 075_repeat_while.yaml │ ├── 076_optional_assertion.yaml │ ├── 077_env_special_characters.yaml │ ├── 078_swipe_relative.yaml │ ├── 079_scroll_until_visible.yaml │ ├── 080_hierarchy_pruning_assert_visible.yaml │ ├── 081_hierarchy_pruning_assert_not_visible.yaml │ ├── 082_repeat_while_true.yaml │ ├── 083_assert_properties.yaml │ ├── 084_open_browser.yaml │ ├── 085_open_link_auto_verify.yaml │ ├── 086_launchApp_sets_all_permissions_to_allow.yaml │ ├── 087_launchApp_with_all_permissions_to_deny.yaml │ ├── 088_launchApp_with_all_permissions_to_deny_and_notification_to_allow.yaml │ ├── 089_launchApp_with_sms_permission_group_to_allow.yaml │ ├── 090_travel.yaml │ ├── 091_assert_visible_by_index.yaml │ ├── 092_log_messages.yaml │ ├── 092_script.js │ ├── 093_js_default_value.yaml │ ├── 094_runFlow_inline.yaml │ ├── 095_launch_arguments.yaml │ ├── 096_platform_condition.yaml │ ├── 097_contains_descendants.yaml │ ├── 098_runScript.js │ ├── 098_runscript_conditionals.yaml │ ├── 099_screen_recording.yaml │ ├── 100_tapOn_multiple_times.yaml │ ├── 101_doubleTapOn.yaml │ ├── 102_graaljs.yaml │ ├── 102_graaljs_subflow.yaml │ ├── 103_on_flow_start_complete_hooks.yaml │ ├── 103_setup.js │ ├── 103_teardown.js │ ├── 104_on_flow_start_complete_hooks_flow_failed.yaml │ ├── 105_on_flow_start_complete_when_js_output_set.yaml │ ├── 105_setup.js │ ├── 105_teardown.js │ ├── 106_on_flow_start_complete_when_js_output_set_subflows.yaml │ ├── 106_setup.js │ ├── 106_subflow.yaml │ ├── 106_teardown.js │ ├── 107_define_variables_command_before_hooks.yaml │ ├── 108_failed_start_hook.yaml │ ├── 109_failed_complete_hook.yaml │ ├── 110_add_media_device.yaml │ ├── 111_add_multiple_media.yaml │ ├── 112_scroll_until_visible_center.yaml │ ├── 113_tap_on_element_settle_timeout.yaml │ ├── 114_child_of_selector.yaml │ ├── 115_airplane_mode.yaml │ ├── 116_kill_app.yaml │ ├── 117_scroll_until_visible_speed.js │ ├── 117_scroll_until_visible_speed.yaml │ ├── 118_scroll_until_visible_negative.yaml │ ├── 119_retry_commands.yaml │ ├── 120_tap_on_element_retryTapIfNoChange.yaml │ └── media │ └── abc.png ├── maestro-utils ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ ├── Collections.kt │ │ ├── DepthTracker.kt │ │ ├── HttpClient.kt │ │ ├── Insight.kt │ │ ├── Insights.kt │ │ ├── MaestroTimer.kt │ │ ├── Metrics.kt │ │ ├── SocketUtils.kt │ │ ├── Strings.kt │ │ └── network │ │ └── Errors.kt │ └── test │ └── kotlin │ ├── CollectionsTest.kt │ ├── DepthTrackerTest.kt │ ├── InsightTest.kt │ ├── MaestroTimerTest.kt │ ├── SocketUtilsTest.kt │ ├── StringsTest.kt │ └── network │ └── ErrorsTest.kt ├── maestro-web ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ └── kotlin │ └── maestro │ └── web │ ├── record │ ├── JcodecVideoEncoder.kt │ ├── VideoEncoder.kt │ └── WebScreenRecorder.kt │ └── selenium │ ├── ChromeSeleniumFactory.kt │ └── SeleniumFactory.kt ├── recipes ├── googleplay │ └── install_twitter.yaml ├── nowinandroid │ └── pick_interests.yaml ├── square │ └── pos │ │ └── android │ │ └── signup.yaml ├── twitter │ └── android │ │ ├── create_account.yaml │ │ └── follow.yaml └── web │ └── xmas.yaml ├── scripts └── install.sh ├── settings.gradle.kts └── tmp.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copied from https://youtrack.jetbrains.com/issue/FL-15599/No-way-of-disabling-Java-Kotlin-wildcard-imports 2 | 3 | [*.java] 4 | ij_java_class_count_to_use_import_on_demand = 1024 5 | ij_java_names_count_to_use_import_on_demand = 1024 6 | 7 | [*.kt] 8 | ij_kotlin_name_count_to_use_star_import = 1024 9 | ij_kotlin_name_count_to_use_star_import_for_members = 1024 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed changes 2 | 3 | copilot:summary 4 | 5 | ## Testing 6 | 7 | 8 | 9 | ## Issues fixed 10 | -------------------------------------------------------------------------------- /.github/workflows/warn_build_xctestrunner.yaml: -------------------------------------------------------------------------------- 1 | name: Warn Build XCTest runner 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'maestro-ios-xctest-runner/**' 7 | 8 | jobs: 9 | warn: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: mshick/add-pr-comment@v2 13 | with: 14 | message: "Make sure to run ./maestro-ios-xctest-runner/build-maestro-ios-runner.sh with every swift change" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Ignore Gradle project-specific cache directory 4 | .gradle 5 | 6 | # Ignore Gradle build output directory 7 | build 8 | 9 | # Ignore Gradle local properties 10 | local.properties 11 | 12 | bin 13 | 14 | # media assets 15 | maestro-orchestra/src/test/resources/media/assets/* 16 | 17 | # Local files 18 | local/ -------------------------------------------------------------------------------- /.run/cli-version.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /.run/cli.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | -------------------------------------------------------------------------------- /assets/add_contact_android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/assets/add_contact_android.gif -------------------------------------------------------------------------------- /assets/edit_contacts_ios.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/assets/edit_contacts_ios.gif -------------------------------------------------------------------------------- /assets/run-on-robin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/assets/run-on-robin.png -------------------------------------------------------------------------------- /debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/debug.keystore -------------------------------------------------------------------------------- /e2e/.gitignore: -------------------------------------------------------------------------------- 1 | apps/ 2 | 3 | samples/ 4 | samples.zip 5 | -------------------------------------------------------------------------------- /e2e/manifest.txt: -------------------------------------------------------------------------------- 1 | https://storage.googleapis.com/mobile.dev/cli_e2e/wikipedia.apk 2 | https://storage.googleapis.com/mobile.dev/cli_e2e/wikipedia.zip 3 | https://storage.googleapis.com/mobile.dev/cli_e2e/nowinandroid.apk 4 | https://storage.googleapis.com/mobile.dev/cli_e2e/demo_app.apk 5 | https://storage.googleapis.com/mobile.dev/cli_e2e/demo_app.zip 6 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/ai_complex.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | - ai 5 | --- 6 | - launchApp: 7 | clearState: true 8 | - tapOn: Defects Test 9 | - assertNoDefectsWithAI: 10 | optional: true 11 | - assertWithAI: A picture of a cute bunny is visible 12 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/ai_simple.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | - ai 5 | --- 6 | - launchApp: 7 | clearState: true 8 | - assertWithAI: 9 | optional: true 10 | assertion: A login screen is visible 11 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/assertNotVisible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp # For idempotence of sections 5 | 6 | - assertNotVisible: 'kwyjibo' 7 | 8 | - assertNotVisible: 9 | text: 'kwyjibo' 10 | 11 | - assertNotVisible: 12 | text: 'Form Test' 13 | enabled: false -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/assertTrue.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp # For idempotence of sections 5 | 6 | - assertTrue: ${"test" == "test"} 7 | 8 | - assertTrue: 9 | condition: ${12 < 20} 10 | 11 | - assertTrue: 12 | condition: ${THING == "five"} # Using the env at the top of the file -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/assertVisible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp # For idempotence of sections 5 | 6 | - assertVisible: 'Form Test' 7 | 8 | - assertVisible: 9 | text: 'Form Test' 10 | 11 | - assertVisible: 12 | id: 'fabAddIcon' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/back.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Form Test' 6 | - assertVisible: 'Login' 7 | - back 8 | - assertVisible: 'Form Test' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/copyTextFrom.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 6 | id: 'fabAddIcon' 7 | retryTapIfNoChange: false 8 | - copyTextFrom: 9 | text: '\d+' 10 | - assertTrue: ${maestro.copiedText == '1'} -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/eraseText.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Form Test' 6 | - tapOn: 'Email' 7 | - inputText: 'foo' 8 | - assertVisible: 'foo' 9 | - eraseText 10 | # Fix me this part is flaky on CI only not local, needs to be addressed why 11 | - assertNotVisible: 12 | text: 'foo' 13 | optional: true 14 | 15 | - inputText: 'testing' 16 | - assertVisible: 'testing' 17 | - eraseText: 3 18 | - assertNotVisible: 'testing' 19 | - assertVisible: 20 | text: 'test' 21 | optional: true # FIXME: This still takes an extra character sometimes, even after #2123 22 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/evalScript.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp # For idempotence of sections 5 | 6 | - evalScript: ${output.test = 'foo'} 7 | - assertTrue: ${output.test == 'foo'} -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/extendedWaitUntil.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - extendedWaitUntil: 6 | timeout: 10000 7 | visible: 8 | text: 'Swipe Test' 9 | 10 | - extendedWaitUntil: 11 | timeout: 100 12 | notVisible: 13 | text: 'Non Existent Text' 14 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/hideKeyboard.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Form Test' 6 | - tapOn: 'Email' 7 | - assertVisible: 8 | id: com.google.android.inputmethod.latin:id/key_pos_shift # The shift key on the Android keyboard 9 | - hideKeyboard 10 | - assertNotVisible: 11 | id: com.google.android.inputmethod.latin:id/key_pos_shift -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/inputRandomEmail.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Input Test' 6 | - tapOn: 7 | id: 'textInput' 8 | - inputRandomEmail 9 | - assertVisible: '.+@.+' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/inputRandomNumber.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Input Test' 6 | - tapOn: 7 | id: 'textInput' 8 | - inputRandomNumber 9 | - assertVisible: 10 | text: '\d{8}' # The default length is 8 11 | id: 'textInput' 12 | 13 | - eraseText 14 | - inputRandomNumber: 15 | length: 4 16 | - assertVisible: 17 | text: '\d{4}' 18 | id: 'textInput' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/inputRandomPersonName.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Input Test' 6 | - tapOn: 7 | id: 'textInput' 8 | - inputRandomPersonName 9 | - assertVisible: 10 | text: '[A-Z][a-z]+ [A-Z][a-z]+' 11 | id: 'textInput' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/inputRandomText.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Input Test' 6 | - tapOn: 7 | id: 'textInput' 8 | - inputRandomText 9 | - assertVisible: 10 | text: '[a-z0-9]{8}' # The default length is 8 11 | id: 'textInput' 12 | 13 | - eraseText 14 | - inputRandomText: 15 | length: 4 16 | - assertVisible: 17 | text: '[a-z0-9]{4}' 18 | id: 'textInput' 19 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/inputText.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - tapOn: 'Input Test' 6 | - tapOn: 7 | id: 'textInput' 8 | - inputText: 'foo' 9 | - assertVisible: 'foo' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/killApp.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp 5 | - assertVisible: 'Form Test' 6 | - killApp 7 | - assertNotVisible: 'Form Test' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/launchApp.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp: 4 | appId: com.example.example 5 | clearState: true 6 | clearKeychain: true 7 | stopApp: true 8 | permissions: 9 | all: allow 10 | - assertVisible: 'Form Test' 11 | 12 | - stopApp 13 | - launchApp 14 | - assertVisible: 'Form Test' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/pasteText.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - evalScript: ${maestro.copiedText = 'foo'} 6 | - tapOn: 'Input Test' 7 | - tapOn: 8 | id: 'textInput' 9 | - inputText: 'foo' 10 | - copyTextFrom: 11 | id: 'textInput' 12 | - pasteText 13 | - assertVisible: 'foofoo' -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/pressKey.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp 4 | 5 | - assertVisible: 'Form Test' 6 | - pressKey: 'Home' 7 | - assertNotVisible: 'Form Test' 8 | 9 | - launchApp 10 | 11 | - assertVisible: 'Form Test' 12 | - tapOn: 'Form Test' 13 | - assertNotVisible: 'Form Test' 14 | - pressKey: 'Back' 15 | - assertVisible: 'Form Test' 16 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/repeat.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp # For idempotence of sections 4 | 5 | - assertVisible: '0' 6 | - repeat: 7 | times: 3 8 | commands: 9 | - tapOn: 10 | id: 'fabAddIcon' 11 | - assertVisible: '3' 12 | - assertNotVisible: '0' 13 | 14 | - evalScript: ${output.counter = 0} 15 | - repeat: 16 | while: 17 | true: ${output.counter < 3} 18 | commands: 19 | - evalScript: ${output.counter = output.counter + 1} -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/retry.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | - launchApp 4 | 5 | - retry: 6 | maxRetries: 3 7 | commands: 8 | - tapOn: 9 | id: 'fabAddIcon' 10 | retryTapIfNoChange: false 11 | - waitForAnimationToEnd 12 | - assertVisible: '2' 13 | - assertVisible: 'Flutter Demo Home Page' 14 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/runFlow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | # runFlow with file: isn't included since it's in the root flow 5 | 6 | - launchApp # For idempotence of sections 7 | 8 | - runFlow: 9 | commands: 10 | - evalScript: ${output.test = 'bar'} 11 | - assertTrue: ${output.test == 'bar'} 12 | - tapOn: 13 | id: 'fabAddIcon' 14 | - assertVisible: '1' 15 | 16 | - runFlow: 17 | env: 18 | THIS_THING: "six" 19 | commands: 20 | - assertTrue: ${THIS_THING == "six"} -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/runScript.js: -------------------------------------------------------------------------------- 1 | if (THIS_THING == "six"){ 2 | output.something = "foo" 3 | } else { 4 | output.something = "bar" 5 | } -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/commands/runScript.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | --- 3 | 4 | - launchApp # For idempotence of sections 5 | 6 | - evalScript: ${output.something = 'baz'} 7 | 8 | - runScript: runScript.js 9 | - assertTrue: ${output.something == 'bar'} 10 | 11 | - evalScript: ${output.something = 'baz'} 12 | 13 | - runScript: 14 | file: runScript.js 15 | - assertTrue: ${output.something == 'bar'} 16 | 17 | - evalScript: ${output.something = 'baz'} 18 | 19 | - runScript: 20 | env: 21 | THIS_THING: "six" 22 | file: runScript.js 23 | - assertTrue: ${output.something == 'foo'} 24 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_fast.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - assertTrue: 8 | condition: ${ false } 9 | label: Fail the flow 10 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_launchApp.yaml: -------------------------------------------------------------------------------- 1 | appId: com.nonexistent 2 | tags: 3 | - failing 4 | --- 5 | - launchApp 6 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_launchApp_nonDefault.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | --- 5 | - launchApp: 6 | appId: com.nonexistent 7 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_not_found.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - tapOn: 8 | id: non-existent-id 9 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_visible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - assertVisible: 8 | id: non-existent-id 9 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fail_visible_extended.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - failing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - extendedWaitUntil: 8 | visible: 9 | id: non-existent-id 10 | timeout: 100 11 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/fill_form.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - passing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - tapOn: Form Test 8 | - tapOn: Email 9 | - inputText: correct@mobile.dev 10 | - tapOn: Password 11 | - inputText: maestro 12 | - tapOn: 13 | text: Login 14 | index: 1 15 | - assertVisible: 16 | text: Credentials are correct 17 | optional: true # Fix me this part is flaky on CI only not local, needs to be addressed why 18 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/long_input_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - passing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - tapOn: Form Test 8 | - tapOn: Email 9 | - inputText: veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongemail@mobile.dev 10 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/relatives.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - passing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - tapOn: Nesting Test 8 | - assertVisible: 9 | id: level-0 10 | - assertVisible: 11 | id: level-0 12 | containsChild: 13 | id: level-1 14 | containsChild: 15 | id: level-2 16 | rightOf: 17 | text: left side 18 | leftOf: 19 | text: right side 20 | below: top side 21 | above: bottom side 22 | - assertNotVisible: 23 | id: level-0 24 | containsChild: 25 | id: level-1 26 | below: bottom side 27 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/scrollUntilVisible_timeout.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - passing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - evalScript: ${maestro.startTime = new Date()} 8 | - scrollUntilVisible: 9 | element: non-existent 10 | timeout: 1000 11 | optional: true 12 | - evalScript: ${maestro.endTime = new Date()} 13 | - assertTrue: ${maestro.endTime - maestro.startTime < 12000} # Far less than the 20000 default, but enough to allow for processing time 14 | -------------------------------------------------------------------------------- /e2e/workspaces/demo_app/swipe.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.example 2 | tags: 3 | - passing 4 | --- 5 | - launchApp: 6 | clearState: true 7 | - tapOn: Swipe Test 8 | - swipe: 9 | start: 50%, 15% 10 | end: 15%, 50% 11 | duration: 1000 12 | - swipe: 13 | start: 15%, 50% 14 | end: 85%, 85% 15 | duration: 1000 16 | - swipe: 17 | start: 85%, 85% 18 | end: 85%, 50% 19 | duration: 1000 20 | - tapOn: 21 | point: 85%, 50% 22 | - assertVisible: All green 23 | -------------------------------------------------------------------------------- /e2e/workspaces/no-app/README.md: -------------------------------------------------------------------------------- 1 | # No App 2 | 3 | For tests that don't require an app to be launched. 4 | 5 | Tests of JavaScript or environment variables can be run here. -------------------------------------------------------------------------------- /e2e/workspaces/no-app/environment-variables.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.notused 2 | tags: 3 | - passing 4 | --- 5 | # Relies on MAESTRO_EXAMPLE being set in the environment 6 | - assertTrue: ${MAESTRO_EXAMPLE == 'test-value'} -------------------------------------------------------------------------------- /e2e/workspaces/nowinandroid/bookmarks.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.samples.apps.nowinandroid.demo.debug 2 | name: Bookmarks 3 | tags: 4 | - android 5 | - passing 6 | --- 7 | - launchApp: 8 | clearState: true 9 | - tapOn: Headlines 10 | - tapOn: Done 11 | - tapOn: Bookmark 12 | - tapOn: Saved 13 | - tapOn: Unbookmark 14 | - assertVisible: No saved updates 15 | -------------------------------------------------------------------------------- /e2e/workspaces/nowinandroid/fail.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.samples.apps.nowinandroid.demo.debug 2 | name: Fail 3 | tags: 4 | - android 5 | - failing 6 | --- 7 | - launchApp: 8 | clearState: true 9 | - tapOn: 10 | id: non-existent-id-to-fail-this-test 11 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/android-advanced-flow.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | tags: 3 | - android 4 | - passing 5 | - advanced 6 | --- 7 | - runFlow: subflows/onboarding-android.yaml 8 | - tapOn: 9 | id: "org.wikipedia:id/search_container" 10 | - tapOn: 11 | text: "Non existent view" 12 | optional: true 13 | - runScript: scripts/getSearchQuery.js 14 | - inputText: ${output.result} 15 | - assertVisible: ${output.result} 16 | - runFlow: subflows/launch-clearstate-android.yaml 17 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/android-flow.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | tags: 3 | - android 4 | - passing 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/ios-flow.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikimedia.wikipedia 2 | tags: 3 | - ios 4 | - passing 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/scripts/getSearchQuery.js: -------------------------------------------------------------------------------- 1 | output.result = 'qwerty'; 2 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/subflows/launch-clearstate-android.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - launchApp: 4 | clearState: true 5 | - assertVisible: "Continue" 6 | - assertVisible: "Skip" -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/subflows/launch-clearstate-ios.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikimedia.wikipedia 2 | --- 3 | - launchApp: 4 | clearState: true 5 | - assertVisible: "Next" 6 | - assertVisible: "Skip" -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/subflows/onboarding-android.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - launchApp: 4 | clearState: true 5 | - tapOn: 6 | text: "Non existent view" 7 | optional: true 8 | - tapOn: 9 | id: "org.wikipedia:id/fragment_onboarding_forward_button" 10 | - tapOn: 11 | id: "org.wikipedia:id/fragment_onboarding_forward_button" 12 | - tapOn: 13 | id: "org.wikipedia:id/fragment_onboarding_forward_button" 14 | - tapOn: 15 | id: "org.wikipedia:id/fragment_onboarding_done_button" 16 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/subflows/onboarding-ios.yaml: -------------------------------------------------------------------------------- 1 | appId: org.wikimedia.wikipedia 2 | --- 3 | - launchApp: 4 | clearState: true 5 | - repeat: 6 | times: 3 7 | commands: 8 | - swipe: 9 | direction: LEFT 10 | duration: 400 11 | - waitForAnimationToEnd 12 | - tapOn: Get started 13 | - tapOn: 14 | text: "Non existent view" 15 | optional: true 16 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/login.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "More" 4 | - tapOn: "LOG IN.*" 5 | - tapOn: 6 | id: ".*create_account_login_button" 7 | - runScript: "../scripts/fetchTestUser.js" 8 | - tapOn: "Username" 9 | - inputText: "${output.test_user.username}" 10 | - tapOn: "Password" 11 | - inputText: "No provided" 12 | - tapOn: "LOG IN" 13 | - back 14 | - back 15 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/signup.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "More" 4 | - tapOn: "LOG IN.*" 5 | - runScript: "../scripts/generateCredentials.js" 6 | - tapOn: "Username" 7 | - inputText: "${output.credentials.username}" 8 | - tapOn: "Password" 9 | - inputText: "${output.credentials.password}" 10 | - tapOn: "Repeat password" 11 | - inputText: "${output.credentials.password}" 12 | - tapOn: "Email.*" 13 | - inputText: "${output.credentials.email}" 14 | 15 | # We won't actually create the account 16 | - back 17 | - back 18 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/copy-paste.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "Explore" 4 | - scrollUntilVisible: 5 | element: "Top read" 6 | - copyTextFrom: 7 | id: ".*view_list_card_item_title" 8 | index: 0 9 | - tapOn: "Explore" 10 | - tapOn: "Search Wikipedia" 11 | - inputText: "${maestro.copiedText}" 12 | - back 13 | - back 14 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/feed.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "Explore" 4 | - scrollUntilVisible: 5 | element: "Today on Wikipedia.*" 6 | - tapOn: "Today on Wikipedia.*" 7 | - back 8 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/main.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - runFlow: "search.yml" 4 | - runFlow: "saved.yml" 5 | - runFlow: "feed.yml" 6 | - runFlow: "copy-paste.yml" 7 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/saved.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "Saved" 4 | - tapOn: "Default list for your saved articles" 5 | - assertVisible: "Sun" 6 | - assertVisible: "Star at the center of the Solar System" 7 | - back 8 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/search.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "Search Wikipedia" 4 | - inputText: "Sun" 5 | - assertVisible: "Star at the center of the Solar System" 6 | - tapOn: 7 | id: ".*page_list_item_title" 8 | - tapOn: 9 | id: ".*page_save" 10 | - back 11 | - back 12 | - back 13 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/add-language.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "ADD OR EDIT.*" 4 | - tapOn: "ADD LANGUAGE" 5 | - tapOn: 6 | id: ".*menu_search_language" 7 | - inputText: "Greek" 8 | - assertVisible: "Ελληνικά" 9 | - tapOn: "Ελληνικά" 10 | - tapOn: "Navigate up" 11 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/main.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - runFlow: "add-language.yml" 4 | - runFlow: "remove-language.yml" 5 | - tapOn: "Continue" 6 | - assertVisible: "New ways to explore" 7 | - tapOn: "Continue" 8 | - assertVisible: "Reading lists with sync" 9 | - tapOn: "Continue" 10 | - assertVisible: "Send anonymous data" 11 | - tapOn: "Get started" 12 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/remove-language.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | --- 3 | - tapOn: "ADD OR EDIT.*" 4 | - tapOn: "More options" 5 | - tapOn: "Remove language" 6 | - tapOn: 7 | id: ".*wiki_language_checkbox" 8 | index: 1 9 | - tapOn: 10 | id: ".*menu_delete_selected" 11 | - tapOn: "OK" 12 | - assertNotVisible: "Ελληνικά" 13 | - tapOn: "Navigate up" 14 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/run-test.yml: -------------------------------------------------------------------------------- 1 | appId: org.wikipedia 2 | tags: 3 | - android 4 | - passing 5 | --- 6 | - launchApp: 7 | clearState: true 8 | - runFlow: "onboarding/main.yml" 9 | - runFlow: "dashboard/main.yml" 10 | - runFlow: "auth/signup.yml" 11 | - runFlow: "auth/login.yml" 12 | -------------------------------------------------------------------------------- /e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/fetchTestUser.js: -------------------------------------------------------------------------------- 1 | // Fetches test user from API 2 | function getTestUserFromApi() { 3 | const url = `https://jsonplaceholder.typicode.com/users/1`; 4 | var response = http.get(url); 5 | var data = json(response.body); 6 | 7 | return { 8 | username: data.username, 9 | email: data.email, 10 | }; 11 | } 12 | 13 | output.test_user = getTestUserFromApi(); 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=bed1da33cca0f557ab13691c77f38bb67388119e4794d113e051039b80af9bb1 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip 5 | networkTimeout=10000 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /installLocally.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./gradlew :maestro-cli:installDist 4 | 5 | rm -rf ~/.maestro/bin 6 | rm -rf ~/.maestro/lib 7 | 8 | cp -r ./maestro-cli/build/install/maestro/bin ~/.maestro/bin 9 | cp -r ./maestro-cli/build/install/maestro/lib ~/.maestro/lib -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/logo.png -------------------------------------------------------------------------------- /maestro: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ -t 0 ]; then 6 | input="" 7 | else 8 | input=$(cat -) 9 | fi 10 | 11 | ./gradlew :maestro-cli:installDist -q && ./maestro-cli/build/install/maestro/bin/maestro "$@" 12 | -------------------------------------------------------------------------------- /maestro-ai/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro AI 2 | POM_ARTIFACT_ID=maestro-ai 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /maestro-ai/src/main/java/maestro/ai/IAPredictionEngine.kt: -------------------------------------------------------------------------------- 1 | package maestro.ai 2 | 3 | import maestro.ai.cloud.Defect 4 | 5 | interface AIPredictionEngine { 6 | suspend fun findDefects(screen: ByteArray): List 7 | suspend fun performAssertion(screen: ByteArray, assertion: String): Defect? 8 | suspend fun extractText(screen: ByteArray, query: String): String 9 | } 10 | -------------------------------------------------------------------------------- /maestro-ai/src/main/java/maestro/ai/anthropic/Common.kt: -------------------------------------------------------------------------------- 1 | package maestro.ai.anthropic 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Message( 8 | val role: String, 9 | val content: List, 10 | ) 11 | 12 | @Serializable 13 | data class Content( 14 | val type: String, 15 | val text: String? = null, 16 | val source: ContentSource? = null, 17 | ) 18 | 19 | @Serializable 20 | data class ContentSource( 21 | val type: String, 22 | @SerialName("media_type") val mediaType: String, 23 | val data: String, 24 | ) 25 | -------------------------------------------------------------------------------- /maestro-ai/src/main/java/maestro/ai/anthropic/Request.kt: -------------------------------------------------------------------------------- 1 | package maestro.ai.anthropic 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Request( 8 | val model: String, 9 | @SerialName("max_tokens") val maxTokens: Int, 10 | val messages: List, 11 | ) 12 | -------------------------------------------------------------------------------- /maestro-ai/src/main/java/maestro/ai/anthropic/Response.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.serialization.Serializable 2 | import maestro.ai.anthropic.Content 3 | 4 | @Serializable 5 | data class Response( 6 | val content: List, 7 | ) 8 | -------------------------------------------------------------------------------- /maestro-ai/src/main/java/maestro/ai/common/Image.kt: -------------------------------------------------------------------------------- 1 | package maestro.ai.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Base64Image( 7 | val url: String, 8 | val detail: String, 9 | ) 10 | -------------------------------------------------------------------------------- /maestro-ai/src/main/resources/extractText_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extractText", 3 | "description": "Extracts text from an image based on a given query", 4 | "strict": true, 5 | "schema": { 6 | "type": "object", 7 | "required": [ 8 | "text" 9 | ], 10 | "additionalProperties": false, 11 | "properties": { 12 | "text": { 13 | "type": "string" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /maestro-android/src/androidTest/java/androidx/test/uiautomator/UiDeviceExt.kt: -------------------------------------------------------------------------------- 1 | package androidx.test.uiautomator 2 | 3 | object UiDeviceExt { 4 | 5 | /** 6 | * Fix for a UiDevice.click() method that discards taps that happen outside of the screen bounds. 7 | * The issue with the original method is that it was computing screen bounds incorrectly. 8 | */ 9 | fun UiDevice.clickExt(x: Int, y: Int) { 10 | interactionController.clickNoSync( 11 | x, y 12 | ) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /maestro-android/src/androidTest/java/dev/mobile/maestro/location/MockLocationProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mobile.maestro.location 2 | 3 | import android.location.Location 4 | 5 | interface MockLocationProvider { 6 | 7 | fun setLocation(location: Location) 8 | 9 | fun enable() 10 | 11 | fun disable() 12 | 13 | fun getProviderName(): String 14 | } -------------------------------------------------------------------------------- /maestro-android/src/androidTest/java/dev/mobile/maestro/location/PlayServices.kt: -------------------------------------------------------------------------------- 1 | package dev.mobile.maestro.location 2 | 3 | import android.content.Context 4 | import com.google.android.gms.common.ConnectionResult 5 | import com.google.android.gms.common.GoogleApiAvailability 6 | 7 | class PlayServices { 8 | 9 | fun isAvailable(context: Context): Boolean { 10 | val apiAvailability = GoogleApiAvailability.getInstance() 11 | val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) 12 | return resultCode == ConnectionResult.SUCCESS 13 | } 14 | } -------------------------------------------------------------------------------- /maestro-android/src/main/java/dev/mobile/maestro/receivers/HasAction.kt: -------------------------------------------------------------------------------- 1 | package dev.mobile.maestro.receivers 2 | 3 | interface HasAction { 4 | fun action(): String 5 | } -------------------------------------------------------------------------------- /maestro-android/src/main/res/values/stub.xml: -------------------------------------------------------------------------------- 1 | 2 | Maestro Driver 3 | 4 | -------------------------------------------------------------------------------- /maestro-cli/gradle.properties: -------------------------------------------------------------------------------- 1 | CLI_VERSION=1.40.3 -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/CliError.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli 2 | 3 | class CliError(override val message: String) : RuntimeException(message) 4 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/Dependencies.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli 2 | 3 | import maestro.cli.util.Unpacker.binaryDependency 4 | import maestro.cli.util.Unpacker.unpack 5 | 6 | object Dependencies { 7 | private val appleSimUtils = binaryDependency("applesimutils") 8 | 9 | fun install() { 10 | unpack( 11 | jarPath = "deps/applesimutils", 12 | target = appleSimUtils, 13 | ) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/ShowHelpMixin.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli 2 | 3 | import picocli.CommandLine 4 | 5 | class ShowHelpMixin { 6 | @CommandLine.Option( 7 | names = ["-h", "--help"], 8 | usageHelp = true, 9 | description = ["Display help message"], 10 | ) 11 | var help = false 12 | } 13 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/driver/DriverBuildConfig.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.driver 2 | 3 | import maestro.cli.api.CliVersion 4 | 5 | data class DriverBuildConfig( 6 | val teamId: String, 7 | val derivedDataPath: String, 8 | val sourceCodePath: String = "driver/ios", 9 | val sourceCodeRoot: String = System.getProperty("user.home"), 10 | val destination: String = "generic/platform=iphoneos", 11 | val architectures: String = "arm64", 12 | val configuration: String = "Debug", 13 | val cliVersion: CliVersion? 14 | ) -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/driver/XcodeBuildProcessBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.driver 2 | 3 | import java.io.File 4 | 5 | class XcodeBuildProcessBuilderFactory { 6 | 7 | fun createProcess(commands: List, workingDirectory: File, outputFile: File): Process { 8 | return ProcessBuilder(commands).directory(workingDirectory).redirectOutput(outputFile) 9 | .redirectError(outputFile) 10 | .start() 11 | } 12 | } -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/graphics/VideoRenderer.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.graphics 2 | 3 | import maestro.cli.runner.resultview.AnsiResultView 4 | import java.io.File 5 | 6 | interface VideoRenderer { 7 | fun render( 8 | screenRecording: File, 9 | textFrames: List, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/model/DeviceStartOptions.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.model 2 | 3 | import maestro.device.Platform 4 | 5 | data class DeviceStartOptions( 6 | val platform: Platform, 7 | val osVersion: Int?, 8 | val forceCreate: Boolean 9 | ) -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/model/RunningFlow.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.model 2 | 3 | import maestro.cli.api.UploadStatus 4 | import kotlin.time.Duration 5 | 6 | data class RunningFlows( 7 | val flows: MutableSet = mutableSetOf(), 8 | var duration: Duration? = null 9 | ) 10 | 11 | data class RunningFlow( 12 | val name: String, 13 | var status: FlowStatus? = null, 14 | var startTime: Long? = null, 15 | var duration: Duration? = null, 16 | var reported: Boolean = false 17 | ) 18 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/report/ReportFormat.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.report 2 | 3 | enum class ReportFormat( 4 | val fileExtension: String? 5 | ) { 6 | 7 | JUNIT(".xml"), 8 | HTML(".html"), 9 | NOOP(null), 10 | 11 | } -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/report/ReporterFactory.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.report 2 | 3 | import maestro.cli.model.TestExecutionSummary 4 | import okio.BufferedSink 5 | 6 | object ReporterFactory { 7 | 8 | fun buildReporter(format: ReportFormat, testSuiteName: String?): TestSuiteReporter { 9 | return when (format) { 10 | ReportFormat.JUNIT -> JUnitTestSuiteReporter.xml(testSuiteName) 11 | ReportFormat.NOOP -> TestSuiteReporter.NOOP 12 | ReportFormat.HTML -> HtmlTestSuiteReporter() 13 | } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/runner/resultview/ResultView.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.runner.resultview 2 | 3 | interface ResultView { 4 | fun setState(state: UiState) 5 | } 6 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/runner/resultview/UiState.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.runner.resultview 2 | 3 | import maestro.device.Device 4 | import maestro.cli.runner.CommandState 5 | 6 | sealed class UiState { 7 | 8 | data class Error(val message: String) : UiState() 9 | 10 | data class Running( 11 | val flowName: String, 12 | val device: Device? = null, 13 | val onFlowStartCommands: List = emptyList(), 14 | val onFlowCompleteCommands: List = emptyList(), 15 | val commands: List, 16 | ) : UiState() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/util/ResourceUtils.kt: -------------------------------------------------------------------------------- 1 | import kotlin.reflect.KClass 2 | 3 | fun readResourceAsText(cls: KClass<*>, path: String): String { 4 | val resourceStream = cls::class.java.getResourceAsStream(path) 5 | ?: throw IllegalStateException("Could not find $path in resources") 6 | 7 | return resourceStream.bufferedReader().use { it.readText() } 8 | } 9 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/util/ScreenReporter.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.util 2 | 3 | import maestro.cli.api.ApiClient 4 | import maestro.utils.DepthTracker 5 | 6 | object ScreenReporter { 7 | 8 | fun reportMaxDepth() { 9 | val maxDepth = DepthTracker.getMaxDepth() 10 | 11 | if (maxDepth == 0) return 12 | 13 | ApiClient(EnvUtils.BASE_API_URL).sendScreenReport(maxDepth = maxDepth) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/util/SocketUtils.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.util 2 | 3 | import java.net.ServerSocket 4 | 5 | fun getFreePort(): Int { 6 | (9999..11000).forEach { port -> 7 | try { 8 | ServerSocket(port).use { return it.localPort } 9 | } catch (ignore: Exception) {} 10 | } 11 | ServerSocket(0).use { return it.localPort } 12 | } -------------------------------------------------------------------------------- /maestro-cli/src/main/java/maestro/cli/util/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.util 2 | 3 | import kotlin.math.roundToLong 4 | import kotlin.time.Duration 5 | import kotlin.time.Duration.Companion.seconds 6 | 7 | object TimeUtils { 8 | 9 | fun durationInSeconds(startTimeInMillis: Long?, endTimeInMillis: Long?): Duration { 10 | if (startTimeInMillis == null || endTimeInMillis == null) return Duration.ZERO 11 | return ((endTimeInMillis - startTimeInMillis) / 1000f).roundToLong().seconds 12 | } 13 | 14 | fun durationInSeconds(durationInMillis: Long): Duration { 15 | return ((durationInMillis) / 1000f).roundToLong().seconds 16 | } 17 | } -------------------------------------------------------------------------------- /maestro-cli/src/main/resources/deps/applesimutils: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-cli/src/main/resources/deps/applesimutils -------------------------------------------------------------------------------- /maestro-cli/src/main/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%-5level] %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /maestro-cli/src/main/resources/record-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-cli/src/main/resources/record-background.jpg -------------------------------------------------------------------------------- /maestro-cli/src/main/resources/tailwind.config.js: -------------------------------------------------------------------------------- 1 | tailwind.config = { 2 | darkMode: "media", 3 | theme: { 4 | extend: { 5 | colors: { 6 | "gray-dark": "#110c22", // text-gray-dark 7 | "gray-medium": "#4f4b5c", // text-gray-medium 8 | "gray-1": "#f8f8f8", // surface-gray-1 9 | "gray-0": "#110C22", // surface-gray-0 10 | "orange-2": "#ff9254", // surface-orange-2 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /maestro-cli/src/test/kotlin/maestro/cli/android/AndroidDeviceProvider.kt: -------------------------------------------------------------------------------- 1 | package maestro.cli.android 2 | 3 | import dadb.Dadb 4 | import dadb.adbserver.AdbServer 5 | 6 | class AndroidDeviceProvider { 7 | 8 | fun local(): Dadb { 9 | val dadb = AdbServer.createDadb(connectTimeout = 60_000, socketTimeout = 60_000) 10 | 11 | return dadb 12 | } 13 | } -------------------------------------------------------------------------------- /maestro-cli/src/test/resources/location/assert_multiple_locations.yaml: -------------------------------------------------------------------------------- 1 | appId: "com.google.android.apps.maps" 2 | --- 3 | - launchApp 4 | - tapOn: 5 | text: Skip 6 | optional: true 7 | - setLocation: 8 | longitude: 2.295188 9 | latitude: 48.8578065 10 | - assertVisible: .*Eiffel.* 11 | - setLocation: 12 | latitude: 43.7230 13 | longitude: 10.3966 14 | - assertVisible: .*(Piazza|Pisa).* -------------------------------------------------------------------------------- /maestro-cli/src/test/resources/travel/assert_travel_command.yaml: -------------------------------------------------------------------------------- 1 | appId: "com.google.android.apps.maps" 2 | --- 3 | - launchApp 4 | - tapOn: 5 | text: Skip 6 | optional: true 7 | - setLocation: 8 | latitude: 48.8578065 9 | longitude: 2.295188 10 | - assertVisible: .*Eiffel.* 11 | - travel: 12 | points: 13 | - 48.8578065, 2.295188 14 | - 46.2276, 5.9900 15 | - 43.7230, 10.3966 16 | - 41.8902, 12.4922 17 | speed: 1000 18 | - assertVisible: .*Colosseo.* 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /maestro-client/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro Client 2 | POM_ARTIFACT_ID=maestro-client 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/Capability.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | enum class Capability { 4 | FAST_HIERARCHY, 5 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/FindElementResult.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | data class FindElementResult(val element: UiElement, val hierarchy: ViewHierarchy) -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/Media.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | import okio.Source 4 | 5 | class NamedSource(val name: String, val source: Source, val extension: String, val path: String) 6 | 7 | enum class MediaExt(val extName: String) { 8 | PNG("png"), 9 | JPEG("jpeg"), 10 | JPG("jpg"), 11 | GIF("gif"), 12 | MP4("mp4"), 13 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/Platform.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | enum class Platform { 4 | ANDROID, IOS, WEB 5 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/ScreenRecording.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | interface ScreenRecording : AutoCloseable -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/ScrollDirection.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | enum class ScrollDirection { 4 | UP, 5 | DOWN, 6 | RIGHT, 7 | LEFT 8 | } 9 | 10 | fun ScrollDirection.toSwipeDirection(): SwipeDirection = when (this) { 11 | ScrollDirection.DOWN -> SwipeDirection.UP 12 | ScrollDirection.UP -> SwipeDirection.DOWN 13 | ScrollDirection.LEFT -> SwipeDirection.RIGHT 14 | ScrollDirection.RIGHT -> SwipeDirection.LEFT 15 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/SwipeDirection.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | enum class SwipeDirection { 4 | UP, 5 | DOWN, 6 | RIGHT, 7 | LEFT 8 | } 9 | 10 | inline fun > directionValueOfOrNull(input: String): SwipeDirection? { 11 | return enumValues().find { it.name == input || it.name.lowercase() == input } 12 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/TapRepeat.kt: -------------------------------------------------------------------------------- 1 | package maestro 2 | 3 | data class TapRepeat( 4 | val repeat: Int, 5 | val delay: Long // millis 6 | ) -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/device/DeviceError.kt: -------------------------------------------------------------------------------- 1 | package maestro.device 2 | 3 | /** 4 | * Exception class specifically for device-related errors in the client module. 5 | * Functionally equivalent to CliError in maestro-cli. 6 | */ 7 | class DeviceError(override val message: String) : RuntimeException(message) 8 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/device/Platform.kt: -------------------------------------------------------------------------------- 1 | package maestro.device 2 | 3 | enum class Platform(val description: String) { 4 | ANDROID("Android"), 5 | IOS("iOS"), 6 | WEB("Web"); 7 | 8 | companion object { 9 | fun fromString(p: String?): Platform? { 10 | return values().firstOrNull { it.description.lowercase() == p?.lowercase() } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/device/util/AvdDevice.kt: -------------------------------------------------------------------------------- 1 | package maestro.device.util 2 | 3 | data class AvdDevice(val numericId: String, val nameId: String, val name: String) 4 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/device/util/PrintUtils.kt: -------------------------------------------------------------------------------- 1 | package maestro.device.util 2 | 3 | import org.slf4j.LoggerFactory 4 | 5 | /** 6 | * Simplified version of PrintUtils for DeviceService 7 | */ 8 | object PrintUtils { 9 | private val logger = LoggerFactory.getLogger(PrintUtils::class.java) 10 | 11 | fun message(message: String) { 12 | logger.info(message) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/device/util/SystemInfo.kt: -------------------------------------------------------------------------------- 1 | package maestro.device.util 2 | 3 | object SystemInfo { 4 | fun getJavaVersion(): Int { 5 | // Adapted from https://stackoverflow.com/a/2591122/7009800 6 | val version = System.getProperty("java.version") 7 | return if (version.startsWith("1.")) { 8 | version.substring(2, 3).toInt() 9 | } else { 10 | val dot = version.indexOf(".") 11 | if (dot != -1) version.substring(0, dot).toInt() else 0 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/js/JsConsole.kt: -------------------------------------------------------------------------------- 1 | package maestro.js 2 | 3 | import org.mozilla.javascript.ScriptableObject 4 | 5 | class JsConsole( 6 | private val onLogMessage: (String) -> Unit, 7 | ) : ScriptableObject() { 8 | 9 | fun log(message: String) { 10 | onLogMessage(message) 11 | } 12 | 13 | override fun getClassName(): String { 14 | return "JsConsole" 15 | } 16 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/js/JsEngine.kt: -------------------------------------------------------------------------------- 1 | package maestro.js 2 | 3 | interface JsEngine : AutoCloseable { 4 | fun onLogMessage(callback: (String) -> Unit) 5 | fun enterScope() 6 | fun leaveScope() 7 | fun putEnv(key: String, value: String) 8 | fun setCopiedText(text: String?) 9 | fun evaluateScript( 10 | script: String, 11 | env: Map = emptyMap(), 12 | sourceName: String = "inline-script", 13 | runInSubScope: Boolean = false, 14 | ): Any? 15 | } 16 | -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | object StringUtils { 4 | 5 | fun String.toRegexSafe(option: RegexOption) = toRegexSafe(setOf(option)) 6 | 7 | fun String.toRegexSafe(options: Set = emptySet()): Regex { 8 | return try { 9 | toRegex(options) 10 | } catch (e: Exception) { 11 | Regex.escape(this).toRegex(options) 12 | } 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /maestro-client/src/main/java/maestro/utils/TemporaryDirectory.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Path 5 | 6 | object TemporaryDirectory { 7 | 8 | inline fun use(block: (tmpDir: Path) -> T): T { 9 | val tmpDir = Files.createTempDirectory(null) 10 | return try { 11 | block(tmpDir) 12 | } finally { 13 | tmpDir.toFile().deleteRecursively() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /maestro-client/src/main/resources/maestro-app.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-client/src/main/resources/maestro-app.apk -------------------------------------------------------------------------------- /maestro-client/src/main/resources/maestro-server.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-client/src/main/resources/maestro-server.apk -------------------------------------------------------------------------------- /maestro-client/src/test/java/maestro/utils/StringUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import maestro.utils.StringUtils.toRegexSafe 5 | import org.junit.jupiter.api.Test 6 | 7 | internal class StringUtilsTest { 8 | 9 | @Test 10 | internal fun `toRegexSafe should escape string if regex is invalid`() { 11 | // Given 12 | val input = "*Oświadczam, że zapoznałem się z treścią Regulaminu serwisu i akceptuję jego postanowienia." 13 | 14 | // When 15 | val regex = input.toRegexSafe() 16 | 17 | // Then 18 | assertThat(regex.matches(input)).isTrue() 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /maestro-client/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%-5level] %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /maestro-ios-driver/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro XCUITest Driver 2 | POM_ARTIFACT_ID=maestro-ios-driver 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/util/PrintUtils.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | object PrintUtils { 4 | 5 | fun log(message: String) { 6 | println(message) 7 | } 8 | } -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/XCTestClient.kt: -------------------------------------------------------------------------------- 1 | package xcuitest 2 | 3 | import okhttp3.HttpUrl 4 | 5 | class XCTestClient( 6 | val host: String, 7 | val port: Int, 8 | ) { 9 | 10 | fun xctestAPIBuilder(pathSegment: String): HttpUrl.Builder { 11 | return HttpUrl.Builder() 12 | .scheme("http") 13 | .host(host) 14 | .addPathSegment(pathSegment) 15 | .port(port) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/EraseTextRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class EraseTextRequest( 4 | val charactersToErase: Int, 5 | val appIds: Set, 6 | ) 7 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/Error.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | 5 | data class Error( 6 | @JsonProperty("errorMessage") val errorMessage: String, 7 | @JsonProperty("code") val errorCode: String, 8 | ) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/GetRunningAppIdResponse.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class GetRunningAppIdResponse(val runningAppBundleId: String) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/GetRunningAppRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class GetRunningAppRequest(val appIds: Set) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/InputTextRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class InputTextRequest( 4 | val text: String, 5 | val appIds: Set 6 | ) 7 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/IsScreenStaticResponse.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class IsScreenStaticResponse(val isScreenStatic: Boolean) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/KeyboardInfoRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class KeyboardInfoRequest(val appIds: Set) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/KeyboardInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class KeyboardInfoResponse(val isKeyboardVisible: Boolean) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/LaunchAppRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class LaunchAppRequest(val bundleId: String) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/OkHttpClientInstance.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | import okhttp3.ConnectionPool 4 | import okhttp3.OkHttpClient 5 | import java.util.concurrent.TimeUnit 6 | 7 | object OkHttpClientInstance { 8 | 9 | fun get(): OkHttpClient { 10 | return OkHttpClient.Builder() 11 | .connectionPool(ConnectionPool(225, 10, TimeUnit.MINUTES)) 12 | .connectTimeout(1, TimeUnit.SECONDS) 13 | .readTimeout(200, TimeUnit.SECONDS) 14 | .build() 15 | } 16 | } -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/PressButtonRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class PressButtonRequest(val button: String) 4 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/PressKeyRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class PressKeyRequest(val key: String) 4 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/SetPermissionsRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class SetPermissionsRequest(val permissions: Map) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/SwipeRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class SwipeRequest( 4 | val appId: String? = null, 5 | val startX: Double, 6 | val startY: Double, 7 | val endX: Double, 8 | val endY: Double, 9 | val duration: Double, 10 | val appIds: Set? = null 11 | ) 12 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/TerminateAppRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class TerminateAppRequest(val appId: String) -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/TouchRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class TouchRequest( 4 | val x: Float, 5 | val y: Float, 6 | val duration: Double? 7 | ) 8 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/api/ViewHierarchyRequest.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.api 2 | 3 | data class ViewHierarchyRequest(val appIds: Set, val excludeKeyboardElements: Boolean) 4 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/kotlin/xcuitest/installer/XCTestInstaller.kt: -------------------------------------------------------------------------------- 1 | package xcuitest.installer 2 | 3 | import xcuitest.XCTestClient 4 | 5 | interface XCTestInstaller: AutoCloseable { 6 | fun start(): XCTestClient 7 | 8 | /** 9 | * Attempts to uninstall the XCTest Runner. 10 | * 11 | * @return true if the XCTest Runner was uninstalled, false otherwise. 12 | */ 13 | fun uninstall(): Boolean 14 | 15 | fun isChannelAlive(): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/resources/driver-iPhoneSimulator/Debug-iphonesimulator/maestro-driver-ios.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-ios-driver/src/main/resources/driver-iPhoneSimulator/Debug-iphonesimulator/maestro-driver-ios.zip -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/resources/driver-iPhoneSimulator/Debug-iphonesimulator/maestro-driver-iosUITests-Runner.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-ios-driver/src/main/resources/driver-iPhoneSimulator/Debug-iphonesimulator/maestro-driver-iosUITests-Runner.zip -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/resources/driver-iphoneos/Debug-iphoneos/maestro-driver-ios.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-ios-driver/src/main/resources/driver-iphoneos/Debug-iphoneos/maestro-driver-ios.zip -------------------------------------------------------------------------------- /maestro-ios-driver/src/main/resources/driver-iphoneos/Debug-iphoneos/maestro-driver-iosUITests-Runner.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-ios-driver/src/main/resources/driver-iphoneos/Debug-iphoneos/maestro-driver-iosUITests-Runner.zip -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | xcuserdata/ 3 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/build-maestro-ios-runner-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DESTINATION="generic/platform=iOS Simulator" DERIVED_DATA_DIR="driver-iPhoneSimulator" ARCHS="x86_64 arm64" $PWD/maestro-ios-xctest-runner/build-maestro-ios-runner.sh 4 | DESTINATION="generic/platform=iphoneos" DERIVED_DATA_DIR="driver-iphoneos" ARCHS="arm64" $PWD/maestro-ios-xctest-runner/build-maestro-ios-runner.sh 5 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "fcc5a9e047bc89c422e9ca5571e5b664446abc3c4c0b838c23d066c241e1a3f6", 3 | "pins" : [ 4 | { 5 | "identity" : "flyingfox", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swhitty/FlyingFox", 8 | "state" : { 9 | "revision" : "3ad076e081749cef043e25ac01719a503b772113", 10 | "version" : "0.22.0" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-ios/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // maestro-driver-ios 4 | // 5 | // 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view. 15 | } 16 | 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Categories/XCUIApplication+Helper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface XCUIApplication (Helper) 4 | 5 | + (NSArray *> *)activeAppsInfo; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Categories/maestro-driver-iosUITests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "XCUIApplication+FBQuiescence.h" 6 | #import "XCUIApplication+Helper.h" 7 | #import "XCAXClient_iOS+FBSnapshotReqParams.h" 8 | #import "AXClientProxy.h" 9 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/NSString-XCTAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface NSString (XCTAdditions) 10 | - (id)xct_quotedSwiftStringRepresentation; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/NSValue-XCTestAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface NSValue (XCTestAdditions) 10 | - (id)xct_contentDescription; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UIGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UIGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UILongPressGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UILongPressGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UIPanGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UIPanGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UIPinchGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UIPinchGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UISwipeGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UISwipeGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/UITapGestureRecognizer-RecordingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface UITapGestureRecognizer (RecordingAdditions) 10 | - (id)_automationName; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCApplicationMonitor_iOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface XCApplicationMonitor_iOS : XCApplicationMonitor 10 | { 11 | } 12 | 13 | - (void)_terminateApplicationProcess:(id)arg1; 14 | - (id)monitoredApplicationWithProcessIdentifier:(int)arg1; 15 | - (void)_beginMonitoringApplication:(id)arg1; 16 | - (id)init; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCDebugLogDelegate-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSString; 8 | 9 | @protocol XCDebugLogDelegate 10 | - (void)logDebugMessage:(NSString *)arg1; 11 | @end 12 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSourceCodeTreeNodeEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSMutableArray; 8 | 9 | @interface XCSourceCodeTreeNodeEnumerator : NSObject 10 | { 11 | NSMutableArray *_remainingNodes; 12 | } 13 | 14 | - (id)nextObject; 15 | - (id)initWithNode:(id)arg1; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSymbolicatorHolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @interface XCSymbolicatorHolder : NSObject 8 | { 9 | struct _CSTypeRef _symbolicator; 10 | } 11 | 12 | @property struct _CSTypeRef symbolicator; // @synthesize symbolicator=_symbolicator; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTAXClient-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @class NSData; 10 | 11 | @protocol XCTAXClient 12 | - (void)handleAccessibilityNotification:(int)arg1 withPayload:(NSData *)arg2; 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTAsyncActivity-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @class NSError; 10 | 11 | @protocol XCTAsyncActivity 12 | @property(readonly) BOOL timedOut; 13 | @property(readonly) NSError *error; 14 | - (void)finishWithError:(NSError *)arg1; 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTAutomationTarget-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @protocol XCTAutomationTarget 10 | - (void)requestHostAppExecutableNameWithReply:(void (^)(NSString *))arg1; 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTNSPredicateExpectationObject-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @class XCTNSPredicateExpectation; 10 | 11 | @protocol XCTNSPredicateExpectationObject 12 | 13 | @optional 14 | - (BOOL)evaluatePredicateForExpectation:(XCTNSPredicateExpectation *)arg1 debugMessage:(id *)arg2; 15 | @end 16 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTRunnerAutomationSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | #import "XCTRunnerAutomationSession.h" 10 | 11 | @class NSString, NSXPCConnection; 12 | 13 | @interface XCTRunnerAutomationSession : NSObject 14 | { 15 | NSXPCConnection *_connection; 16 | } 17 | @property NSXPCConnection *connection; // @synthesize connection=_connection; 18 | 19 | - (id)initWithEndpoint:(id)arg1; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTWaiterDelegatePrivate-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class XCTWaiter; 8 | 9 | @protocol XCTWaiterDelegatePrivate 10 | - (void)nestedWaiter:(XCTWaiter *)arg1 wasInterruptedByTimedOutWaiter:(XCTWaiter *)arg2; 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTWaiterManagement-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @protocol XCTWaiterManagement 10 | @property(readonly, getter=isInProgress) BOOL inProgress; 11 | - (void)interruptForWaiter:(id )arg1; 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestCaseSuite.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface XCTestCaseSuite : XCTestSuite 10 | { 11 | Class _testCaseClass; 12 | } 13 | 14 | + (id)emptyTestSuiteForTestCaseClass:(Class)arg1; 15 | - (void)tearDown; 16 | - (void)setUp; 17 | - (id)initWithTestCaseClass:(Class)arg1; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestContextScope.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSMutableArray; 8 | 9 | @interface XCTestContextScope : NSObject 10 | { 11 | XCTestContextScope *_parentScope; 12 | NSMutableArray *_handlers; 13 | } 14 | @property(copy) NSMutableArray *handlers; // @synthesize handlers=_handlers; 15 | @property(readonly) XCTestContextScope *parentScope; // @synthesize parentScope=_parentScope; 16 | 17 | - (id)initWithParentScope:(id)arg1; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestExpectationDelegate-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "NSObject.h" 8 | 9 | @class XCTestExpectation; 10 | 11 | @protocol XCTestExpectationDelegate 12 | - (void)didFulfillExpectation:(XCTestExpectation *)arg1; 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestExpectationWaiter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface XCTestExpectationWaiter : XCTWaiter 10 | { 11 | } 12 | 13 | - (long long)wait:(double)arg1 forExpectations:(id)arg2 enforceOrder:(BOOL)arg3; 14 | - (long long)wait:(double)arg1 forExpectations:(id)arg2; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestManager_TestsInterface-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSData, NSString; 8 | 9 | @protocol XCTestManager_TestsInterface 10 | - (void)_XCT_receivedAccessibilityNotification:(int)arg1 withPayload:(NSData *)arg2; 11 | - (void)_XCT_applicationWithBundleID:(NSString *)arg1 didUpdatePID:(int)arg2 andState:(unsigned long long)arg3; 12 | @end 13 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestProbe.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @interface XCTestProbe : NSObject 8 | { 9 | } 10 | 11 | + (BOOL)isTesting; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestWaiter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @interface XCTestWaiter : XCTWaiter 10 | { 11 | } 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIRecorderTimingMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSString; 8 | 9 | @interface XCUIRecorderTimingMessage : NSObject 10 | { 11 | double _start; 12 | NSString *_message; 13 | } 14 | @property(copy) NSString *message; // @synthesize message=_message; 15 | @property double start; // @synthesize start=_start; 16 | 17 | + (id)descriptionForTimingMessages:(id)arg1; 18 | + (id)messageWithString:(id)arg1; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestCaseInterruptionException.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @interface _XCTestCaseInterruptionException : NSException 8 | { 9 | } 10 | 11 | + (void)interruptTest; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestImplementation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class XCTestRun; 8 | 9 | @interface _XCTestImplementation : NSObject 10 | { 11 | XCTestRun *_testRun; 12 | } 13 | @property(retain) XCTestRun *testRun; // @synthesize testRun=_testRun; 14 | @end 15 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestObservationCenterImplementation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSMutableSet; 8 | 9 | @interface _XCTestObservationCenterImplementation : NSObject 10 | { 11 | NSMutableArray *_observers; 12 | BOOL _suspended; 13 | } 14 | 15 | @property BOOL suspended; // @synthesize suspended=_suspended; 16 | @property(retain) NSMutableArray *observers; // @synthesize observers=_observers; 17 | - (id)init; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Extensions/Logger.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import os 4 | 5 | extension Logger { 6 | func measure(message: String, _ block: () throws -> T) rethrows -> T { 7 | let start = Date() 8 | info("\(message) - start") 9 | 10 | let result = try block() 11 | 12 | let duration = Date().timeIntervalSince(start) 13 | NSLog("\(message) - duration \(duration)") 14 | 15 | return result 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Extensions/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | extension String { 5 | func toUInt16() -> UInt16? { 6 | return UInt16(self) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Extensions/XCUIElement+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | extension XCUIElement { 5 | func setText(text: String, application: XCUIApplication) { 6 | UIPasteboard.general.string = text 7 | doubleTap() 8 | application.menuItems["Paste"].tap() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/DeviceInfoResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct DeviceInfoResponse: Codable { 4 | let widthPoints: Int 5 | let heightPoints: Int 6 | let widthPixels: Int 7 | let heightPixels: Int 8 | } 9 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/EraseTextRequest.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | struct EraseTextRequest: Codable { 5 | let charactersToErase: Int 6 | let appIds: [String] 7 | } 8 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/GetRunningAppRequest.swift: -------------------------------------------------------------------------------- 1 | struct RunningAppRequest: Codable { 2 | let appIds: [String] 3 | } 4 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/InputTextRequest.swift: -------------------------------------------------------------------------------- 1 | struct InputTextRequest: Codable { 2 | let text: String 3 | let appIds: [String] 4 | } 5 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/KeyboardHandlerRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct KeyboardHandlerRequest: Codable { 4 | let appIds: [String] 5 | } 6 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/KeyboardHandlerResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct KeyboardHandlerResponse: Codable { 4 | let isKeyboardVisible: Bool 5 | } 6 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/LaunchAppRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct LaunchAppRequest : Codable { 4 | let bundleId: String 5 | } 6 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/PressButtonRequest.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import XCTest 4 | 5 | struct PressButtonRequest: Codable { 6 | enum Button: String, Codable { 7 | case home 8 | case lock 9 | } 10 | 11 | let button: Button 12 | } 13 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/SetPermissionsRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum PermissionValue: String, Codable { 4 | case allow 5 | case deny 6 | case unset 7 | case unknown 8 | 9 | init(from decoder: Decoder) throws { 10 | self = try PermissionValue(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown 11 | } 12 | } 13 | 14 | struct SetPermissionsRequest: Codable { 15 | let permissions: [String : PermissionValue] 16 | } 17 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/StatusResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct StatusResponse: Codable { 4 | let status: String 5 | } 6 | 7 | enum Status: Codable { 8 | case ok 9 | } 10 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/TerminateAppRequest.swift: -------------------------------------------------------------------------------- 1 | struct TerminateAppRequest: Codable { 2 | let appId: String 3 | } 4 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/TouchRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TouchRequest : Codable { 4 | let x: Float 5 | let y: Float 6 | let duration: TimeInterval? 7 | } 8 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/ViewHierarchyRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ViewHierarchyRequest: Codable { 4 | let appIds: [String] 5 | let excludeKeyboardElements: Bool 6 | } 7 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTest/KeyModifierFlags.swift: -------------------------------------------------------------------------------- 1 | 2 | struct KeyModifierFlags: OptionSet { 3 | let rawValue: UInt64 4 | static let capsLock = KeyModifierFlags(rawValue: 1 << 0) 5 | static let shift = KeyModifierFlags(rawValue: 1 << 1) 6 | static let control = KeyModifierFlags(rawValue: 1 << 2) 7 | static let option = KeyModifierFlags(rawValue: 1 << 3) 8 | static let command = KeyModifierFlags(rawValue: 1 << 4) 9 | static let function = KeyModifierFlags(rawValue: 1 << 5) 10 | } 11 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Utilities/AXClientProxy.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "XCAccessibilityElement.h" 3 | 4 | @interface AXClientProxy : NSObject 5 | 6 | + (instancetype)sharedClient; 7 | 8 | - (NSArray> *)activeApplications; 9 | 10 | - (NSDictionary *)defaultParameters; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /maestro-ios-xctest-runner/maestro-driver-iosUITests/Utilities/XCTestDaemonsProxy.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "XCSynthesizedEventRecord.h" 3 | 4 | 5 | @protocol XCTestManager_ManagerInterface; 6 | 7 | @interface XCTestDaemonsProxy : NSObject 8 | 9 | + (id)testRunnerProxy; 10 | 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /maestro-ios/README.md: -------------------------------------------------------------------------------- 1 | # iOS Device Config 2 | 3 | A wrapper around `simctl` and XCTest to communicate with iOS devices. 4 | 5 | ## Prerequisites 6 | 7 | ### Xcode 8 | 9 | Install the latest Xcode (Command Line Tools are not enough, install the full IDE). 10 | 11 | ### IntelliJ setup 12 | 13 | If you are working with this subproject, update your IntelliJ config (Help -> Edit Custom Properties) by including the following lines: 14 | 15 | ``` 16 | # Needed for working with idb.proto definition 17 | idea.max.intellisense.filesize=4000 18 | ``` 19 | 20 | Then restart the IDE. 21 | -------------------------------------------------------------------------------- /maestro-ios/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro iOS 2 | POM_ARTIFACT_ID=maestro-ios 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /maestro-ios/src/main/java/ios/IOSDeviceErrors.kt: -------------------------------------------------------------------------------- 1 | package ios 2 | 3 | sealed class IOSDeviceErrors : Throwable() { 4 | data class AppCrash(val errorMessage: String): IOSDeviceErrors() 5 | } -------------------------------------------------------------------------------- /maestro-orchestra-models/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Orchestra Models 2 | POM_ARTIFACT_ID=maestro-orchestra-models 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /maestro-orchestra-models/src/main/java/maestro/orchestra/ElementTrait.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra 2 | 3 | enum class ElementTrait(val description: String) { 4 | TEXT("Has text"), 5 | SQUARE("Is square"), 6 | LONG_TEXT("Has long text"), 7 | } -------------------------------------------------------------------------------- /maestro-orchestra-models/src/test/kotlin/LitmusTest.kt: -------------------------------------------------------------------------------- 1 | import com.google.common.truth.Truth.assertThat 2 | import org.junit.jupiter.api.Test 3 | 4 | class LitmusTest { 5 | @Test 6 | fun `junit and truth are setup`() { 7 | assertThat(true) 8 | .isTrue() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /maestro-orchestra/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro Orchestra 2 | POM_ARTIFACT_ID=maestro-orchestra 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/InvalidFlowFile.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | import java.nio.file.Path 4 | 5 | class InvalidFlowFile( 6 | override val message: String, 7 | val flowPath: Path 8 | ) : RuntimeException() -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/MediaFileNotFound.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | import java.nio.file.Path 4 | 5 | class MediaFileNotFound( 6 | override val message: String, 7 | val mediaPath: Path 8 | ): ValidationError(message) -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/NoInputException.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | object NoInputException : RuntimeException() -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/SyntaxError.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | class SyntaxError(override val message: String, cause: Throwable? = null) : ValidationError(message, cause) -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/UnicodeNotSupportedError.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | data class UnicodeNotSupportedError( 4 | val text: String, 5 | ) : RuntimeException("Unicode not supported: $text") -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/error/ValidationError.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.error 2 | 3 | open class ValidationError(override val message: String, cause: Throwable? = null) : RuntimeException(message, cause) 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/filter/FilterWithDescription.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.filter 2 | 3 | import maestro.ElementFilter 4 | 5 | data class FilterWithDescription( 6 | val description: String, 7 | val filterFunc: ElementFilter, 8 | ) 9 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAddMedia.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlAddMedia( 6 | val files: List? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | companion object { 11 | 12 | @JvmStatic 13 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 14 | fun parse(files: List) = YamlAddMedia( 15 | files = files, 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAssertNoDefectsWithAI.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlAssertNoDefectsWithAI( 4 | val optional: Boolean = true, 5 | val label: String? = null, 6 | ) 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlClearState( 6 | val appId: String? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | companion object { 11 | @JvmStatic 12 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 13 | fun parse(appId: String) = YamlClearState( 14 | appId = appId, 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCondition.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat 4 | import maestro.Platform 5 | 6 | data class YamlCondition( 7 | @JsonFormat(with = [JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES]) 8 | val platform: Platform? = null, 9 | val visible: YamlElementSelectorUnion? = null, 10 | val notVisible: YamlElementSelectorUnion? = null, 11 | val `true`: String? = null, 12 | val label: String? = null, 13 | val optional: Boolean = false, 14 | ) 15 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEraseTextUnion.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlEraseText( 6 | val charactersToErase: Int? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | 11 | companion object { 12 | 13 | @JvmStatic 14 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 15 | fun parse(charactersToRemove: Int): YamlEraseText { 16 | return YamlEraseText( 17 | charactersToErase = charactersToRemove 18 | ) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlExtendedWaitUntil.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlExtendedWaitUntil( 4 | val visible: YamlElementSelectorUnion? = null, 5 | val notVisible: YamlElementSelectorUnion? = null, 6 | val timeout: String? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) 10 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlKillApp( 6 | val appId: String? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | 11 | companion object { 12 | 13 | @JvmStatic 14 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 15 | fun parse(appId: String) = YamlKillApp( 16 | appId = appId, 17 | ) 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOnFlowComplete.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlOnFlowComplete(val commands: List) { 6 | companion object { 7 | @JvmStatic 8 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 9 | fun parse(commands: List) = YamlOnFlowComplete( 10 | commands = commands 11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOnFlowStart.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlOnFlowStart(val commands: List) { 6 | companion object { 7 | @JvmStatic 8 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 9 | fun parse(commands: List) = YamlOnFlowStart( 10 | commands = commands 11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlPressKey.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlPressKey ( 6 | val key: String, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ){ 10 | companion object { 11 | @JvmStatic 12 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 13 | fun parse(key: String) = YamlPressKey( 14 | key = key, 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRepeatCommand.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlRepeatCommand( 4 | val times: String? = null, 5 | val `while`: YamlCondition? = null, 6 | val commands: List, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) 10 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRetry.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlRetryCommand( 4 | val maxRetries: String? = null, 5 | val file: String? = null, 6 | val commands: List? = null, 7 | val env: Map = emptyMap(), 8 | val label: String? = null, 9 | val optional: Boolean = false, 10 | ) -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunScript.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlRunScript( 6 | val file: String, 7 | val env: Map = emptyMap(), 8 | val `when`: YamlCondition? = null, 9 | val label: String? = null, 10 | val optional: Boolean = false, 11 | ) { 12 | 13 | companion object { 14 | 15 | @JvmStatic 16 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 17 | fun parse(file: String) = YamlRunScript( 18 | file = file, 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetLocation.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlSetLocation @JsonCreator constructor( 6 | val latitude: String, 7 | val longitude: String, 8 | val label: String? = null, 9 | val optional: Boolean = false, 10 | ) 11 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlStopApp( 6 | val appId: String? = null, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | 11 | companion object { 12 | 13 | @JvmStatic 14 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 15 | fun parse(appId: String) = YamlStopApp( 16 | appId = appId, 17 | ) 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTakeScreenshot.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator 4 | 5 | data class YamlTakeScreenshot( 6 | val path: String, 7 | val label: String? = null, 8 | val optional: Boolean = false, 9 | ) { 10 | 11 | companion object { 12 | 13 | @JvmStatic 14 | @JsonCreator(mode = JsonCreator.Mode.DELEGATING) 15 | fun parse(path: String): YamlTakeScreenshot { 16 | return YamlTakeScreenshot( 17 | path = path, 18 | ) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlToggleAirplaneMode.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlToggleAirplaneMode( 4 | val label: String? = null, 5 | val optional: Boolean = false, 6 | ) 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTravelCommand.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlTravelCommand( 4 | val points: List, 5 | val speed: Double? = null, 6 | val label: String? = null, 7 | val optional: Boolean = false, 8 | ) 9 | -------------------------------------------------------------------------------- /maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlWaitForAnimationToEndCommand.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml 2 | 3 | data class YamlWaitForAnimationToEndCommand( 4 | val timeout: Long?, 5 | val label: String? = null, 6 | val optional: Boolean = false, 7 | ) 8 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/java/maestro/orchestra/yaml/junit/YamlFile.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml.junit 2 | 3 | @Retention(AnnotationRetention.RUNTIME) 4 | @Target(AnnotationTarget.VALUE_PARAMETER) 5 | annotation class YamlFile(val name: String) 6 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/java/maestro/orchestra/yaml/junit/YamlResourceFile.kt: -------------------------------------------------------------------------------- 1 | package maestro.orchestra.yaml.junit 2 | 3 | import java.nio.file.Path 4 | import java.nio.file.Paths 5 | 6 | class YamlResourceFile(val name: String) { 7 | val path: Path get() { 8 | val resource = this::class.java.getResource("/YamlCommandReaderTest/${name}")!! 9 | return Paths.get(resource.toURI()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/002_launchApp.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/003_launchApp_withClearState.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | clearState: true 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/008_config_unknownKeys.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | extra: True 3 | extraMap: 4 | keyA: valueB 5 | extraArray: 6 | - itemA 7 | --- 8 | - launchApp 9 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/017_launchApp_otherPackage.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: com.other.app 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/018_backPress_string.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - back 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/019_scroll_string.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scroll 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/020_config_name.yaml: -------------------------------------------------------------------------------- 1 | name: Example Flow 2 | appId: com.example.app 3 | --- 4 | - launchApp 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/022_on_flow_start_complete.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - back 4 | onFlowComplete: 5 | - scroll 6 | --- 7 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/023_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/YamlCommandReaderTest/023_image.png -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/023_runScript_test.js: -------------------------------------------------------------------------------- 1 | const myNumber = 1 + 1; -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/025_killApp.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - killApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/027_waitToSettleTimeoutMs.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scrollUntilVisible: 4 | element: 5 | id: "maybe-later" 6 | waitToSettleTimeoutMs: 50 7 | direction: DOWN 8 | - swipe: 9 | start: 90%, 50% 10 | end: 10%, 50% 11 | waitToSettleTimeoutMs: 50 12 | - swipe: 13 | direction: LEFT 14 | waitToSettleTimeoutMs: 50 15 | - swipe: 16 | from: 17 | id: "feeditem_identifier" 18 | direction: LEFT 19 | waitToSettleTimeoutMs: 50 20 | - swipe: 21 | start: 100, 200 22 | end: 300, 400 23 | waitToSettleTimeoutMs: 50 24 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/028_command_descriptions.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: "maybe-later" 5 | label: "Scroll to find the maybe-later button" 6 | 7 | - inputText: 8 | text: ${username} 9 | 10 | - assertVisible: 11 | text: "Hello ${username}" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/YamlCommandReaderTest/flow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/YamlCommandReaderTest/flow.zip -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/android/add_media_gif.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.android.apps.photos 2 | --- 3 | - addMedia: 4 | - "../assets/android_gif.gif" 5 | - launchApp: 6 | appId: com.google.android.apps.photos 7 | - runFlow: 8 | when: 9 | visible: Update Now 10 | commands: 11 | - tapOn: 12 | text: Update Now 13 | optional: true 14 | - back 15 | # assert that photo is taken 16 | - assertVisible: "Photo taken on.*" 17 | - tapOn: "Photo taken on.*" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/android/add_media_jpeg.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.android.apps.photos 2 | --- 3 | - addMedia: 4 | - "../assets/android_jpeg.jpeg" 5 | - launchApp: 6 | appId: com.google.android.apps.photos 7 | - runFlow: 8 | when: 9 | visible: Update Now 10 | commands: 11 | - tapOn: 12 | text: Update Now 13 | optional: true 14 | - back 15 | # assert that photo is taken 16 | - assertVisible: "Photo taken on.*" 17 | - tapOn: "Photo taken on.*" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/android/add_media_jpg.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.android.apps.photos 2 | --- 3 | - addMedia: 4 | - "../assets/android_jpg.jpg" 5 | - launchApp: 6 | appId: com.google.android.apps.photos 7 | - runFlow: 8 | when: 9 | visible: Update Now 10 | commands: 11 | - tapOn: 12 | text: Update Now 13 | optional: true 14 | - back 15 | # assert that photo is taken 16 | - tapOn: "Photo taken on.*" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/android/add_media_mp4.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.android.apps.photos 2 | --- 3 | - addMedia: 4 | - "../assets/sample_video.mp4" 5 | - launchApp: 6 | appId: com.google.android.apps.photos 7 | - runFlow: 8 | when: 9 | visible: Update Now 10 | commands: 11 | - tapOn: 12 | text: Update Now 13 | optional: true 14 | - back 15 | # assert that photo is taken 16 | - assertVisible: "Video taken on.*" 17 | - tapOn: "Video taken on.*" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/android/add_media_png.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.android.apps.photos 2 | --- 3 | - addMedia: 4 | - "../assets/android.png" 5 | - launchApp: 6 | appId: com.google.android.apps.photos 7 | - runFlow: 8 | when: 9 | visible: Update Now 10 | commands: 11 | - tapOn: 12 | text: Update Now 13 | optional: true 14 | - back 15 | # assert that photo is taken 16 | - assertVisible: "Photo taken on.*" 17 | - tapOn: "Photo taken on.*" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_media_gif.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "./maestro-orchestra/src/test/resources/media/assets/android.gif" 5 | - launchApp 6 | - assertVisible: All Photos -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_media_jpeg.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "../assets/android_jpeg.jpeg" 5 | - launchApp 6 | - assertVisible: All Photos -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_media_jpg.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "./maestro-orchestra/src/test/resources/media/assets/android_jpg.jpg" 5 | - launchApp 6 | - assertVisible: All Photos -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_media_mp4.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "./maestro-orchestra/src/test/resources/media/assets/sample_video.mp4" 5 | - launchApp 6 | - assertVisible: All Photos -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_media_png.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "./maestro-orchestra/src/test/resources/media/assets/android.png" 5 | - launchApp 6 | - assertVisible: All Photos -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/media/ios/add_multiple_media.yaml: -------------------------------------------------------------------------------- 1 | appId: com.apple.mobileslideshow 2 | --- 3 | - addMedia: 4 | - "./maestro-orchestra/src/test/resources/media/assets/android.gif" 5 | - "./maestro-orchestra/src/test/resources/media/assets/android_jpeg.jpeg" 6 | - "./maestro-orchestra/src/test/resources/media/assets/sample_video.mp4" 7 | - "./maestro-orchestra/src/test/resources/media/assets/android.png" 8 | - launchApp 9 | - assertVisible: All Photos 10 | - tapOn: 11 | point: "16%,19%" 12 | - tapOn: 13 | point: "50%,19%" 14 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/.gitignore: -------------------------------------------------------------------------------- 1 | error.actual.txt -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/000_individual_file/flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/001_simple/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/001_simple/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/001_simple/notAFlow.txt: -------------------------------------------------------------------------------- 1 | This file is not a flow -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/002_subflows/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/002_subflows/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | - runFlow: subflows/subflow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/002_subflows/subflows/subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/003_include_tags/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - someOtherTag 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/003_include_tags/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - notIncluded 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/003_include_tags/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/004_exclude_tags/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - someOtherTag 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/004_exclude_tags/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - excluded 4 | - someOtherTag 5 | --- 6 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/004_exclude_tags/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/005_custom_include_pattern/config.yaml: -------------------------------------------------------------------------------- 1 | flows: 2 | - featureA/* 3 | - featureB/* -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/005_custom_include_pattern/featureA/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/005_custom_include_pattern/featureB/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/005_custom_include_pattern/featureC/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/005_custom_include_pattern/flowD.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/006_include_subfolders/config.yaml: -------------------------------------------------------------------------------- 1 | flows: 2 | - "**" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/006_include_subfolders/featureA/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/006_include_subfolders/featureB/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/006_include_subfolders/featureC/subfolder/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/006_include_subfolders/flowD.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/007_empty_config/config.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/workspaces/007_empty_config/config.yml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/007_empty_config/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/007_empty_config/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/008_literal_pattern/config.yaml: -------------------------------------------------------------------------------- 1 | flows: featureA/* -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/008_literal_pattern/featureA/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/008_literal_pattern/featureB/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/009_custom_config_fields/config.yml: -------------------------------------------------------------------------------- 1 | customField: Custom Value -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/009_custom_config_fields/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/009_custom_config_fields/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/config.yaml: -------------------------------------------------------------------------------- 1 | includeTags: 2 | - featureA -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - featureA 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowA_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - featureA 4 | --- 5 | - launchApp 6 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - featureB 5 | --- 6 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowD.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - notIncluded 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/010_global_include_tags/flowE.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/config.yaml: -------------------------------------------------------------------------------- 1 | excludeTags: notIncluded -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - featureA 5 | --- 6 | - launchApp 7 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowA_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - featureA 4 | --- 5 | - launchApp 6 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | - featureB 5 | --- 6 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowD.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - notIncluded 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/011_global_exclude_tags/flowE.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/012_local_deterministic_order/config.yaml: -------------------------------------------------------------------------------- 1 | local: 2 | deterministicOrder: true -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/012_local_deterministic_order/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/012_local_deterministic_order/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/012_local_deterministic_order/flowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/013_execution_order/config.yaml: -------------------------------------------------------------------------------- 1 | executionOrder: 2 | flowsOrder: 3 | - flowB 4 | - flowC 5 | - flowD -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/013_execution_order/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/013_execution_order/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/013_execution_order/flowCWithCustomName.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | name: flowC 3 | --- 4 | - launchApp 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/013_execution_order/flowD.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/014_config_not_null/config.yaml: -------------------------------------------------------------------------------- 1 | includeTags: 2 | - included 3 | - excluded 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/014_config_not_null/config/another_config.yaml: -------------------------------------------------------------------------------- 1 | includeTags: 2 | - included 3 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/014_config_not_null/flowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - included 4 | --- 5 | - launchApp 6 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/014_config_not_null/flowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: 3 | - excluded 4 | --- 5 | - launchApp 6 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e000_flow_path_does_not_exist/error.txt: -------------------------------------------------------------------------------- 1 | Flow path does not exist: /tmp/WorkspaceExecutionPlannerErrorsTest_workspace/workspace -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e001_directory_does_not_contain_flow_files/error.txt: -------------------------------------------------------------------------------- 1 | Flow directories do not contain any Flow files: /tmp/WorkspaceExecutionPlannerErrorsTest_workspace/workspace -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e001_directory_does_not_contain_flow_files/workspace/dummy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/workspaces/e001_directory_does_not_contain_flow_files/workspace/dummy -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e002_top_level_directory_does_not_contain_flow_files/error.txt: -------------------------------------------------------------------------------- 1 | Top-level directories do not contain any Flows: /tmp/WorkspaceExecutionPlannerErrorsTest_workspace/workspace 2 | To configure Maestro to run Flows in subdirectories, check out the following resources: 3 | * https://maestro.mobile.dev/cli/test-suites-and-reports#inclusion-patterns 4 | * https://blog.mobile.dev/maestro-best-practices-structuring-your-test-suite-54ec390c5c82 -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e002_top_level_directory_does_not_contain_flow_files/workspace/subdir/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e003_flow_inclusion_pattern_does_not_match_any_flow_files/error.txt: -------------------------------------------------------------------------------- 1 | Flow inclusion pattern(s) did not match any Flow files: 2 | - FlowA.yaml 3 | - FlowB.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e003_flow_inclusion_pattern_does_not_match_any_flow_files/workspace/FlowC.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e003_flow_inclusion_pattern_does_not_match_any_flow_files/workspace/config.yaml: -------------------------------------------------------------------------------- 1 | flows: 2 | - FlowA.yaml 3 | - FlowB.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/error.txt: -------------------------------------------------------------------------------- 1 | Include / Exclude tags did not match any Flows: 2 | 3 | Include Tags: 4 | - parameterInclude 5 | - configInclude 6 | 7 | Exclude Tags: 8 | - parameterExclude 9 | - configExclude -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/excludeTags.txt: -------------------------------------------------------------------------------- 1 | parameterExclude -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/includeTags.txt: -------------------------------------------------------------------------------- 1 | parameterInclude -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/workspace/ConfigExclude.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | - configInclude 3 | - configExclude 4 | appId: com.example 5 | --- 6 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/workspace/ParameterExclude.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | - parameterInclude 3 | - parameterExclude 4 | appId: com.example 5 | --- 6 | - launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e004_tags_config_does_not_match_any_flow_files/workspace/config.yaml: -------------------------------------------------------------------------------- 1 | includeTags: 2 | - configInclude 3 | excludeTags: 4 | - configExclude -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e005_single_flow_does_not_exist/error.txt: -------------------------------------------------------------------------------- 1 | Flow path does not exist: /tmp/WorkspaceExecutionPlannerErrorsTest_workspace/workspace/Flow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e005_single_flow_does_not_exist/singleFlow.txt: -------------------------------------------------------------------------------- 1 | Flow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e006_single_flow_invalid_string_command/singleFlow.txt: -------------------------------------------------------------------------------- 1 | Flow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e006_single_flow_invalid_string_command/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - invalidCommand -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e007_single_flow_malformatted_command/singleFlow.txt: -------------------------------------------------------------------------------- 1 | Flow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e007_single_flow_malformatted_command/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - launchApp: 4 | invalidOption: true -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e008_subflow_invalid_string_command/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - runFlow: ./subflow/SubFlow.yaml 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e008_subflow_invalid_string_command/workspace/subflow/SubFlow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - invalidCommand -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e009_nested_subflow_invalid_string_command/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - runFlow: ./subflow/SubFlowA.yaml 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e009_nested_subflow_invalid_string_command/workspace/subflow/SubFlowA.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - runFlow: ./SubFlowB.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e009_nested_subflow_invalid_string_command/workspace/subflow/SubFlowB.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - invalidCommand -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e010_missing_config_section/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | - launchApp 2 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e011_missing_dashes/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | launchApp -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e012_invalid_subflow_path/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - runFlow: invalidpath.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e013_invalid_media_file/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - addMedia: 4 | - "./assets/invalid_android.png" 5 | - launchApp 6 | - tapOn: "Add to Cart" 7 | - assertVisible: "Added" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e013_invalid_media_file/workspace/assets/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/workspaces/e013_invalid_media_file/workspace/assets/android.png -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e014_invalid_media_file_outside/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example 2 | --- 3 | - addMedia: 4 | - "../../e013_invalid_media_file/workspace/assets/android.png" 5 | - launchApp 6 | - tapOn: "Add to Cart" 7 | - assertVisible: "Added" -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e015_array_command/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - [foo, bar, baz] -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e016_config_invalid_command_in_onFlowStart/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - inp 4 | --- -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e017_config_invalid_tags/error.txt: -------------------------------------------------------------------------------- 1 | > Incorrect Format: tags 2 | 3 | /tmp/WorkspaceExecutionPlannerErrorsTest_workspace/workspace/Flow.yaml:2 4 | ╭──────────────────────────────────────╮ 5 | │ 1 | appId: com.example.app │ 6 | │ 2 | tags: foo, bar │ 7 | │ ^ │ 8 | │ ╭──────────────────────────────────╮ │ 9 | │ │ The format for tags is incorrect │ │ 10 | │ ╰──────────────────────────────────╯ │ 11 | │ 3 | --- │ 12 | │ 4 | │ 13 | ╰──────────────────────────────────────╯ -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e017_config_invalid_tags/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | tags: foo, bar 3 | --- 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e018_config_missing_appId/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | name: MyFlow 2 | --- 3 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e019_invalid_swipe_direction/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - swipe: 4 | direction: diagonal 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e020_missing_command_options/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn 4 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e021_multiple_command_names/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: foo 4 | inputText: bar 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e022_top_level_option/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: foo 4 | optional: true 5 | -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e023_empty/workspace/Flow.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-orchestra/src/test/resources/workspaces/e023_empty/workspace/Flow.yaml -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e023_empty_commands/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- -------------------------------------------------------------------------------- /maestro-orchestra/src/test/resources/workspaces/e023_launchApp_empty_string/workspace/Flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | -------------------------------------------------------------------------------- /maestro-proto/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.SonatypeHost 2 | 3 | plugins { 4 | id("maven-publish") 5 | java 6 | alias(libs.plugins.mavenPublish) 7 | } 8 | 9 | mavenPublishing { 10 | publishToMavenCentral(SonatypeHost.S01) 11 | } 12 | 13 | java { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | tasks.named("jar") { 19 | from("src/main/proto/maestro_android.proto") 20 | } 21 | -------------------------------------------------------------------------------- /maestro-proto/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Orchestra Proto 2 | POM_ARTIFACT_ID=maestro-proto 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /maestro-studio/server/.gitignore: -------------------------------------------------------------------------------- 1 | /src/main/resources/web -------------------------------------------------------------------------------- /maestro-studio/server/src/main/java/maestro/studio/HttpException.kt: -------------------------------------------------------------------------------- 1 | package maestro.studio 2 | 3 | import io.ktor.http.HttpStatusCode 4 | 5 | data class HttpException( 6 | val statusCode: HttpStatusCode, 7 | val errorMessage: String, 8 | ) : RuntimeException("$statusCode: $errorMessage") 9 | -------------------------------------------------------------------------------- /maestro-studio/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | yarn.lock -------------------------------------------------------------------------------- /maestro-studio/web/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /maestro-studio/web/.nvmrc: -------------------------------------------------------------------------------- 1 | v16.14.0 2 | -------------------------------------------------------------------------------- /maestro-studio/web/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/addon-interactions", 10 | "@storybook/preset-create-react-app", 11 | "storybook-addon-react-router-v6" 12 | ], 13 | "framework": "@storybook/react", 14 | "core": { 15 | "builder": "@storybook/builder-webpack5" 16 | }, 17 | "staticDirs": ['../public', '../storybook-assets'], 18 | } -------------------------------------------------------------------------------- /maestro-studio/web/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { withRouter } from "storybook-addon-react-router-v6"; 2 | 3 | import "../src/style/index.css"; 4 | import { installMocks } from "../src/api/mocks"; 5 | 6 | installMocks(); 7 | 8 | export const decorators = [withRouter]; 9 | 10 | export const parameters = { 11 | actions: { argTypesRegex: "^on[A-Z].*" }, 12 | controls: { 13 | matchers: { 14 | color: /(background|color)$/i, 15 | date: /Date$/, 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /maestro-studio/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /maestro-studio/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-studio/web/public/favicon.ico -------------------------------------------------------------------------------- /maestro-studio/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Maestro Studio 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /maestro-studio/web/src/components/design-system/keyboard-key.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | interface KeyboardKeyProps { 4 | children: ReactNode; 5 | } 6 | 7 | const KeyboardKey = ({ children }: KeyboardKeyProps) => { 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | }; 14 | 15 | export default KeyboardKey; 16 | -------------------------------------------------------------------------------- /maestro-studio/web/src/components/design-system/utils/functions.tsx: -------------------------------------------------------------------------------- 1 | export const checkImageUrl = async (imageUrl: string) => { 2 | try { 3 | const response = await fetch(imageUrl); 4 | if ( 5 | response.ok && 6 | response.headers.get("content-type")?.startsWith("image/") 7 | ) { 8 | return true; 9 | } else { 10 | return false; 11 | } 12 | } catch (error) { 13 | return false; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /maestro-studio/web/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | import "./style/index.css"; 5 | import App from "./App"; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById("root") as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /maestro-studio/web/src/pages/InteractPage.tsx: -------------------------------------------------------------------------------- 1 | import { DeviceProvider } from "../context/DeviceContext"; 2 | import InteractPageLayout from "../components/interact/InteractPageLayout"; 3 | import { ReplProvider } from '../context/ReplContext'; 4 | 5 | export default function InteractPage() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /maestro-studio/web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /maestro-studio/web/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /maestro-studio/web/src/storybook/App.stories.tsx: -------------------------------------------------------------------------------- 1 | import App from "../App"; 2 | 3 | export default { 4 | title: "App", 5 | parameters: { 6 | layout: "fullscreen", 7 | }, 8 | }; 9 | 10 | export const MainStory = () => { 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /maestro-studio/web/src/storybook/ConfirmationDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ConfirmationDialog } from "../components/common/ConfirmationDialog"; 2 | 3 | export default { 4 | title: "ConfirmationDialog", 5 | }; 6 | 7 | export const Main = () => { 8 | return ( 9 | 10 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /maestro-studio/web/src/storybook/Header.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "../components/common/Header"; 3 | 4 | export default { 5 | title: "Header", 6 | }; 7 | 8 | export const Main = () => { 9 | return
; 10 | }; 11 | -------------------------------------------------------------------------------- /maestro-studio/web/src/storybook/InteractPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import InteractPage from "../pages/InteractPage"; 2 | 3 | export default { 4 | title: "InteractPage", 5 | parameters: { 6 | layout: "fullscreen", 7 | }, 8 | }; 9 | 10 | export const Main = () => { 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /maestro-studio/web/src/storybook/ReplView.stories.tsx: -------------------------------------------------------------------------------- 1 | import ReplView from "../components/commands/ReplView"; 2 | import { DeviceProvider } from "../context/DeviceContext"; 3 | 4 | export default { 5 | title: "ReplView", 6 | }; 7 | 8 | export const Main = () => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /maestro-studio/web/src/style/fonts/JetBrainsMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-studio/web/src/style/fonts/JetBrainsMono-Italic.ttf -------------------------------------------------------------------------------- /maestro-studio/web/src/style/fonts/JetBrainsMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-studio/web/src/style/fonts/JetBrainsMono.ttf -------------------------------------------------------------------------------- /maestro-studio/web/storybook-assets/sample-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-studio/web/storybook-assets/sample-screenshot.png -------------------------------------------------------------------------------- /maestro-studio/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/001_assert_visible_by_id.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | id: "element_id" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/002_assert_visible_by_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | text: "Element Text" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/003_assert_visible_by_size.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | width: 100 5 | height: 100 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/004_assert_no_visible_element_with_id.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | id: "element_id" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/005_assert_no_visible_element_with_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | text: "Element Text" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/006_assert_no_visible_element_with_size.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | width: 100 5 | height: 100 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/007_assert_visible_by_size_with_tolerance.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | width: 100 5 | height: 100 6 | tolerance: 1 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/008_tap_on_element.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: ".*button.*" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/009_skip_optional_elements.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: "Optional Element" 5 | optional: true 6 | - assertVisible: 7 | text: "Non Optional" 8 | optional: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/010_scroll.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scroll -------------------------------------------------------------------------------- /maestro-test/src/test/resources/011_back_press.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - back -------------------------------------------------------------------------------- /maestro-test/src/test/resources/012_input_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: "Hello World" 4 | - inputText: user@example.com -------------------------------------------------------------------------------- /maestro-test/src/test/resources/013_launch_app.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp -------------------------------------------------------------------------------- /maestro-test/src/test/resources/014_tap_on_point.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | point: 100,200 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/016_multiline_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: "Hello world.*" 5 | retryTapIfNoChange: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/017_swipe.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - swipe: 4 | start: 100,500 5 | end: 100,200 6 | duration: 3000 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/018_contains_child.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | retryTapIfNoChange: false 5 | containsChild: 6 | text: "Child" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/019_dont_wait_for_visibility.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: "Button" 5 | retryTapIfNoChange: false 6 | waitUntilVisible: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/020_parse_config.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/021_launch_app_with_clear_state.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | clearState: true -------------------------------------------------------------------------------- /maestro-test/src/test/resources/022_launch_app_that_is_not_installed.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.nonexistent 2 | --- 3 | - launchApp: 4 | clearState: true -------------------------------------------------------------------------------- /maestro-test/src/test/resources/026_assert_not_visible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertNotVisible: 4 | id: "element_id" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/027_open_link.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - openLink: https://example.com -------------------------------------------------------------------------------- /maestro-test/src/test/resources/029_long_press_on_element.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - longPressOn: 4 | text: ".*button.*" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/030_long_press_on_point.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - longPressOn: 4 | point: 100,200 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/031_traits.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | traits: text 5 | retryTapIfNoChange: false 6 | - tapOn: 7 | traits: square 8 | retryTapIfNoChange: false 9 | - tapOn: 10 | traits: long-text 11 | retryTapIfNoChange: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/032_element_index.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: Item.* 5 | index: 0 6 | retryTapIfNoChange: false 7 | - tapOn: 8 | text: Item.* 9 | index: ${0 + 1} 10 | retryTapIfNoChange: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/033_int_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 2022 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/035_refresh_position_ignore_duplicates.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | below: Item 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/036_erase_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: Hello World 4 | - eraseText: 6 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/037_unicode_input.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: Tést inpüt 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/038_partial_id.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | id: "keyboard_area" 5 | retryTapIfNoChange: false 6 | - tapOn: 7 | id: ".*keyboard_area" 8 | retryTapIfNoChange: false 9 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/039_hide_keyboard.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - hideKeyboard -------------------------------------------------------------------------------- /maestro-test/src/test/resources/040_escape_regex.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: \+123456 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/041_take_screenshot.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - takeScreenshot: ${MAESTRO_FILENAME}_with_filename 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/042_extended_wait.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | TIMEOUT: 1000 4 | --- 5 | - extendedWaitUntil: 6 | visible: Item 7 | timeout: ${TIMEOUT} 8 | - extendedWaitUntil: 9 | notVisible: Another item 10 | timeout: 1000 11 | - extendedWaitUntil: 12 | visible: Item 13 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/043_stop_app.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - stopApp 4 | - stopApp: another.app 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/044_clear_state.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - clearState 4 | - clearState: another.app 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/045_clear_keychain.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - clearKeychain 4 | - launchApp: 5 | appId: com.example.app 6 | clearKeychain: true 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/046_run_flow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - runFlow: 013_launch_app.yaml 4 | - tapOn: Primary button -------------------------------------------------------------------------------- /maestro-test/src/test/resources/047_run_flow_nested.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - runFlow: 046_run_flow.yaml 4 | - tapOn: Secondary Button -------------------------------------------------------------------------------- /maestro-test/src/test/resources/048_tapOn_clickable.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: Button -------------------------------------------------------------------------------- /maestro-test/src/test/resources/049_run_flow_conditionally.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - runFlow: 4 | when: 5 | visible: Not Clicked 6 | file: 008_tap_on_element.yaml 7 | - assertVisible: Clicked 8 | - runFlow: 9 | when: 10 | visible: ${NOT_CLICKED} 11 | file: 008_tap_on_element.yaml -------------------------------------------------------------------------------- /maestro-test/src/test/resources/051_set_location.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | appId: com.example.app 5 | - setLocation: 6 | latitude: 12.5266 7 | longitude: 78.2150 8 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/052_text_random.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputRandomText 4 | - inputRandomNumber 5 | - inputRandomText: 6 | length: 5 7 | - inputRandomNumber: 8 | length: 5 9 | - inputRandomEmail 10 | - inputRandomPersonName 11 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/053_repeat_times.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - repeat: 4 | times: 3 5 | commands: 6 | - tapOn: Button 7 | - assertVisible: "3" 8 | - evalScript: ${output.list = [1, 2, 3]} 9 | - repeat: 10 | times: ${output.list.length} 11 | commands: 12 | - tapOn: Button 13 | - assertVisible: "6" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/054_enabled.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - assertVisible: 4 | text: Button 5 | enabled: true 6 | - tapOn: Button 7 | - assertNotVisible: 8 | text: Button 9 | enabled: true 10 | - assertVisible: 11 | text: Button 12 | enabled: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/055_compare_regex.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: (Secondary button) -------------------------------------------------------------------------------- /maestro-test/src/test/resources/056_ignore_error.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: Non existent text 4 | - repeat: 5 | times: 2 6 | commands: 7 | - tapOn: Non existent text 8 | - tapOn: Button 9 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/057_runFlow_env.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runFlow: 4 | file: 057_subflow.yaml 5 | env: 6 | INNER_ENV: Inner Parameter 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/057_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: ${INNER_ENV} 4 | - inputText: ${OUTER_ENV} 5 | - runFlow: 6 | file: 057_subflow_override.yaml 7 | env: 8 | INNER_ENV: Overriden Parameter 9 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/057_subflow_override.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: ${INNER_ENV} 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/058_inline_env.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | INLINE_ENV: Inline Parameter 4 | --- 5 | - inputText: ${INLINE_ENV} 6 | - runFlow: 058_subflow.yaml 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/058_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | INLINE_ENV: Overriden Parameter 4 | --- 5 | - inputText: ${INLINE_ENV} 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/059_directional_swipe_command.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - swipe: 4 | direction: RIGHT 5 | duration: 500 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/060_pass_env_to_env.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | PARAM_INLINE: ${PARAM} 4 | PARAM: ${PARAM} 5 | --- 6 | - runFlow: 7 | file: 060_subflow.yaml 8 | env: 9 | PARAM_FLOW: ${PARAM_INLINE} 10 | PARAM: ${PARAM} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/060_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: ${PARAM_FLOW} 4 | - inputText: ${PARAM_INLINE} 5 | - inputText: ${PARAM} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/061_launchApp_withoutStopping.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | stopApp: false -------------------------------------------------------------------------------- /maestro-test/src/test/resources/062_copy_paste_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - copyTextFrom: 4 | id: "myId" 5 | - pasteText 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/063_js_injection.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | A: 1 4 | B: 2 5 | --- 6 | - inputText: ${A} 7 | - inputText: ${B} 8 | - inputText: ${A + B} 9 | - inputText: ${parseInt(A) + parseInt(B)} 10 | - inputText: \${A} \${B} ${A} ${B} 11 | 12 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/064_script.js: -------------------------------------------------------------------------------- 1 | output.sharedResult = 'Main' 2 | output.mainFlow = { 3 | result: 'Main' 4 | } 5 | output.fileName = MAESTRO_FILENAME 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/064_script_alt.js: -------------------------------------------------------------------------------- 1 | output.sharedResult = 'Sub' 2 | output.subFlow = { 3 | result: 'Sub' 4 | } 5 | output.subFlowFileName = MAESTRO_FILENAME 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/064_script_with_args.js: -------------------------------------------------------------------------------- 1 | output.resultWithParameters = 'Hello, ' + parameter + '!' 2 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/064_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runScript: 064_script_alt.js 4 | - inputText: ${output.sharedResult} 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/065_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: ${name} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/066_copyText_jsVar.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - copyTextFrom: 4 | id: Field 5 | - inputText: ${'Hello, ' + maestro.copiedText} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/067_assertTrue_fail.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertTrue: ${1-1} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/067_assertTrue_pass.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertTrue: ${1+1} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/068_erase_all_text.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - inputText: Hello World 4 | - eraseText 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/069_wait_for_animation_to_end.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - waitForAnimationToEnd: 4 | timeout: 500 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/070_evalScript.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - evalScript: ${output.number = 1 + 1} 4 | - evalScript: "${output.text = 'Result is: ' + output.number}" 5 | - inputText: ${output.number} 6 | - inputText: ${output.text} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/071_tapOnRelativePoint.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | QUARTER: 25% 4 | QUARTER_FLOAT: 0.25 5 | --- 6 | - tapOn: 7 | point: 0%,0% 8 | - tapOn: 9 | point: 100%,100% 10 | - tapOn: 11 | point: 50%,50% 12 | - tapOn: 13 | point: ${QUARTER},${QUARTER} 14 | - tapOn: 15 | point: ${relativePoint(QUARTER_FLOAT,QUARTER_FLOAT)} 16 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/072_searchDepthFirst.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: "Element" 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/073_handle_linebreaks.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: Hello World 4 | - tapOn: Hello\nWorld -------------------------------------------------------------------------------- /maestro-test/src/test/resources/074_directional_swipe_element.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - swipe: 4 | direction: RIGHT 5 | from: 6 | text: "swiping element" 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/075_repeat_while.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - repeat: 4 | while: 5 | notVisible: "Value 3" 6 | commands: 7 | - tapOn: Button 8 | - assertVisible: "Value 3" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/076_optional_assertion.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scrollUntilVisible: 4 | timeout: 1 5 | element: 6 | id: "not_found" 7 | optional: true 8 | - assertTrue: 9 | condition: "false" 10 | optional: true 11 | - extendedWaitUntil: 12 | visible: 13 | id: "not_found" 14 | timeout: 1 15 | optional: true 16 | - assertVisible: 17 | text: "Button" 18 | optional: true 19 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/077_env_special_characters.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | INNER: | 4 | !@#$&*()_+{}|:"<>?[]\\;',./ 5 | --- 6 | - inputText: ${OUTER} 7 | - inputText: ${INNER} 8 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/078_swipe_relative.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - swipe: 4 | start: "50%,30%" 5 | end: "50%,60%" 6 | duration: 3000 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/079_scroll_until_visible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scrollUntilVisible: 4 | element: 5 | text: "Test" 6 | speed: 100 7 | visibilityPercentage: 100 8 | direction: DOWN 9 | timeout: 10 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/080_hierarchy_pruning_assert_visible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | id: "visible_1" 5 | - assertVisible: 6 | id: "visible_2" 7 | - assertVisible: 8 | id: "visible_3" 9 | - assertVisible: 10 | id: "visible_4" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/081_hierarchy_pruning_assert_not_visible.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertNotVisible: 4 | id: "not_visible_1" 5 | - assertNotVisible: 6 | id: "not_visible_2" 7 | - assertNotVisible: 8 | id: "not_visible_3" 9 | - assertNotVisible: 10 | id: "not_visible_4" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/082_repeat_while_true.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - evalScript: ${output.value = 0} 4 | - repeat: 5 | while: 6 | true: ${output.value < 3} 7 | commands: 8 | - evalScript: ${output.value = output.value + 1} 9 | - inputText: ${output.value} 10 | - tapOn: Button 11 | - assertVisible: "Value 3" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/084_open_browser.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - openLink: 4 | link: https://example.com 5 | browser: true -------------------------------------------------------------------------------- /maestro-test/src/test/resources/085_open_link_auto_verify.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - openLink: 4 | link: https://example.com 5 | autoVerify: true -------------------------------------------------------------------------------- /maestro-test/src/test/resources/086_launchApp_sets_all_permissions_to_allow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp 4 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/087_launchApp_with_all_permissions_to_deny.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | permissions: { all: deny } 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/088_launchApp_with_all_permissions_to_deny_and_notification_to_allow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | permissions: 5 | all: deny 6 | notifications: allow 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/089_launchApp_with_sms_permission_group_to_allow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - launchApp: 4 | permissions: 5 | sms: allow 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/090_travel.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - travel: 4 | points: 5 | - 0.0,0.0 6 | - 0.1,0.0 7 | - 0.1,0.1 8 | - 0.0,0.1 9 | speed: 7900 # 7.9 km/s aka orbital velocity 10 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/091_assert_visible_by_index.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | text: Item 5 | index: 0 6 | - assertVisible: 7 | text: Item 8 | index: 1 9 | - assertNotVisible: 10 | text: Item 11 | index: 2 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/092_log_messages.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - evalScript: ${console.log('Log from evalScript')} 4 | - runScript: 092_script.js -------------------------------------------------------------------------------- /maestro-test/src/test/resources/092_script.js: -------------------------------------------------------------------------------- 1 | console.log('Log from runScript') -------------------------------------------------------------------------------- /maestro-test/src/test/resources/093_js_default_value.yaml: -------------------------------------------------------------------------------- 1 | appId: ${APP_ID} 2 | env: 3 | APP_ID: ${APP_ID || 'com.example.default'} 4 | --- 5 | - launchApp -------------------------------------------------------------------------------- /maestro-test/src/test/resources/094_runFlow_inline.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runFlow: 4 | env: 5 | INNER_ENV: Inner Parameter 6 | commands: 7 | - inputText: ${INNER_ENV} 8 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/095_launch_arguments.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | env: 3 | ARGUMENT_B: argumentB 4 | BOOL_VALUE: true 5 | --- 6 | - launchApp: 7 | arguments: 8 | argumentA: true 9 | ${ARGUMENT_B}: 4 10 | argumentC: 4.0 11 | argumentD: "Hello String Value ${BOOL_VALUE}" 12 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/096_platform_condition.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runFlow: 4 | when: 5 | platform: iOS 6 | commands: 7 | - inputText: Hello iOS 8 | - runFlow: 9 | when: 10 | platform: ios 11 | commands: 12 | - inputText: Hello ios 13 | - runFlow: 14 | when: 15 | platform: Android 16 | commands: 17 | - inputText: Hello Android 18 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/097_contains_descendants.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | id: id1 5 | containsDescendants: 6 | - text: "Child 1" 7 | - text: "Child 2" 8 | enabled: false 9 | - assertVisible: 10 | id: id2 11 | containsDescendants: 12 | - text: "Child 1" 13 | - assertNotVisible: 14 | id: id1 15 | containsDescendants: 16 | - text: "Child 1" 17 | - text: "Child 3" 18 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/098_runScript.js: -------------------------------------------------------------------------------- 1 | console.log('Log from runScript') -------------------------------------------------------------------------------- /maestro-test/src/test/resources/098_runscript_conditionals.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - runScript: 4 | when: 5 | visible: Click me 6 | file: 098_runScript.js 7 | - tapOn: Click me 8 | - assertVisible: Clicked 9 | - assertNotVisible: Click me 10 | - runScript: 11 | when: 12 | visible: Not Clicked 13 | file: 098_runScript.js -------------------------------------------------------------------------------- /maestro-test/src/test/resources/099_screen_recording.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - startRecording: ${MAESTRO_FILENAME} 4 | - stopRecording 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/100_tapOn_multiple_times.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - tapOn: 4 | text: Button 5 | repeat: 3 6 | delay: 1 7 | retryTapIfNoChange: false 8 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/101_doubleTapOn.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - doubleTapOn: 4 | text: Button 5 | retryTapIfNoChange: false 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/102_graaljs.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | jsEngine: graaljs 3 | --- 4 | # Note: The ?? operator is an example of an ES2020 feature and is not supported by RhinoJS 5 | - inputText: ${null ?? 'foo'} 6 | - runFlow: 102_graaljs_subflow.yaml -------------------------------------------------------------------------------- /maestro-test/src/test/resources/102_graaljs_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | # Still uses graaljs in a subflow based on the top-level flow config 4 | - inputText: ${null ?? 'bar'} 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/103_on_flow_start_complete_hooks.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - runScript: "103_setup.js" 4 | - inputText: "test1" 5 | onFlowComplete: 6 | - runScript: "103_teardown.js" 7 | - inputText: "test2" 8 | --- 9 | - tapOn: 10 | point: 100,200 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/103_setup.js: -------------------------------------------------------------------------------- 1 | console.log('setup'); -------------------------------------------------------------------------------- /maestro-test/src/test/resources/103_teardown.js: -------------------------------------------------------------------------------- 1 | console.log('teardown'); -------------------------------------------------------------------------------- /maestro-test/src/test/resources/104_on_flow_start_complete_hooks_flow_failed.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - inputText: "test1" 4 | onFlowComplete: 5 | - inputText: "test2" 6 | --- 7 | - assertVisible: 8 | id: "element_id" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/105_on_flow_start_complete_when_js_output_set.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - runScript: "105_setup.js" 4 | onFlowComplete: 5 | - runScript: "105_teardown.js" 6 | - evalScript: ${ console.log(output.teardown_result); } 7 | --- 8 | - evalScript: ${ console.log(output.setup_result); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/105_setup.js: -------------------------------------------------------------------------------- 1 | output.setup_result = 'setup'; -------------------------------------------------------------------------------- /maestro-test/src/test/resources/105_teardown.js: -------------------------------------------------------------------------------- 1 | output.teardown_result = 'teardown'; -------------------------------------------------------------------------------- /maestro-test/src/test/resources/106_on_flow_start_complete_when_js_output_set_subflows.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runFlow: "106_subflow.yaml" 4 | - evalScript: ${ console.log(output.setup_subflow_result); } 5 | - evalScript: ${ console.log(output.teardown_subflow_result); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/106_setup.js: -------------------------------------------------------------------------------- 1 | output.setup_subflow_result = 'setup subflow'; -------------------------------------------------------------------------------- /maestro-test/src/test/resources/106_subflow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - runScript: "106_setup.js" 4 | onFlowComplete: 5 | - runScript: "106_teardown.js" 6 | --- 7 | - evalScript: ${ console.log('subflow'); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/106_teardown.js: -------------------------------------------------------------------------------- 1 | output.teardown_subflow_result = 'teardown subflow'; -------------------------------------------------------------------------------- /maestro-test/src/test/resources/107_define_variables_command_before_hooks.yaml: -------------------------------------------------------------------------------- 1 | appId: ${MAESTRO_APP_ID} 2 | env: 3 | MAESTRO_APP_ID: com.example.app 4 | onFlowStart: 5 | - launchApp: 6 | appId: ${MAESTRO_APP_ID} 7 | --- 8 | - evalScript: ${ console.log(MAESTRO_APP_ID); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/108_failed_start_hook.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - evalScript: ${ console.log('on start'); } 4 | - assertTrue: ${1-1} 5 | onFlowComplete: 6 | - evalScript: ${ console.log('on complete'); } 7 | --- 8 | - evalScript: ${ console.log('main flow'); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/109_failed_complete_hook.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | onFlowStart: 3 | - evalScript: ${ console.log('on start'); } 4 | onFlowComplete: 5 | - evalScript: ${ console.log('on complete'); } 6 | - assertTrue: ${1-1} 7 | --- 8 | - evalScript: ${ console.log('main flow'); } -------------------------------------------------------------------------------- /maestro-test/src/test/resources/110_add_media_device.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.id 2 | --- 3 | - addMedia: 4 | - "./media/abc.png" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/111_add_multiple_media.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.id 2 | --- 3 | - addMedia: 4 | - "./media/abc.png" 5 | - "./media/abc.png" 6 | - "./media/abc.png" 7 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/112_scroll_until_visible_center.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scrollUntilVisible: 4 | centerElement: true 5 | element: 6 | text: "Test" 7 | speed: 100 8 | visibilityPercentage: 100 9 | direction: DOWN 10 | timeout: 10 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/113_tap_on_element_settle_timeout.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: "The time is.*" 5 | waitToSettleTimeoutMs: 100 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/114_child_of_selector.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - assertVisible: 4 | text: "child_id" 5 | childOf: 6 | text: "parent_id_1" 7 | - assertNotVisible: 8 | text: "child_id" 9 | childOf: 10 | text: "parent_id_3" -------------------------------------------------------------------------------- /maestro-test/src/test/resources/115_airplane_mode.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - setAirplaneMode: enabled 4 | - setAirplaneMode: disabled 5 | - toggleAirplaneMode 6 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/116_kill_app.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - killApp 4 | - killApp: another.app 5 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/117_scroll_until_visible_speed.js: -------------------------------------------------------------------------------- 1 | output.speed = { 2 | slow: 40 3 | } 4 | 5 | output.timeout = { 6 | slow: 20000 7 | } 8 | 9 | output.element = { 10 | id: "maestro" 11 | } 12 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/117_scroll_until_visible_speed.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - runScript: 117_scroll_until_visible_speed.js 4 | - scrollUntilVisible: 5 | element: 6 | id: ${output.element.id} 7 | direction: DOWN 8 | speed: ${output.speed.slow} 9 | timeout: ${output.timeout.slow} -------------------------------------------------------------------------------- /maestro-test/src/test/resources/118_scroll_until_visible_negative.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - scrollUntilVisible: 4 | element: 5 | id: "maestro" 6 | direction: DOWN 7 | speed: 110 8 | timeout: -200 -------------------------------------------------------------------------------- /maestro-test/src/test/resources/119_retry_commands.yaml: -------------------------------------------------------------------------------- 1 | appId: com.other.app 2 | --- 3 | - retry: 4 | maxRetries: 3 5 | commands: 6 | - scroll 7 | - tapOn: 8 | text: Button 9 | waitToSettleTimeoutMs: 40 10 | - scroll 11 | -------------------------------------------------------------------------------- /maestro-test/src/test/resources/120_tap_on_element_retryTapIfNoChange.yaml: -------------------------------------------------------------------------------- 1 | appId: com.example.app 2 | --- 3 | - tapOn: 4 | text: ".*button.*" 5 | retryTapIfNoChange: true -------------------------------------------------------------------------------- /maestro-test/src/test/resources/media/abc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobile-dev-inc/Maestro/9092b081c35cb420a9030cbecf282006696dd9c2/maestro-test/src/test/resources/media/abc.png -------------------------------------------------------------------------------- /maestro-utils/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro Utils 2 | POM_ARTIFACT_ID=maestro-utils 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /maestro-utils/src/main/kotlin/Collections.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | import java.io.File 4 | import java.nio.file.Path 5 | import kotlin.io.path.isRegularFile 6 | 7 | val Collection.isSingleFile get() = 8 | size == 1 && first().isDirectory().not() 9 | 10 | val Collection.isRegularFile get() = 11 | size == 1 && first().isRegularFile() 12 | -------------------------------------------------------------------------------- /maestro-utils/src/main/kotlin/DepthTracker.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | object DepthTracker { 4 | 5 | private var currentDepth: Int = 0 6 | private var maxDepth: Int = 0 7 | 8 | fun trackDepth(depth: Int) { 9 | currentDepth = depth 10 | if (currentDepth > maxDepth) { 11 | maxDepth = currentDepth 12 | } 13 | } 14 | 15 | fun getMaxDepth(): Int = maxDepth 16 | } -------------------------------------------------------------------------------- /maestro-utils/src/main/kotlin/Insight.kt: -------------------------------------------------------------------------------- 1 | package maestro.utils 2 | 3 | object CliInsights: Insights { 4 | 5 | private var insight: Insight = Insight("", Insight.Level.NONE) 6 | private val listeners = mutableListOf<(Insight) -> Unit>() 7 | 8 | override fun report(insight: Insight) { 9 | CliInsights.insight = insight 10 | listeners.forEach { it.invoke(insight) } 11 | } 12 | 13 | override fun onInsightsUpdated(callback: (Insight) -> Unit) { 14 | listeners.add(callback) 15 | } 16 | 17 | override fun unregisterListener(callback: (Insight) -> Unit) { 18 | listeners.remove(callback) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /maestro-web/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Maestro Web 2 | POM_ARTIFACT_ID=maestro-web 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /maestro-web/src/main/kotlin/maestro/web/record/VideoEncoder.kt: -------------------------------------------------------------------------------- 1 | package maestro.web.record 2 | 3 | import okio.Sink 4 | 5 | interface VideoEncoder : AutoCloseable { 6 | 7 | fun start(out: Sink) 8 | 9 | fun encodeFrame(frame: ByteArray) 10 | 11 | } -------------------------------------------------------------------------------- /maestro-web/src/main/kotlin/maestro/web/selenium/SeleniumFactory.kt: -------------------------------------------------------------------------------- 1 | package maestro.web.selenium 2 | 3 | import org.openqa.selenium.WebDriver 4 | 5 | interface SeleniumFactory { 6 | 7 | fun create(): WebDriver 8 | 9 | } -------------------------------------------------------------------------------- /recipes/googleplay/install_twitter.yaml: -------------------------------------------------------------------------------- 1 | # Note that this flow expects that you already have a 2 | # Google account on your device 3 | 4 | appId: com.android.vending 5 | name: Install Twitter 6 | --- 7 | - launchApp 8 | - tapOn: Search for apps & games 9 | - inputText: Twitter 10 | - tapOn: 11 | text: twitter 12 | index: 1 13 | - tapOn: Install 14 | -------------------------------------------------------------------------------- /recipes/nowinandroid/pick_interests.yaml: -------------------------------------------------------------------------------- 1 | appId: com.google.samples.apps.nowinandroid.demo.debug 2 | name: Pick Interests 3 | --- 4 | - launchApp: 5 | clearState: true 6 | - tapOn: Headlines 7 | - tapOn: Testing 8 | - tapOn: Done 9 | -------------------------------------------------------------------------------- /recipes/square/pos/android/signup.yaml: -------------------------------------------------------------------------------- 1 | appId: com.squareup 2 | name: Sign Up 3 | --- 4 | - launchApp 5 | - tapOn: Create account 6 | - tapOn: 7 | text: Accept all cookies 8 | optional: true 9 | - inputText: ${EMAIL} 10 | - tapOn: Continue 11 | - inputText: ${PASSWORD} 12 | - tapOn: Continue 13 | - tapOn: 14 | rightOf: I agree to.* 15 | retryTapIfNoChange: false 16 | - tapOn: Continue 17 | - tapOn: Confirm 18 | -------------------------------------------------------------------------------- /recipes/twitter/android/create_account.yaml: -------------------------------------------------------------------------------- 1 | # Gets user all the way up to OTP step in Twitter signup 2 | 3 | appId: com.twitter.android 4 | name: Create Account 5 | --- 6 | - launchApp: 7 | clearState: true 8 | - tapOn: Create account 9 | - tapOn: Skip for now 10 | - inputText: ${NAME} 11 | - tapOn: 12 | id: com.twitter.android:id/phone_or_email_field 13 | - tapOn: Use email instead 14 | - inputText: ${EMAIL} 15 | - tapOn: Date of birth 16 | - longPressOn: 2021 17 | - tapOn: Next 18 | - tapOn: Next 19 | - tapOn: Sign up 20 | -------------------------------------------------------------------------------- /recipes/twitter/android/follow.yaml: -------------------------------------------------------------------------------- 1 | appId: com.twitter.android 2 | --- 3 | - launchApp 4 | - tapOn: Search and Explore 5 | - tapOn: Search Twitter 6 | - inputText: "@mobile__dev" 7 | - tapOn: mobile.dev 8 | - tapOn: Follow 9 | - assertVisible: Following 10 | -------------------------------------------------------------------------------- /tmp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ -t 0 ]; then 6 | input="" 7 | else 8 | input=$(cat -) 9 | fi 10 | 11 | echo "Hello $input" --------------------------------------------------------------------------------