Showing preview only (3,079K chars total). Download the full file or copy to clipboard to get everything.
Repository: mobile-dev-inc/Maestro
Branch: main
Commit: f24a6f8fdcca
Files: 1166
Total size: 13.8 MB
Directory structure:
gitextract_0f8ml73h/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yaml
│ │ └── feature_request.yaml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts/
│ │ └── boot_simulator.sh
│ └── workflows/
│ ├── close-inactive-issues.yaml
│ ├── lock-closed-issues.yaml
│ ├── publish-cli.yaml
│ ├── publish-release.yaml
│ ├── publish-snapshot.yaml
│ ├── test-e2e-ios-intel.yaml
│ ├── test-e2e-prod.yaml
│ ├── test-e2e.yaml
│ ├── test.yaml
│ └── update-samples.yaml
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── .name
│ └── dictionaries/
│ └── project.xml
├── .run/
│ ├── cli-version.run.xml
│ └── cli.run.xml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASING.md
├── build.gradle.kts
├── debug.keystore
├── detekt.yml
├── e2e/
│ ├── .gitignore
│ ├── README.md
│ ├── download_apps
│ ├── install_apps
│ ├── manifest.txt
│ ├── run_tests
│ ├── update_samples
│ └── workspaces/
│ ├── setOrientation/
│ │ └── test-set-orientation-flow.yaml
│ ├── simple_web_view/
│ │ └── webview.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
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── installLocally.sh
├── 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
│ │ └── screenshot/
│ │ ├── ScreenshotService.kt
│ │ └── ScreenshotServiceTest.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
│ ├── jvm-version.jar
│ └── 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
│ │ │ │ ├── AnalyticsStateManager.kt
│ │ │ │ └── PostHogEvents.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
│ │ │ │ ├── ListCloudDevicesCommand.kt
│ │ │ │ ├── ListDevicesCommand.kt
│ │ │ │ ├── LoginCommand.kt
│ │ │ │ ├── LogoutCommand.kt
│ │ │ │ ├── McpCommand.kt
│ │ │ │ ├── PrintHierarchyCommand.kt
│ │ │ │ ├── QueryCommand.kt
│ │ │ │ ├── RecordCommand.kt
│ │ │ │ ├── StartDeviceCommand.kt
│ │ │ │ ├── StudioCommand.kt
│ │ │ │ └── TestCommand.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
│ │ │ ├── mcp/
│ │ │ │ ├── McpServer.kt
│ │ │ │ ├── README.md
│ │ │ │ └── tools/
│ │ │ │ ├── BackTool.kt
│ │ │ │ ├── CheatSheetTool.kt
│ │ │ │ ├── CheckFlowSyntaxTool.kt
│ │ │ │ ├── InputTextTool.kt
│ │ │ │ ├── InspectViewHierarchyTool.kt
│ │ │ │ ├── LaunchAppTool.kt
│ │ │ │ ├── ListDevicesTool.kt
│ │ │ │ ├── QueryDocsTool.kt
│ │ │ │ ├── RunFlowFilesTool.kt
│ │ │ │ ├── RunFlowTool.kt
│ │ │ │ ├── StartDeviceTool.kt
│ │ │ │ ├── StopAppTool.kt
│ │ │ │ ├── TakeScreenshotTool.kt
│ │ │ │ ├── TapOnTool.kt
│ │ │ │ └── ViewHierarchyFormatters.kt
│ │ │ ├── model/
│ │ │ │ ├── FlowStatus.kt
│ │ │ │ ├── RunningFlow.kt
│ │ │ │ └── TestExecutionSummary.kt
│ │ │ ├── promotion/
│ │ │ │ └── PromotionStateManager.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
│ │ │ │ ├── DependencyResolver.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
│ │ │ │ ├── WorkingDirectory.kt
│ │ │ │ └── WorkspaceUtils.kt
│ │ │ ├── view/
│ │ │ │ ├── ErrorViewUtils.kt
│ │ │ │ ├── ProgressBar.kt
│ │ │ │ ├── TestSuiteStatusView.kt
│ │ │ │ └── ViewUtils.kt
│ │ │ └── web/
│ │ │ └── WebInteractor.kt
│ │ └── resources/
│ │ ├── ai_report.css
│ │ ├── deps/
│ │ │ └── applesimutils
│ │ ├── html-detailed.css
│ │ ├── logback-test.xml
│ │ └── tailwind.config.js
│ └── test/
│ ├── kotlin/
│ │ └── maestro/
│ │ └── cli/
│ │ ├── android/
│ │ │ └── AndroidDeviceProvider.kt
│ │ ├── cloud/
│ │ │ └── CloudInteractorTest.kt
│ │ ├── command/
│ │ │ └── TestCommandTest.kt
│ │ ├── driver/
│ │ │ ├── DriverBuilderTest.kt
│ │ │ └── RealDeviceDriverTest.kt
│ │ ├── report/
│ │ │ ├── HtmlTestSuiteReporterTest.kt
│ │ │ ├── JUnitTestSuiteReporterTest.kt
│ │ │ ├── TestDebugReporterTest.kt
│ │ │ └── TestSuiteReporterTest.kt
│ │ ├── runner/
│ │ │ └── resultview/
│ │ │ └── PlainTextResultViewTest.kt
│ │ └── util/
│ │ ├── ChangeLogUtilsTest.kt
│ │ ├── DependencyResolverTest.kt
│ │ └── WorkspaceUtilsTest.kt
│ ├── mcp/
│ │ ├── README.md
│ │ ├── full-evals.yaml
│ │ ├── inspect-view-hierarchy-evals.yaml
│ │ ├── launch_app_with_env_replacement.yaml
│ │ ├── maestro-mcp.json
│ │ ├── mcp-server-config.json
│ │ ├── run_mcp_evals.sh
│ │ ├── run_mcp_tool_tests.sh
│ │ ├── setup/
│ │ │ ├── check-maestro-cli-built.sh
│ │ │ ├── download-and-install-apps.sh
│ │ │ ├── flows/
│ │ │ │ ├── launch-demo-app-ios.yaml
│ │ │ │ ├── launch-safari-ios.yaml
│ │ │ │ ├── setup-wikipedia-search-android.yaml
│ │ │ │ ├── setup-wikipedia-search-ios.yaml
│ │ │ │ └── verify-ready-state.yaml
│ │ │ ├── launch-simulator.sh
│ │ │ └── setup_and_run_eval.sh
│ │ ├── tool-tests-with-device.yaml
│ │ └── tool-tests-without-device.yaml
│ └── resources/
│ ├── apps/
│ │ └── web-manifest.json
│ ├── location/
│ │ └── assert_multiple_locations.yaml
│ ├── travel/
│ │ └── assert_travel_command.yaml
│ └── workspaces/
│ ├── cloud_test/
│ │ ├── android/
│ │ │ └── flow.yaml
│ │ ├── ios/
│ │ │ └── flow.yaml
│ │ ├── tagged/
│ │ │ ├── regression.yaml
│ │ │ └── smoke.yaml
│ │ └── web/
│ │ └── flow.yaml
│ └── test_command_test/
│ ├── 00_mixed_web_mobile_flow_tests/
│ │ ├── mobileflow.yaml
│ │ ├── mobileflow2.yaml
│ │ ├── webflow.yaml
│ │ └── webflow2.yaml
│ ├── 01_web_only/
│ │ ├── webflow.yaml
│ │ └── webflow2.yaml
│ ├── 02_mobile_only/
│ │ ├── mobileflow1.yaml
│ │ └── mobileflow2.yaml
│ ├── 03_mixed_with_config_execution_order/
│ │ ├── config.yaml
│ │ └── subFolder/
│ │ ├── mobileflow.yaml
│ │ ├── mobileflow2.yaml
│ │ ├── webflow.yaml
│ │ └── webflow2.yaml
│ └── 04_web_only_with_config_execution_order/
│ ├── config.yaml
│ └── subFolder/
│ ├── mobileflow.yaml
│ ├── mobileflow2.yaml
│ ├── webflow.yaml
│ └── webflow2.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
│ │ │ ├── OnDeviceElementQuery.kt
│ │ │ ├── OnDeviceElementQueryResult.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
│ │ │ ├── auth/
│ │ │ │ └── ApiKey.kt
│ │ │ ├── debuglog/
│ │ │ │ ├── DebugLogStore.kt
│ │ │ │ └── LogConfig.kt
│ │ │ ├── device/
│ │ │ │ ├── Device.kt
│ │ │ │ ├── DeviceError.kt
│ │ │ │ ├── DeviceOrientation.kt
│ │ │ │ ├── DeviceService.kt
│ │ │ │ ├── DeviceSpec.kt
│ │ │ │ ├── Platform.kt
│ │ │ │ ├── locale/
│ │ │ │ │ ├── AndroidLocale.kt
│ │ │ │ │ ├── DeviceLocale.kt
│ │ │ │ │ ├── IosLocale.kt
│ │ │ │ │ ├── LocaleValidationException.kt
│ │ │ │ │ └── WebLocale.kt
│ │ │ │ ├── serialization/
│ │ │ │ │ ├── DeviceLocaleSerializer.kt
│ │ │ │ │ └── DeviceSpecModule.kt
│ │ │ │ └── util/
│ │ │ │ ├── AndroidEnvUtils.kt
│ │ │ │ ├── AvdDevice.kt
│ │ │ │ ├── CommandLineUtils.kt
│ │ │ │ ├── EnvUtils.kt
│ │ │ │ ├── PrintUtils.kt
│ │ │ │ ├── SimctlList.kt
│ │ │ │ └── SystemInfo.kt
│ │ │ ├── drivers/
│ │ │ │ ├── AndroidDriver.kt
│ │ │ │ ├── CdpWebDriver.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/
│ │ ├── FiltersTest.kt
│ │ ├── PointTest.kt
│ │ ├── UiElementTest.kt
│ │ ├── android/
│ │ │ ├── AndroidAppFilesTest.kt
│ │ │ ├── AndroidLaunchArgumentsTest.kt
│ │ │ └── chromedevtools/
│ │ │ └── AndroidWebViewHierarchyClientTest.kt
│ │ ├── device/
│ │ │ ├── DeviceServiceTest.kt
│ │ │ ├── DeviceSpecTest.kt
│ │ │ └── serialization/
│ │ │ └── DeviceSpecSerializationTest.kt
│ │ ├── ios/
│ │ │ └── MockXCTestInstaller.kt
│ │ ├── locale/
│ │ │ └── DeviceLocaleTest.kt
│ │ ├── utils/
│ │ │ ├── HttpUtilsTest.kt
│ │ │ └── StringUtilsTest.kt
│ │ └── xctestdriver/
│ │ └── XCTestDriverClientTest.kt
│ └── resources/
│ └── logback-test.xml
├── maestro-ios/
│ ├── README.md
│ ├── build.gradle.kts
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── ios/
│ ├── IOSDeviceErrors.kt
│ ├── LocalIOSDevice.kt
│ ├── devicectl/
│ │ └── DeviceControlIOSDevice.kt
│ └── xctest/
│ └── XCTestIOSDevice.kt
├── 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
│ │ │ │ ├── SetOrientationRequest.kt
│ │ │ │ ├── SetPermissionsRequest.kt
│ │ │ │ ├── SwipeRequest.kt
│ │ │ │ ├── TerminateAppRequest.kt
│ │ │ │ ├── TouchRequest.kt
│ │ │ │ └── ViewHierarchyRequest.kt
│ │ │ └── installer/
│ │ │ ├── IOSBuildProductsExtractor.kt
│ │ │ ├── LocalXCTestInstaller.kt
│ │ │ └── XCTestInstaller.kt
│ │ └── resources/
│ │ ├── driver-iPhoneSimulator/
│ │ │ └── maestro-driver-ios-config.xctestrun
│ │ ├── driver-iphoneos/
│ │ │ └── maestro-driver-ios-config.xctestrun
│ │ └── screenrecord.sh
│ └── test/
│ └── kotlin/
│ ├── DeviceCtlResponseTest.kt
│ ├── IOSBuildProductsExtractorTest.kt
│ └── IOSLaunchArgumentsTest.kt
├── maestro-ios-xctest-runner/
│ ├── .gitignore
│ ├── MaestroDriverLib/
│ │ ├── Info.plist
│ │ ├── Package.swift
│ │ ├── Sources/
│ │ │ └── MaestroDriverLib/
│ │ │ ├── Helpers/
│ │ │ │ └── PermissionButtonFinder.swift
│ │ │ ├── MaestroDriverLib.swift
│ │ │ └── Models/
│ │ │ ├── AXElement.swift
│ │ │ ├── AXFrame.swift
│ │ │ ├── ElementType.swift
│ │ │ └── PermissionValue.swift
│ │ └── Tests/
│ │ └── MaestroDriverLibTests/
│ │ ├── AXElementTests.swift
│ │ ├── AXFrameTests.swift
│ │ └── PermissionButtonFinderTests.swift
│ ├── build-maestro-ios-runner-all.sh
│ ├── build-maestro-ios-runner.sh
│ ├── 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-ios.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm/
│ │ │ └── Package.resolved
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ ├── maestro-driver-ios.xcscheme
│ │ └── maestro-driver-iosTests.xcscheme
│ ├── maestro-driver-iosTests/
│ │ ├── Info.plist
│ │ ├── SnapshotParametersTests.swift
│ │ └── maestro-driver-iosTests-Bridging-Header.h
│ ├── 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
│ │ │ │ ├── SetOrientationHandler.swift
│ │ │ │ ├── SetPermissionsHandler.swift
│ │ │ │ ├── StatusHandler.swift
│ │ │ │ ├── SwipeRouteHandler.swift
│ │ │ │ ├── SwipeRouteHandlerV2.swift
│ │ │ │ ├── TerminateAppHandler.swift
│ │ │ │ ├── TouchRouteHandler.swift
│ │ │ │ └── ViewHierarchyHandler.swift
│ │ │ ├── Helpers/
│ │ │ │ ├── AppError.swift
│ │ │ │ ├── ScreenSizeHelper.swift
│ │ │ │ ├── SystemPermissionHelper.swift
│ │ │ │ └── TextInputHelper.swift
│ │ │ ├── Models/
│ │ │ │ ├── AXElement.swift
│ │ │ │ ├── DeviceInfoResponse.swift
│ │ │ │ ├── EraseTextRequest.swift
│ │ │ │ ├── GetRunningAppRequest.swift
│ │ │ │ ├── InputTextRequest.swift
│ │ │ │ ├── KeyboardHandlerRequest.swift
│ │ │ │ ├── KeyboardHandlerResponse.swift
│ │ │ │ ├── LaunchAppRequest.swift
│ │ │ │ ├── PressButtonRequest.swift
│ │ │ │ ├── PressKeyRequest.swift
│ │ │ │ ├── SetOrientationRequest.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
│ │ ├── maestro_driver_iosUITests.swift
│ │ └── maestro_driver_iosUITestsLaunchTests.swift
│ ├── run-maestro-ios-runner.sh
│ └── test-maestro-ios-runner.sh
├── 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
│ │ ├── util/
│ │ │ ├── AppMetadataAnalyzer.kt
│ │ │ └── ElementCoordinateUtil.kt
│ │ ├── validation/
│ │ │ ├── AppValidationException.kt
│ │ │ ├── AppValidator.kt
│ │ │ ├── WorkspaceValidationException.kt
│ │ │ └── WorkspaceValidator.kt
│ │ ├── workspace/
│ │ │ ├── ExecutionOrderPlanner.kt
│ │ │ ├── Filters.kt
│ │ │ ├── WorkspaceExecutionPlanner.kt
│ │ │ ├── WorkspaceValidator.kt
│ │ │ └── YamlCommandsPathValidator.kt
│ │ └── yaml/
│ │ ├── MaestroFlowParser.kt
│ │ ├── YamlAction.kt
│ │ ├── YamlAddMedia.kt
│ │ ├── YamlAssertNoDefectsWithAI.kt
│ │ ├── YamlAssertScreenshot.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
│ │ ├── YamlSetClipboard.kt
│ │ ├── YamlSetLocation.kt
│ │ ├── YamlSetOrientation.kt
│ │ ├── YamlSetPermissions.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
│ │ ├── util/
│ │ │ ├── AppMetadataAnalyzerTest.kt
│ │ │ └── ElementCoordinateUtilTest.kt
│ │ ├── validation/
│ │ │ ├── AppValidatorTest.kt
│ │ │ └── WorkspaceValidatorTest.kt
│ │ ├── workspace/
│ │ │ ├── ExecutionOrderPlannerTest.kt
│ │ │ ├── WorkspaceExecutionPlannerErrorsTest.kt
│ │ │ ├── WorkspaceExecutionPlannerTest.kt
│ │ │ └── WorkspaceValidatorTest.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_labels.yaml
│ │ ├── 023_runScript_test.js
│ │ ├── 024_string_non_string_commands.yaml
│ │ ├── 025_killApp.yaml
│ │ ├── 027_waitToSettleTimeoutMs.yaml
│ │ ├── 028_inputRandomAnimal.yaml
│ │ ├── 029_command_descriptions.yaml
│ │ ├── 029_double_tap_element_relative.yaml
│ │ ├── 029_element_relative_tap_css.yaml
│ │ ├── 029_element_relative_tap_enabled.yaml
│ │ ├── 029_element_relative_tap_id_absolute.yaml
│ │ ├── 029_element_relative_tap_index.yaml
│ │ ├── 029_element_relative_tap_label.yaml
│ │ ├── 029_element_relative_tap_size.yaml
│ │ ├── 029_element_relative_tap_text_percentage.yaml
│ │ ├── 029_element_relative_tap_with_repeat.yaml
│ │ ├── 029_pure_point_tap.yaml
│ │ ├── 029_regular_element_tap.yaml
│ │ ├── 030_setPermissions.yaml
│ │ ├── 031_setOrientation.yaml
│ │ └── 032_setOrientation_error.yaml
│ ├── 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
│ ├── 013_execution_order/
│ │ ├── config.yaml
│ │ ├── flowA.yaml
│ │ ├── flowB.yaml
│ │ ├── flowCWithCustomName.yaml
│ │ └── flowD.yaml
│ ├── 014_config_not_null/
│ │ ├── config/
│ │ │ └── another_config.yaml
│ │ ├── config.yaml
│ │ ├── flowA.yaml
│ │ └── flowB.yaml
│ ├── 015_workspace_cloud_configs/
│ │ ├── 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
│ ├── 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
│ └── workspace_validator_flow.yaml
├── 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
│ └── test/
│ └── kotlin/
│ └── maestro/
│ └── orchestra/
│ ├── CommandsTest.kt
│ ├── ElementSelectorTest.kt
│ └── util/
│ └── EnvTest.kt
├── 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
│ ├── build.gradle
│ ├── package.json
│ ├── postcss.config.js
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── App.tsx
│ │ ├── api/
│ │ │ └── api.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
│ │ └── style/
│ │ └── index.css
│ ├── 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/
│ │ ├── DeepestMatchingElementTest.kt
│ │ ├── FlowControllerTest.kt
│ │ ├── 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
│ ├── 098_runscript_conditionals_eager.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
│ ├── 122_pause_resume.yaml
│ ├── 123_pause_resume_preserves_js_engine.yaml
│ ├── 124_cancellation_during_flow_execution.yaml
│ ├── 125_assert_by_css.yaml
│ ├── 126_set_orientation.yaml
│ ├── 126_set_orientation_with_env.yaml
│ ├── 127_env_vars_isolation_graaljs.yaml
│ ├── 127_env_vars_isolation_rhinojs.yaml
│ ├── 127_script.js
│ ├── 127_script_mutate_env_var.js
│ ├── 128_datafaker_graaljs.yaml
│ ├── 129_text_and_id.yaml
│ ├── 130_text_and_index.yaml
│ ├── 131_setPermissions.yaml
│ ├── 132_repeat_while_timeout.yaml
│ ├── 133_setClipboard.yaml
│ ├── 134_take_screenshot_with_path.yaml
│ ├── 135_screen_recording_with_path.yaml
│ ├── 136_js_http_multi_part_requests.yaml
│ ├── 137_shard_device_env_vars.yaml
│ ├── 138_take_cropped_screenshot.yaml
│ └── script/
│ └── multipart_request_file_script.js
├── 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
│ │ ├── TempFileHandler.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/
│ ├── cdp/
│ │ └── CdpClient.kt
│ ├── record/
│ │ ├── JcodecVideoEncoder.kt
│ │ ├── VideoEncoder.kt
│ │ └── WebScreenRecorder.kt
│ └── selenium/
│ ├── ChromeSeleniumFactory.kt
│ └── SeleniumFactory.kt
├── scripts/
│ └── install.sh
├── settings.gradle.kts
└── tmp.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# Copied from https://youtrack.jetbrains.com/issue/FL-15599/No-way-of-disabling-Java-Kotlin-wildcard-imports
[*.java]
ij_java_class_count_to_use_import_on_demand = 1024
ij_java_names_count_to_use_import_on_demand = 1024
[*.kt]
ij_kotlin_name_count_to_use_star_import = 1024
ij_kotlin_name_count_to_use_star_import_for_members = 1024
================================================
FILE: .gitattributes
================================================
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Report a bug
description: You have a problem with Maestro.
body:
- type: markdown
attributes:
value: >
### Thank you for using Maestro!
Before creating a new issue, please first search the [existing issues]
and make sure it hasn't been reported before.
If you are sure that you have found a bug that hasn't been reported yet,
or if our documentation doesn't have an answer to what you're looking
for, then please fill out this template.
---
[existing issues]: https://github.com/mobile-dev-inc/maestro
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: |
Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues and didn't find mine.
required: true
- type: textarea
validations:
required: true
attributes:
label: Steps to reproduce
description: >
Create a [minimal, reproducible example] that:
1. Demonstrates the problem
2. Explains how to reproduce the problem with detailed step-by-step
instructions
**In addition to the detailed step-by-step instructions**, you must include
information about the device you're encountering the issue on
(e.g. physical Android or iOS simulator), and the OS version
(e.g. Android 9, Android 14 with Play Services, or iOS 18).
**It's critical that you include your test flow file**. In general, try
to include as much additional details as possible to make it easier for
us to understand and fix the problem. Screenshots and videos are
welcome.
> [!TIP]
> If you're recording a video on Android, we recommend enabling these options to show taps and gestures:
> ```
> adb shell settings put system show_touches 1
> adb shell settings put system pointer_location 1
> ```
> [!WARNING]
> Issues that cannot be reproduced are much more likely to be closed.
[minimal, reproducible example]: https://stackoverflow.com/help/minimal-reproducible-example
placeholder: |
Example good reproduction steps:
1. Clone https://github.com/your_username/your_repo_with_bug and `cd` into it
2. Start Android emulator (Pixel 7, API 34, with Google Play)
3. Build app: `./gradlew :app:assembleDebug`
4. Run the problematic flow and see it fail: `maestro test .maestro/flow.yaml`
- type: textarea
validations:
required: true
attributes:
label: Actual results
description: Please explain what is happening.
- type: textarea
validations:
required: true
attributes:
label: Expected results
description: Please explain what you expect to happen.
- type: textarea
validations:
required: true
attributes:
label: About app
description: >
Include information about the app you're testing:
- Is this an open source or closed source project?
- If open source, please share link to the repo
- If closed source, please share app binary and/or an isolated, reproducible sample
- Is this a native or cross-platform app?
- Framework used to build the app
- e.g. UIKit, SwiftUI, Android Views, Compose, React Native, or NativeScript
- If applicable, version of the framework (e.g. Flutter 3.22.0, Compose 1.62.0)
- If applicable, minimum and target Android SDK/iOS version (e.g. minSdk 21, targetSdk 34)
placeholder: |
The info you enter here will make it easier to resolve your issue. For example:
- This is an open source app, available at https://github.com/wikimedia/wikipedia-ios
- It's a native iOS app. There is also an Android version, but the issue is only on iOS.
- It's built mainly with UIKit, minimum iOS deployment target is 13.0
- type: textarea
validations:
required: true
attributes:
label: About environment
description: |
Include information about machine you're running Maestro on:
- Java version (e.g. OpenJDK 17, Eclipse Temurin 8). To find it, run `java -version`
- OS and its version (e.g. macOS 13.1 Ventura, Ubuntu 24.04, Arch (btw))
- Processor architecture (x86_64, arm64)
placeholder: |
The info you enter here will make it easier to resolve your issue. For example:
- I'm on M1 MacBook Air, with macOS 14.5 Sonoma and Xcode 15.4.
- type: textarea
attributes:
label: Logs
description: >
Include the full logs of the command you're running. The zip files
created with `maestro bugreport` can be uploaded here as well.
Things to keep in mind:
- If you're running more than single command, include its logs in a
separate backticks block.
- If the logs are too large to be uploaded to Github, you may upload
them as a `txt` file or use online tools like https://pastebin.com and
share the link. Just make sure the link won't break in the future.
- **Do not upload screenshots of text**. Instead, use code blocks or the
above mentioned ways to upload logs.
- **Make sure the logs are well formatted**. If you post garbled logs, it
will make it harder for us to help you.
value: |
<details>
<summary>Logs</summary>
```
<!-- Replace this line with your logs. *DO NOT* remove the backticks! -->
```
</details>
- type: input
validations:
required: true
attributes:
label: Maestro version
description: >
Provide version of Maestro CLI where the problem occurs. Run
`maestro --version` to find out.
placeholder: 1.36.0
- type: dropdown
validations:
required: true
attributes:
label: How did you install Maestro?
options:
- install script (https://get.maestro.mobile.dev)
- Homebrew
- built from source (please include commit hash in the text area below)
- other (please specify in the text area below)
default: 0
- type: textarea
validations:
required: false
attributes:
label: Anything else?
description: >
Links? Other issues? StackOverflow threads? Anything that will give us
more context about the issue you are encountering will be helpful.
> [!TIP]
> You can attach images or log files by clicking this area to highlight it and then dragging files in.
- type: markdown
attributes:
value: >
Now that you've filled all the required information above, you're ready
to submit the issue.
**Please check what your issue looks like after creating it**. If it
contains garbled code and logs, please take some time to adjust it so
it's easier to parse.
**Try reading your issue as if you were seeing it for the first time**.
Does it read well? Is it easy to understand? Is the formatting correct?
If not, please improve it.
Thank you for helping us improve Maestro and keeping our issue tracker
in a good shape!
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
name: Suggest a feature
description: You want to share a new idea to improve Maestro.
body:
- type: markdown
attributes:
value: >
### Thank you for using Maestro!
We can't wait to hear your idea!
First though, please search the [existing issues] to see if an issue
already exists for the feature you need. Maybe someone already did the
job for you and you don't need to fill this template.
---
If you are sure that the feature you want to suggest hasn't been
requested before, or if our documentation doesn't have an answer to what
you're looking for, then fill out the template below. Please bear in
mind that duplicates and insufficiently described feature requests will
be closed.
[existing issues]: https://github.com/mobile-dev-inc/maestro/issues
- type: textarea
attributes:
label: Use case
description: >
Please tell us more about the use case you have that led to you wanting
this new feature.
Is your feature request related to a problem? Please give a clear and
concise description of what the problem is. This will help avoid the
[XY problem].
Describe the alternative solutions you've considered and the tradeoffs
they come with. The more context you can provide, the better.
[XY problem]: https://en.wikipedia.org/wiki/XY_problem
validations:
required: true
- type: textarea
attributes:
label: Proposal
description: >
Briefly but precisely describe what the new feature should look like
from the user perspective.
Consider attaching something showing what you are imagining:
* code samples (maybe you already know )
* API design ideas (e.g. of new YAML commands)
validations:
required: true
- type: textarea
validations:
required: false
attributes:
label: Anything else?
description: >
Links? Other issues? StackOverflow threads? Anything that will give us
more context about this feature request will be helpful.
> [!TIP]
> You can attach images or other files by clicking this area to highlight it and then dragging files in.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Proposed changes
copilot:summary
## Testing
<!--- Please describe how you tested your changes. -->
> **Does this need e2e tests?** Please consider contributing them to the [demo app](https://github.com/mobile-dev-inc/demo_app) repository.
## Issues fixed
================================================
FILE: .github/scripts/boot_simulator.sh
================================================
#!/bin/bash
# Specify the device type and runtime as per your requirements
DEVICE_TYPE="${DEVICE_TYPE:-iPhone 15 Pro}"
RUNTIME="${RUNTIME:-iOS18.6}"
# Create a unique identifier for the new simulator to avoid naming conflicts
SIMULATOR_NAME="Simulator_$(uuidgen)"
echo "Creating a new iOS simulator: $SIMULATOR_NAME (Device: $DEVICE_TYPE, Runtime: $RUNTIME)"
# Create the simulator
simulator_id=$(xcrun simctl create "$SIMULATOR_NAME" "$DEVICE_TYPE" $RUNTIME)
echo "Simulator ID: $simulator_id created."
# Boot the simulator
echo "Booting the simulator..."
xcrun simctl boot "$simulator_id"
# Wait for the simulator to be fully booted
while true; do
# Check the current state of the simulator
state=$(xcrun simctl list | grep "$simulator_id" | grep -o "Booted" || true)
if [ "$state" == "Booted" ]; then
echo "Simulator $SIMULATOR_NAME is now ready."
break
else
echo "Waiting for the simulator to be ready..."
sleep 5 # sleep for 5 seconds before checking again to avoid spamming
fi
done
================================================
FILE: .github/workflows/close-inactive-issues.yaml
================================================
# Close issues that have had "waiting for customer response" label for too long.
# This workflow is based on a very similar one from Flutter
# https://github.com/flutter/flutter/blob/3.22.0/.github/workflows/no-response.yaml
name: close inactive issues
on:
issue_comment:
types: [created]
schedule:
- cron: '0 */6 * * *'
permissions:
issues: write
jobs:
main:
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09
with:
token: ${{ github.token }}
closeComment: >
Without additional information, we can't resolve this issue. We're
therefore reluctantly going to close it.
Feel free to open a new issue with all the required information
provided, including a [minimal, reproducible sample]. When creating
a new issue, please make sure to diligently fill out the issue
template.
Thank you for your contribution to our open-source community!
[minimal, reproducible sample]: https://stackoverflow.com/help/minimal-reproducible-example
# Number of days of inactivity before an issue is closed.
daysUntilClose: 14
# Only issues with this label will be closed (if they are inactive).
responseRequiredLabel: waiting for customer response
================================================
FILE: .github/workflows/lock-closed-issues.yaml
================================================
# Lock closed issues that have been inactive for a while.
# This workflow is copied from Flutter
# https://github.com/flutter/flutter/blob/3.22.0/.github/workflows/lock.yaml
name: lock closed issues
permissions:
issues: write
on:
schedule:
- cron: '0 */6 * * *'
jobs:
lock:
permissions:
issues: write
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- uses: dessant/lock-threads@v5
with:
process-only: issues
github-token: ${{ github.token }}
# Number of days of inactivity before a closed issue is locked.
issue-inactive-days: 7
issue-comment: >
This issue has been automatically locked since there has not been
any recent activity after it was closed. If you are still
experiencing a similar problem, please file a new issue. Make
sure to follow the template and provide all the information
necessary to reproduce the issue.
Thank you for helping keep us our issue tracker clean!
================================================
FILE: .github/workflows/publish-cli.yaml
================================================
name: Publish CLI
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
cache: gradle
- name: Publish CLI
run: ./gradlew :maestro-cli:jreleaserFullRelease --no-daemon --no-parallel
env:
JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }}
- name: Print jReleaser log
if: always()
run: cat maestro-cli/build/jreleaser/trace.log
================================================
FILE: .github/workflows/publish-release.yaml
================================================
name: Publish Release
on:
workflow_dispatch:
push:
tags:
- 'v*'
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }}
jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
cache: gradle
- name: Retrieve version
run: |
echo "VERSION_NAME=$(cat gradle.properties | grep -w "VERSION_NAME" | cut -d'=' -f2)" >> $GITHUB_ENV
- name: Upload Maestro utils release
run: ./gradlew clean :maestro-utils:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro client release
run: ./gradlew clean :maestro-client:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro ios release
run: ./gradlew clean :maestro-ios:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro orchestra release
run: ./gradlew clean :maestro-orchestra:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro Orchestra Models release
run: ./gradlew clean :maestro-orchestra-models:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro Proto release
run: ./gradlew clean :maestro-proto:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro XCUiTest Driver
run: ./gradlew clean :maestro-ios-driver:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro AI release
run: ./gradlew clean :maestro-ai:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro Web release
run: ./gradlew clean :maestro-web:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
- name: Upload Maestro CLI release
run: ./gradlew clean :maestro-cli:publishToMavenCentral --no-daemon --no-parallel
if: ${{ !endsWith(env.VERSION_NAME, '-SNAPSHOT') }}
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ env.ORG_GRADLE_PROJECT_mavenCentralUsername }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ env.ORG_GRADLE_PROJECT_mavenCentralPassword }}
================================================
FILE: .github/workflows/publish-snapshot.yaml
================================================
name: Publish Snapshot
on:
workflow_dispatch:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
cache: gradle
- name: Retrieve version
run: |
echo "VERSION_NAME=$(cat gradle.properties | grep -w "VERSION_NAME" | cut -d'=' -f2)" >> $GITHUB_ENV
- name: Upload Maestro utils release
run: ./gradlew clean :maestro-utils:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro client release
run: ./gradlew clean :maestro-client:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro ios release
run: ./gradlew clean :maestro-ios:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro orchestra release
run: ./gradlew clean :maestro-orchestra:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro Orchestra Models release
run: ./gradlew clean :maestro-orchestra-models:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro Proto release
run: ./gradlew clean :maestro-proto:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro XCUiTest Driver
run: ./gradlew clean :maestro-ios-driver:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro AI
run: ./gradlew clean :maestro-ai:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
- name: Upload Maestro web
run: ./gradlew clean :maestro-web:publish --no-daemon --no-parallel
if: endsWith(env.VERSION_NAME, '-SNAPSHOT')
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
================================================
FILE: .github/workflows/test-e2e-ios-intel.yaml
================================================
name: Test E2E on iOS (Intel)
on:
workflow_dispatch:
jobs:
build:
name: Build on Java ${{ matrix.java-version }}
runs-on: macos-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
java-version: [17]
steps:
- name: Clone repository
uses: actions/checkout@v6
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: ${{ matrix.java-version }}
cache: gradle
# Do not rebuild this - let's test the one that is in the repo
#- name: Build xctest-runner
# run: ./maestro-ios-xctest-runner/build-maestro-ios-runner.sh | xcbeautify
- name: Build Maestro CLI
run: ./gradlew :maestro-cli:distZip
- name: Upload zipped Maestro CLI artifact
uses: actions/upload-artifact@v6
with:
name: maestro-cli-jdk${{ matrix.java-version }}-run_id${{ github.run_id }}
path: maestro-cli/build/distributions/maestro.zip
retention-days: 1
- name: Upload build/Products to artifacts
uses: actions/upload-artifact@v6
with:
name: build__Products-jdk${{ matrix.java-version }}
path: build/Products
retention-days: 1
test-ios:
name: Test on iOS
runs-on: macos-15-intel
needs: build
timeout-minutes: 120
env:
MAESTRO_DRIVER_STARTUP_TIMEOUT: 240000 # 240s
MAESTRO_CLI_LOG_PATTERN_CONSOLE: '%d{HH:mm:ss.SSS} [%5level] %logger.%method: %msg%n'
steps:
- name: Clone repository (only needed for the e2e directory)
uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
- name: Download artifacts
uses: actions/download-artifact@v7
with:
name: maestro-cli-jdk17-run_id${{ github.run_id }}
- name: Add Maestro CLI executable to PATH
run: |
unzip maestro.zip -d maestro_extracted
echo "$PWD/maestro_extracted/maestro/bin" >> $GITHUB_PATH
- name: Check if Maestro CLI executable starts up
run: |
maestro --help
maestro --version
- name: Boot Simulator
run: |
xcrun simctl list runtimes
export RUNTIME="iOS18.5"
export DEVICE_TYPE="iPhone 16"
./.github/scripts/boot_simulator.sh
- name: Download apps
working-directory: ${{ github.workspace }}/e2e
run: ./download_apps ios
- name: Install apps
working-directory: ${{ github.workspace }}/e2e
run: ./install_apps ios
- name: Start screen recording
run: |
xcrun simctl io booted recordVideo --codec h264 ~/screenrecord.mp4 &
echo $! > ~/screenrecord.pid
- name: Run tests
working-directory: ${{ github.workspace }}/e2e
timeout-minutes: 120
run: ./run_tests ios
- name: Stop screen recording
if: success() || failure()
run: kill -SIGINT "$(cat ~/screenrecord.pid)"
- name: Upload ~/.maestro artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-root-dir-ios
path: ~/.maestro
retention-days: 7
include-hidden-files: true
- name: Upload xctest runner logs
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: xctest_runner_logs
path: ~/Library/Logs/maestro/xctest_runner_logs
retention-days: 7
include-hidden-files: true
- name: Upload screen recording of Simulator
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-screenrecord-ios.mp4
path: ~/screenrecord.mp4
retention-days: 7
================================================
FILE: .github/workflows/test-e2e-prod.yaml
================================================
name: Test E2E (prod)
on:
workflow_dispatch:
schedule:
- cron: '0 * * * *'
jobs:
test-cloud-production:
# This job is copied from "e2e-production" in mobile-dev-inc/monorepo.
# We want it here so open-source users can also have some visibility into it.
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
- name: Install Maestro
run: |
curl -Ls --retry 3 --retry-all-errors "https://get.maestro.mobile.dev" | bash
echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
- name: Print Maestro version
run: maestro --version
- name: Download samples
run: maestro download-samples
- name: Run iOS test
run: |
maestro cloud \
--apiKey ${{ secrets.E2E_MOBILE_DEV_API_KEY }} \
--timeout 180 \
--fail-on-cancellation \
--include-tags=advanced \
samples/sample.zip samples
- name: Run Android test
run: |
maestro cloud \
--apiKey ${{ secrets.E2E_MOBILE_DEV_API_KEY }} \
--fail-on-cancellation \
--include-tags advanced \
samples/sample.apk samples
- name: Send Slack message
if: failure()
run: |
curl --request POST \
--url "${{ secrets.E2E_SLACK_WEBHOOK_URL }}" \
--header 'Content-Type: application/json' \
--data '{
"text": "🚨 *Maestro E2E Test Failed*\nStatus: 'Failure'\nRun: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View details>"
}'
# - name: Trigger alert on failure
# if: ${{ false }}
# # if: failure()
# run: |
# curl --request POST \
# --url "https://events.pagerduty.com/v2/enqueue" \
# --header 'Content-Type: application/json' \
# --data '{
# "payload": {
# "summary": "E2E test failed",
# "source": "E2E test",
# "severity": "critical"
# },
# "routing_key": "${{ secrets.E2E_PAGER_DUTY_INTEGRATION_KEY }}",
# "event_action": "trigger",
# "links": [
# {
# "href": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
# "text": "Failed E2E test - Github Action"
# }
# ]
# }'
================================================
FILE: .github/workflows/test-e2e.yaml
================================================
name: Test E2E
on:
workflow_dispatch:
pull_request:
jobs:
build:
name: Build on Java ${{ matrix.java-version }}
runs-on: macos-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
java-version: [17]
steps:
- name: Clone repository
uses: actions/checkout@v6
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: ${{ matrix.java-version }}
cache: gradle
- name: Build xctest-runner
run: ./maestro-ios-xctest-runner/build-maestro-ios-runner.sh | xcbeautify
- name: Build Maestro CLI
run: ./gradlew :maestro-cli:distZip
- name: Upload zipped Maestro CLI artifact
uses: actions/upload-artifact@v6
with:
name: maestro-cli-jdk${{ matrix.java-version }}-run_id${{ github.run_id }}
path: maestro-cli/build/distributions/maestro.zip
retention-days: 1
- name: Upload build/Products to artifacts
uses: actions/upload-artifact@v6
with:
name: build__Products-jdk${{ matrix.java-version }}
path: build/Products
retention-days: 1
test-web:
name: Test on Web
runs-on: ubuntu-latest
needs: build
steps:
- name: Clone repository (only needed for the e2e directory)
uses: actions/checkout@v6
- name: Set up demo_app workspace
run: |
git clone --depth 1 https://github.com/mobile-dev-inc/demo_app /tmp/demo_app
mkdir -p ${{ github.workspace }}/e2e/workspaces/demo_app
cp -r /tmp/demo_app/.maestro/. ${{ github.workspace }}/e2e/workspaces/demo_app/
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
- name: Set up Chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: 142
- name: Download artifacts
uses: actions/download-artifact@v7
with:
name: maestro-cli-jdk17-run_id${{ github.run_id }}
- name: Add Maestro CLI executable to PATH
run: |
unzip maestro.zip -d maestro_extracted
echo "$PWD/maestro_extracted/maestro/bin" >> $GITHUB_PATH
- name: Check if Maestro CLI executable starts up
run: |
maestro --help
maestro --version
- name: Run tests
working-directory: ${{ github.workspace }}/e2e
timeout-minutes: 20
run: ./run_tests web
- name: Upload ~/.maestro artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-root-dir-web
path: ~/.maestro
retention-days: 7
include-hidden-files: true
test-android:
name: Test on Android
runs-on: ubuntu-latest
needs: build
timeout-minutes: 60
env:
ANDROID_HOME: /home/runner/androidsdk
ANDROID_SDK_ROOT: /home/runner/androidsdk
ANDROID_AVD_HOME: /home/runner/.config/.android/avd/
ANDROID_OS_IMAGE: system-images;android-32;google_apis;x86_64
ANDROID_PLATFORM: platforms;android-34
steps:
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Clone repository (only needed for the e2e directory)
uses: actions/checkout@v6
- name: Set up demo_app workspace
run: |
git clone --depth 1 https://github.com/mobile-dev-inc/demo_app /tmp/demo_app
mkdir -p ${{ github.workspace }}/e2e/workspaces/demo_app
cp -r /tmp/demo_app/.maestro/. ${{ github.workspace }}/e2e/workspaces/demo_app/
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
- name: Download Maestro build from previous job
uses: actions/download-artifact@v7
with:
name: maestro-cli-jdk17-run_id${{ github.run_id }}
- name: Add Maestro CLI executable to PATH
run: |
unzip maestro.zip -d maestro_extracted
echo "$PWD/maestro_extracted/maestro/bin" >> $GITHUB_PATH
- name: Check if Maestro CLI executable starts up
run: |
maestro --help
maestro --version
- name: Set up mobile-dev-inc/bartek-scripts (for install_android_sdk script)
run: |
git clone https://github.com/mobile-dev-inc/bartek-scripts.git $HOME/scripts
echo "$HOME/scripts/bin" >> $GITHUB_PATH
- name: Set up Android Command-line Tools
run: |
# v13 - see https://stackoverflow.com/a/78890086/7009800
install_android_sdk https://dl.google.com/android/repository/commandlinetools-linux-12266719_latest.zip
echo "$ANDROID_HOME/cmdline-tools/latest/bin:$PATH" >> $GITHUB_PATH
- name: Set up Android SDK components
run: |
yes | sdkmanager --licenses
sdkmanager --install emulator
echo "$ANDROID_HOME/emulator" >> $GITHUB_PATH
sdkmanager --install "platform-tools"
echo "$ANDROID_HOME/platform-tools" >> $GITHUB_PATH
sdkmanager --install "$ANDROID_PLATFORM"
sdkmanager --install "$ANDROID_OS_IMAGE"
- name: Create AVD
run: |
avdmanager -s create avd \
--package "$ANDROID_OS_IMAGE" \
--name "MyAVD"
echo "DEBUG INFO"
avdmanager list avd
echo "ANDROID_PREFS_ROOT=$ANDROID_PREFS_ROOT"
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
echo "ANDROID_HOME=$ANDROID_HOME"
echo "ANDROID_SDK_HOME=$ANDROID_SDK_HOME"
echo "ANDROID_AVD_HOME=$ANDROID_AVD_HOME"
echo "ANDROID_EMULATOR_HOME=$ANDROID_EMULATOR_HOME"
echo "HOME=$HOME"
cat << EOF >> ~/.config/.android/avd/MyAVD.avd/config.ini
hw.cpu.ncore=2
hw.gpu.enabled=yes
hw.gpu.mode=swiftshader_indirect
hw.ramSize=3072
disk.dataPartition.size=4G
vm.heapSize=576
hw.lcd.density=440
hw.lcd.height=2220
hw.lcd.width=1080
EOF
- name: Run AVD
run: |
emulator @MyAVD \
-verbose -no-snapshot -no-window -no-audio -no-boot-anim -accel on -camera-back none -qemu -m 3072 \
>~/emulator_stdout.log \
2>~/emulator_stderr.log &
- name: Wait for AVD to start up
run: |
adb wait-for-device && echo 'Emulator device online'
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;' && echo 'Emulator booted'
# This is also a prerequiste
while true; do
adb shell service list | grep 'package' && echo 'service "package" is active!' && break
echo 'waiting for service "package" to start'
sleep 1
done
- name: Download apps
working-directory: ${{ github.workspace }}/e2e
run: ./download_apps android
- name: Install apps
working-directory: ${{ github.workspace }}/e2e
run: ./install_apps android
- name: Start screen recording of AVD
run: |
adb shell screenrecord /sdcard/screenrecord.mp4 &
echo $! > ~/screenrecord.pid
- name: Run tests
working-directory: ${{ github.workspace }}/e2e
timeout-minutes: 20
run: ./run_tests android
- name: Stop screen recording of AVD
if: success() || failure()
run: |
kill -SIGINT "$(cat ~/screenrecord.pid)" || echo "failed to kill screenrecord: code $?" && exit 0
sleep 5 # prevent video file corruption
adb pull /sdcard/screenrecord.mp4 ~/screenrecord.mp4
- name: Upload ~/.maestro artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-root-dir-android
path: ~/.maestro
retention-days: 7
include-hidden-files: true
- name: Upload screen recording of AVD
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-screenrecord-android.mp4
path: ~/screenrecord.mp4
retention-days: 7
test-ios:
name: Test on iOS
runs-on: macos-26
needs: build
timeout-minutes: 120
env:
MAESTRO_DRIVER_STARTUP_TIMEOUT: 240000 # 240s
MAESTRO_CLI_LOG_PATTERN_CONSOLE: '%d{HH:mm:ss.SSS} [%5level] %logger.%method: %msg%n'
steps:
- name: Clone repository (only needed for the e2e directory)
uses: actions/checkout@v6
- name: Set up demo_app workspace
run: |
git clone --depth 1 https://github.com/mobile-dev-inc/demo_app /tmp/demo_app
mkdir -p ${{ github.workspace }}/e2e/workspaces/demo_app
cp -r /tmp/demo_app/.maestro/. ${{ github.workspace }}/e2e/workspaces/demo_app/
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
- name: Download artifacts
uses: actions/download-artifact@v7
with:
name: maestro-cli-jdk17-run_id${{ github.run_id }}
- name: Add Maestro CLI executable to PATH
run: |
unzip maestro.zip -d maestro_extracted
echo "$PWD/maestro_extracted/maestro/bin" >> $GITHUB_PATH
- name: Check if Maestro CLI executable starts up
run: |
maestro --help
maestro --version
- name: Boot Simulator
run: |
xcrun simctl list runtimes
export RUNTIME="iOS26.1"
export DEVICE_TYPE="iPhone 17 Pro"
./.github/scripts/boot_simulator.sh
- name: Download apps
working-directory: ${{ github.workspace }}/e2e
run: ./download_apps ios
- name: Install apps
working-directory: ${{ github.workspace }}/e2e
run: ./install_apps ios
- name: Start screen recording
run: |
xcrun simctl io booted recordVideo --codec h264 ~/screenrecord.mp4 &
echo $! > ~/screenrecord.pid
- name: Run tests
working-directory: ${{ github.workspace }}/e2e
timeout-minutes: 120
run: ./run_tests ios
- name: Stop screen recording
if: success() || failure()
run: kill -SIGINT "$(cat ~/screenrecord.pid)"
- name: Upload ~/.maestro artifacts
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-root-dir-ios
path: ~/.maestro
retention-days: 7
include-hidden-files: true
- name: Upload xctest runner logs
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: xctest_runner_logs
path: ~/Library/Logs/maestro/xctest_runner_logs
retention-days: 7
include-hidden-files: true
- name: Upload screen recording of Simulator
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: maestro-screenrecord-ios.mp4
path: ~/screenrecord.mp4
retention-days: 7
test-ios-xctest-runner:
name: Test on iOS (XCTest Runner only)
if: false # Disabled: This needs be fixed, not working yet.
runs-on: macos-latest
needs: build
timeout-minutes: 30
steps:
- name: Clone repository (only needed for the e2e directory)
uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 17
- name: Download Maestro artifact
uses: actions/download-artifact@v7
with:
name: maestro-cli-jdk17-run_id${{ github.run_id }}
- name: Download build/Products artifact
uses: actions/download-artifact@v7
with:
name: build__Products-jdk17
path: build/Products
- name: Add Maestro CLI executable to PATH
run: |
unzip maestro.zip -d maestro_extracted
echo "$PWD/maestro_extracted/maestro/bin" >> $GITHUB_PATH
- name: Check if Maestro CLI executable starts up
run: |
maestro --help
maestro --version
- name: Boot Simulator
run: ./.github/scripts/boot_simulator.sh
- name: Run tests
timeout-minutes: 15
run: ./maestro-ios-xctest-runner/test-maestro-ios-runner.sh
- name: Upload xc test runner logs
uses: actions/upload-artifact@v6
if: success() || failure()
with:
name: test-ios-xctest-runner__xctest_runner_logs
path: ~/Library/Logs/maestro/xctest_runner_logs
retention-days: 7
include-hidden-files: true
================================================
FILE: .github/workflows/test.yaml
================================================
name: Test
on:
workflow_dispatch:
pull_request:
jobs:
unit-test:
name: Unit Test on Java ${{ matrix.java-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
java-version: [17]
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: ${{ matrix.java-version }}
cache: gradle
- name: Test
id: unit-test
run: ./gradlew test
- name: Upload unit test report
uses: actions/upload-artifact@v4
if: failure()
with:
name: maestro-unit-test-report
path: ./**/build/reports/tests/test
retention-days: 1
include-hidden-files: true
ios-driver-lib-test:
name: MaestroDriverLib Unit Tests
runs-on: macos-latest
timeout-minutes: 10
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Run MaestroDriverLib Tests
working-directory: ${{ github.workspace }}/maestro-ios-xctest-runner/MaestroDriverLib
run: swift test
ios-xctest-runner-test:
name: iOS XCTest Runner Unit Tests
runs-on: macos-latest
timeout-minutes: 15
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
- name: Run iOS Unit Tests
working-directory: ${{ github.workspace }}/maestro-ios-xctest-runner
run: |
xcodebuild test \
-project maestro-driver-ios.xcodeproj \
-scheme maestro-driver-iosTests \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:maestro-driver-iosTests \
| xcpretty --color || exit ${PIPESTATUS[0]}
validate-gradle-wrapper:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
================================================
FILE: .github/workflows/update-samples.yaml
================================================
name: Update samples
on:
workflow_dispatch:
push:
branches: [main]
jobs:
main:
runs-on: ubuntu-latest
if: github.repository == 'mobile-dev-inc/maestro'
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
# These credentials should only have write access to the bucket
credentials_json: ${{ secrets.GCP_MOBILEDEV_BUCKET_CREDENTIALS }}
- name: Set up Google Cloud CLI
uses: google-github-actions/setup-gcloud@v2
with:
version: '>= 484.0.0'
project_id: perf-dev-289002
- name: Upload samples to public Google Cloud Storage bucket
run: |
cd e2e/
./update_samples
================================================
FILE: .gitignore
================================================
.DS_Store
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# Ignore Gradle local properties
local.properties
bin
# media assets
maestro-orchestra/src/test/resources/media/assets/*
# Local files
local/
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Above is default, IntelliJ-generated config. Below is our custom config.
# Inspired by:
# - https://github.com/Vadorequest/JetBrains-Intellij-IDEA-.gitignore-best-practices
# - https://github.com/salarmehr/idea-gitignore
# - https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
dataSources/
dataSources.local.xml
misc.xml
workspace.xml
google-java-format.xml
inspectionProfiles/
compiler.xml
deploymentTargetSelector.xml
gradle.xml
kotlinc.xml
vcs.xml
copilot.data.migration.*
================================================
FILE: .idea/.name
================================================
maestro
================================================
FILE: .idea/dictionaries/project.xml
================================================
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>addmedia</w>
<w>amanjeet</w>
<w>applesimutils</w>
<w>avdmanager</w>
<w>bartekpacia</w>
<w>bartkepacia</w>
<w>berik</w>
<w>caseley</w>
<w>cirrusci</w>
<w>clearstate</w>
<w>dadb</w>
<w>devicectl</w>
<w>dpad</w>
<w>evals</w>
<w>faceid</w>
<w>feeditem</w>
<w>graal</w>
<w>graaljs</w>
<w>inputmethod</w>
<w>iphoneos</w>
<w>iphonesimulator</w>
<w>jreleaser</w>
<w>keyevent</w>
<w>macosx</w>
<w>mdev</w>
<w>medialibrary</w>
<w>mobiledev</w>
<w>mobilesafari</w>
<w>modelcontextprotocol</w>
<w>niklasson</w>
<w>nowinandroid</w>
<w>openqa</w>
<w>posthog</w>
<w>printenv</w>
<w>reinstalls</w>
<w>rhinojs</w>
<w>runscript</w>
<w>saveliev</w>
<w>screenrecord</w>
<w>screenrecording</w>
<w>sdkmanager</w>
<w>simctl</w>
<w>systemui</w>
<w>takamine</w>
<w>testsuites</w>
<w>tokou</w>
<w>udid</w>
<w>visschers</w>
<w>xcbeautify</w>
<w>xcrun</w>
<w>xctestrun</w>
<w>xctestrunner</w>
<w>xctrunner</w>
<w>xcuitest</w>
<w>yamls</w>
<w>zaytsev</w>
</words>
</dictionary>
</component>
================================================
FILE: .run/cli-version.run.xml
================================================
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="CLI | version" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="maestro.cli.AppKt" />
<module name="maestro.maestro-cli.main" />
<option name="PROGRAM_PARAMETERS" value="-version" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
================================================
FILE: .run/cli.run.xml
================================================
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="CLI" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="maestro.cli.AppKt" />
<module name="maestro.maestro-cli.main" />
<option name="PROGRAM_PARAMETERS" value="$Prompt$" />
<option name="WORKING_DIRECTORY" value="$FilePrompt$" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## Unreleased
## 2.4.0
- Add new device config flags for cloud and start-device
- Deprecated `--ios-version`, `--android-api-level`, and `--os-version` flags. These will be removed in a future release.
- `--device-os` and `--device-model` replace all platform-specific device options, providing a single consistent way to specify devices across iOS, Android, and Web
- Add `maestro list-devices` command to see locally available devices on the machine
- Add `maestro list-cloud-devices` command to see available cloud device models and OS versions
- Support iframes for web tests
- Faster feedback when using Maestro Cloud - more validation is happening locally
- Improve feedback when startRecording fails on iOS
- Add clearState support for web tests
- Fix inputText crashing on iOS pincode screens
- Fix incorrect websocket timeout (5000ms, not 5000s!)
- Add support for variables as input to setOrientation
- Add deprecation notice to `maestro studio`
- Improved variable isolation and reduced memory usage in JavaScript evaluations
- Fix step ordering in the html-detailed test report
- Improve timeouts in all API calls (especially useful for Maestro Cloud uploads)
## 2.3.0
- Add web support for `clearState` command
- Fix `assertScreenshot` not failing when screenshot dimensions are mismatched
- Make `assertScreenshot` work more like `takeScreenshot` by not requiring file extension
- Fix path resolution for `assertScreenshot` to allow relative paths from flows for reference images
- Fix `inputRandomPersonName` to generate a predictable "FirstName LastName" format
- Fix iPad landscape orientation support
- Fix specifying `--device` when also specifying `--host`
- Fix cloud uploads to always use requested device specifications on retries
Thanks to @SosenWiosen, @leggomuhgreggo and @jkronborg who contributed changes included in this release ❤️
## 2.2.0
- Add `--screen-size` option to test command, to specify the headless browser window size when testing web flows
- Add `MAESTRO_DEVICE_UDID`, `MAESTRO_SHARD_ID`, and `MAESTRO_SHARD_INDEX` as default environment variables (useful for screenshot filenames when sharding)
- Add step information to HTML test reports via a new `html-detailed` formatter
- Add tags and custom property information to HTML and JUnit test reports
- Add a new `assertScreenshot` command for visual regression testing
- Add a `cropOn` property to the `takeScreenshot` command to crop screenshots to a specific element
- Fix scrolling in Flutter Web
- Fix output of subflows when using `--no-ansi` flag
- Show `maestro hierarchy` and `maestro check-syntax` commands in `maestro --help`
- Fix iOS driver app on Simulators running on Intel-based Macs
- Fix a potential hang between Maestro and the on-device drivers when calls take too long
- Some logging adjustments for less noise during web tests
- Bump web support to Chrome v144
- Bump DataFaker to v2.5.3, GraalJS engine to v24.2.0, log4j to v2.25.3
Thanks to @sazquatch17, @ImL1s, @sidferreira, @SosenWiosen, @TheKohan, @Fl0p, @ff-vivek and @eldare who all contributed changes included in this release ❤️
## 2.1.0
- Add `setPermissions` command, for setting app permissions outside of `launchApp`
- Add `setClipboard` command, for setting Maestro's internal clipboard without copying from an element
- Add `--platform` and `--device` to `maestro test` command
- Add custom JUnit properties to reporting
- Add support for --no-reinstall-driver option to `test` and `hierarchy` commands
- Add creation of missing folders specified in the path when taking screenshots or recording videos
- Bump web support to Chrome v142
- Bump npm dependencies in legacy Maestro Studio
- Hide incomplete `maestro driver-setup` command from `maestro --help`
- Remove deprecated `deterministicOrder` feature from workspace config
- Remove deprecated `maestro upload` command
- Fix bug that reported that analytics was enabled when it wasn't
- Fix building Maestro on Java >17
- Fix link in `maestro bugreport`
- Fix cancellation of flows whilst repeat loops are running
- Fix enumeration of multi-select elements in Web
- Fix use of hierarchy and screenshot strategies across Android and iOS
- Fix web tests running into Chrome's password leak detection
- Fix webview detection and interaction on iOS 26
- Fix broken relative paths when uploading files via multipart form in `http.post`
Special thanks to the Maestro community for contributing to this release! Shout out to @tokou, @kprakash2, @trongrg, @vibin, @ryuuhei0729, @Thomvis, @MarcellDr and @leovarmak ❤️
## 2.0.10
- Fix error messaging when running with shards fails
- Improve gathering of dependencies when running single flows with `maestro cloud`
## 2.0.7
### Fix
- Fixed bug affecting CI and pull request integrations where org prompts would fail in non-interactive environments.
## 2.0.6
### Features
- Added support for negative index in element selector
- Made specifying `--project_id` for cloud upload optional. **In case it is not specified and there are multiple projects, a prompt for selecting the project will be provided.**
- In case the user belongs to multiple organizations and hasn't specified `--api-key` during cloud upload, a prompt for **selecting the organization** will be provided.
### Fixes
- Added descriptions to missing element selector aspects (enabled, disabled, selected, not selected, focused, not focused)
## 2.0.5
### Fixes
- Removed debugging logs
## 2.0.4
### Features
- Added support for tapping at specific coordinates relative to an element using the `relativePoint` parameter in `tapOn` and `doubleTapOn` commands [Github Issue](https://github.com/mobile-dev-inc/Maestro/issues/2059)
- Labels in commands can now be dynamically evaluated using JavaScript expressions (thanks @jerriais!)
### Fixes
- Fixed issue where `maestro login` would fail if user was already logged in
- Fixed iOS permission setting when using 'all' with specific permission overrides
- Fixed issue where platform argument would be ignored
- Fixed issue where blank platform argument would incorrectly filter out all tests
- Fixed off-by-one error when specifying count with `eraseText` command on Android
- Improved performance by evaluating script conditions eagerly, ahead of visibility conditions (thanks @tokou!)
- Fixed crash when running Maestro with empty arguments
- Updated iOS test runner to support Xcode 26
- Improved logging on Android driver timeouts
- Improved copy/paste experience in legacy Maestro Studio (thanks @tylerqr!)
## 2.0.3
Fixes:
- Fix filter logic that was causing incorrect element selection when using multiple selectors together for some applications
- GraalJS will now isolate environment variables correctly between different runScript executions
- Fix incorrect reporting of failures in `HtmlTestSuiteReporter`
## 2.0.2
Fixes:
- Added Rhino deprecation warning in CLI
- Fix conditions for checking if web flows exist in workspace
- Added back Run details to cloud upload logs (regression in Maestro 2.0.0)
## 2.0.1
Fixes:
- Fix issues with launching CLI on Windows systems
## 2.0.0
Breaking Change:
- Updated java version to 17 better performance, security, and modern features. **If you’re still on an older version, update before using 2.0.0.**
- We’ve switched from Rhino to **GraalJS** as the default JavaScript engine. Expect **faster execution** and **modern JS support** for your scripts. [GraalJS Behaviour Differences](https://docs.maestro.dev/advanced/javascript/graaljs-support#graaljs-behavior-differences)
- URLs in the `appId` field are no longer supported. Flows must now use the `url` field in the YAML config for URLs.
Features:
- Added `setOrientation` command — adjust device orientation in tests (`PORTRAIT`, `UPSIDE_DOWN`, `LANDSCAPE_LEFT`, `LANDSCAPE_RIGHT`). ([Docs](https://docs.maestro.dev/api-reference/commands/setorientation))
- Enhanced MCP Integration:
- More accurate flow path resolution
- View hierarchy output size reduced by **50%** (faster & lighter)
- `run_flow` / `run_flow_files` now support env variables & hooks
- Added `--test-output-dir` to specify where test artifacts should be saved. ([Docs](https://docs.maestro.dev/cli/test-output-directory))
- Added support for running entire workspace of **web flows** in a single `test` command.
- Allowed Keep-Alive from Server to support for persistent connections.
- Environment variables are now isolated between peer `runFlow` commands.
- Added timestamp to JUnit and HTML test report
- DataFaker is now available in JavaScript to generate random data for use in tests ([Docs](https://docs.maestro.dev/advanced/javascript/generating-random-with-faker.md))
Fixes:
- Fix CLI Cloud upload output
- Fix broken `maestro studio` command for web version of Studio.
- Fix **memory leak** for ios test runs that could cause out of memory issues on testing environments.
- Fix `maestro cloud` command when uploading files that have external dependencias (subflows, scripts and media)
- Fix disconnect in local iOS test executions when flow contains a large element tree
## 1.41.0
Fix:
- Resolved an issue where view hierarchy was incorrectly returned on full-screen apps or larger devices (e.g., iPhone Pro models, iOS 18). This affected selector matching for taps and assertions.
- Maestro now properly handles timeouts from the XCTest framework when the app UI is slow or too large. These are surfaced as actionable exceptions with helpful messages.
- setLocation now mocks all major location providers (GPS, network, fused). Also ensures proper cleanup when the driver shuts down.
- Errors when .maestro config file is misinterpreted as a test flow file.
Features:
- Platform configs are now supported via workspace configuration [(Docs)](https://docs.maestro.dev/api-reference/configuration/workspace-configuration#platform-configuration):
* `disableAnimations` for both android and iOS.
* `snapshotKeyHonorModalViews`: On iOS, includes elements behind modals that are still visible on modal to user but gets missing in hierarchy.
- Added support for selecting `select` tags dropdown elements in web flows.
- Debug messages are now attached to Maestro exceptions to help users understand failures faster.
- Added support for selecting elements using CSS/DOM query
- Added Maestro MCP server implementation to cli by [[Stevie Clifton](https://github.com/steviec)]
Breaking Change:
- `retryTapIfNoChange` is now disabled by default. It was causing side effects in some apps. If needed, it can still be manually enabled.
## 1.40.3
Fix
- MissingKotlinParameterException during using maestro commands.
## 1.40.2
Fix
- Sharding on iOS, throwing FileSystemAlreadyExistsException exception
## 1.40.1
Fix
- iOS apps going on background while using maestro commands
Feature
- Flag to skip interactive device selection by picking a --device-index
## 1.40.0
Fix:
- JavaScript fails when running maestro test in continuous mode. Affected Commands: `maestro test`, `maestro record` ([#2311](https://github.com/mobile-dev-inc/Maestro/pull/2311))
- Ignore notifications in analyse command for CI ([#2306](https://github.com/mobile-dev-inc/Maestro/pull/2306))
- `config.yaml` not resolving on Windows ([#2327](https://github.com/mobile-dev-inc/Maestro/pull/2327))
- Fix swipe command failure on iOS after upgrading to Xcode 16.2 [issue #2422](https://github.com/mobile-dev-inc/maestro/issues/2422) ([#2332](https://github.com/mobile-dev-inc/maestro/pull/2332))
- Fix `app-binary-id` option on maestro cloud upload ([#2361](https://github.com/mobile-dev-inc/Maestro/pull/2361))
- Ensure commands with missing elements fail as expected in Studio ([#2140](https://github.com/mobile-dev-inc/Maestro/pull/2140))
- Prevent flows from getting stuck on the cloud by properly setting driver closing state ([#2364](https://github.com/mobile-dev-inc/Maestro/pull/2364))
- Fix `maestro cloud` & `maestro start-device` on windows ([#2371](https://github.com/mobile-dev-inc/Maestro/pull/2371))
- Improved `maestro cloud` to only process valid flow `.yaml`/`.yml` files and skip unrelated files like `config.yaml`, preventing parsing errors when uploading folders with mixed content ([#2359](https://github.com/mobile-dev-inc/Maestro/pull/2359))
- Improved `maestro cloud` to skip validating non-flow files (e.g., .js, README, config.yaml) in folders, preventing parsing errors and upload failures
- Fix setting up iOS Driver when not on bash environment ([#2412](https://github.com/mobile-dev-inc/Maestro/pull/2412))
- Speed up view hierarchy generation by reducing SpringBoard queries and avoiding redundant app list calls on iOS. ([#2419](https://github.com/mobile-dev-inc/Maestro/pull/2419))
Features:
- Added support for `androidWebViewHierarchy: devtools` option to build Android WebView hierarchy using Chrome DevTools ([#2350](https://github.com/mobile-dev-inc/Maestro/pull/2350))
- Added Chrome to available devices for web automation ([#2360](https://github.com/mobile-dev-inc/Maestro/pull/2360))
- Introduced pre-built mode for setting up iOS driver on simulators without relying on `xcodebuild` ([#2325](https://github.com/mobile-dev-inc/Maestro/pull/2325))
- Added command-line chat mode to Maestro CLI accessible by `maestro chat --ask=` and `maestro chat` ([#2378](https://github.com/mobile-dev-inc/Maestro/pull/2378))
- Introduced `maestro check-syntax` command for validating flow syntax ([#2387](https://github.com/mobile-dev-inc/Maestro/pull/2387))
- Added `--reinstall-driver` flag that reinstalls xctestrunner driver before running the test. Set to false if the driver shouldn't be reinstalled ([#2413](https://github.com/mobile-dev-inc/Maestro/pull/2413))
- Added `--compact` flag that remove empty values to make the output hierarchy json smaller ([#2413](https://github.com/mobile-dev-inc/Maestro/pull/2413))
- Added `--device-os` and `--device-model` options to target specific iOS minor versions and devices ([Docs](https://docs.maestro.dev/cloud/reference/configuring-os-version#using-a-specific-ios-minor-version-and-device-recommended)) ([#2413](https://github.com/mobile-dev-inc/Maestro/pull/2413))
- Added support for ios 18 on cloud and local
- Bumped default iOS version to 16 for `maestro start-device`
- Enabled AI command usage on `mobile.dev` ([#2425](https://github.com/mobile-dev-inc/Maestro/pull/2425))
Chore:
- Update Flying Fox HTTP server on iOS driver ([#2415](https://github.com/mobile-dev-inc/Maestro/pull/2415))
- Migrated app termination from `simctl` to `xctest` for improved stability` ([#2418](https://github.com/mobile-dev-inc/Maestro/pull/2418))
## 1.39.13
- Fix : Adding upload route back again
- Feature: Removing Analyze logs from CI uploads
## 1.39.12
- Fix: Upload route on Robin was not working on maestro cloud command
## 1.39.11
- Feature: Starting trial from CLI
- Feature: Better logs to improve visibility
- Feature: Prebuilt iOS driver without xcodebuild
- Feature: Analyze option to test command
## 1.39.10
- Update install script to tidy up old installation binaries
## 1.39.9
- Revert: Error in showing keyboard during input and erase commands on iOS
- Fix: applesimutils affecting granting location permission
- Fix: Setting host and port from the optional arguments
- Feature: New `maestro login` command for logging in Robin.
- Feature: Improved `maestro record` video to scroll and follow the currently executing commands
- Fix: Enable running Maestro on Windows without WSL
- Feature: Add console.log messages directly to the maestro log file.
## 1.39.8
- Fix: Debug message not showing up when we execute commands on maestro cli anymore
## 1.39.7
- Feature: Improved web support.
- Fix: Maestro can test web pages again (it was broken)
- Fix: WebDriver was reporting invalid screen size
- Web: support cases where a new tab is opened from the page
- Web: screen recording support (via JCodec for now, but we could add ffmpeg later)
- Web: fake geolocation support
- Studio: better layout for wide aspect-ratio screens (i.e. web pages or tablets)
- Feature: Introduces extractTextWithAI command
- Fix: Retry should throw exception when max retries reaches
- Fix: Studio getting unresponsive due to exceptions in streaming device
## 1.39.5
Released on 2024-12-16
Fixes:
- Fix: Failure on how the assertConditionCommand was being handled on Robin([#2171](https://github.com/mobile-dev-inc/maestro/pull/2171))
## 1.39.4
Features:
- Add `waitToSettleTimeoutMs` to other swipe related commands ([#2153](https://github.com/mobile-dev-inc/maestro/pull/2153))
- Add retry command for flaky conditions ([#2168](https://github.com/mobile-dev-inc/maestro/pull/2168))
- Add support for recording maestro flows locally instead of using remote servers ([#2173](https://github.com/mobile-dev-inc/maestro/pull/2173))
Fixes:
- Fix: multiple xcodebuild process and leading to IOSDriverTimeoutException ([#2097](https://github.com/mobile-dev-inc/maestro/pull/2097))
- Fix: NullPointerException during view hierarchy operations for android ([#2172](https://github.com/mobile-dev-inc/maestro/pull/2172))
- Fix: Debug level logs in maestro.log file leading to large debug files ([#2170](https://github.com/mobile-dev-inc/maestro/pull/2170))
- Fix: Environment variable not being set for test suite ([#2163](https://github.com/mobile-dev-inc/maestro/pull/2163))
- Fix: Failures on clearKeychain operations on iOS due to missing directories ([#2178](https://github.com/mobile-dev-inc/maestro/pull/2178))
## 1.39.2
Released on 2024-11-19
Fixes:
- Fix: Insights object causing ConcurrentModificationException ([#2131](https://github.com/mobile-dev-inc/maestro/pull/2131))
- Fix: Timeout unit in scrollUntilVisible command ([#2112](https://github.com/mobile-dev-inc/maestro/pull/2112))
- Feat: Add new status for robin flows: PREPARING and INSTALLING. ([#2145](https://github.com/mobile-dev-inc/maestro/pull/2145))
## 1.39.1
Released on 2024-11-04
Fixes:
- Fix: clearState now automatically reinstall the App ([#2118](https://github.com/mobile-dev-inc/maestro/pull/2118))
## 1.39.0
Released on 2024-10-15
Features:
- Feature: add `--shard-split` and `--shard-all` options to `maestro test` ([#1955](https://github.com/mobile-dev-inc/maestro/pull/1955) by [Tarek Belkahia](https://github.com/tokou))
The `--shard` is now deprecated and superseded by `--shard-split`.
- Feature: allow for passing multiple flow files to `maestro test` ([#1995](https://github.com/mobile-dev-inc/maestro/pull/1995) by [Tarek Belkahia](https://github.com/tokou))
- Feature: add the `optional` argument to all commands ([#1946](https://github.com/mobile-dev-inc/maestro/pull/1946) by [Tarek Belkahia](https://github.com/tokou))
This new command-level `optional` argument supersedes the (now removed) selector-level `optional` argument. No behavior changes are expected.
When command with `optional: true` fails, its status is now "warned ⚠️" instead of "skipped ⚪️"
- Feature: add changelog to the update prompt when new Maestro version is available ([#1950](https://github.com/mobile-dev-inc/maestro/pull/1950) by [Tarek Belkahia](https://github.com/tokou))
- Feature: add back the `--platform` option ([#1954](https://github.com/mobile-dev-inc/maestro/pull/1954) by [Tarek Belkahia](https://github.com/tokou))
- Feature: expose current flow name as `MAESTRO_FILENAME` env var ([#1945](https://github.com/mobile-dev-inc/maestro/pull/1945) by [Tarek Belkahia](https://github.com/tokou))
Fixes:
- Fix: Warnings generated by AI-powered commands aren't formatted nicely ([#2043](https://github.com/mobile-dev-inc/maestro/pull/2043)) ([#2044](https://github.com/mobile-dev-inc/maestro/pull/2044))
- Fix: not working when iOS simulator is in landscape orientation ([caveats apply](https://github.com/mobile-dev-inc/maestro/pull/1974#issuecomment-2346074593)) ([#1974](https://github.com/mobile-dev-inc/maestro/pull/1974))
- Fix: confusing error message "BlockingCoroutine is cancelling" ([#2036](https://github.com/mobile-dev-inc/maestro/pull/2036))
- Fix: AI-powered commands crashing when Anthropic is used ([#2033](https://github.com/mobile-dev-inc/maestro/pull/2033))
- Fix: display warnings generated by AI-powered commands in CLI output when `optional: true` ([#2026](https://github.com/mobile-dev-inc/maestro/pull/2026))
- Fix: visual bug with emojis having slightly different length in `maestro test`'s interactive CLI output ([#2016](https://github.com/mobile-dev-inc/maestro/pull/2016))
- Fix: no tests being run when flowsOrder specified all tests in the workspace ([#2003](https://github.com/mobile-dev-inc/maestro/pull/2003))
- Fix: using integers from JavaScript outputs causing a deserialization error ([#1788](https://github.com/mobile-dev-inc/maestro/pull/1788) by [Muhammed Furkan Boran](https://github.com/boranfrkn))
- Fix: delete temporary APKs after using them ([#1947](https://github.com/mobile-dev-inc/maestro/pull/1947) by [Tarek Belkahia](https://github.com/tokou))
- Fix: allow env vars in `setLocation` and `travel` commands ([#1988](https://github.com/mobile-dev-inc/maestro/pull/1988) by [Prasanta Biswas](https://github.com/prasanta-biswas))
- Fix: error message when specifying `--format` together with `--continuous` #1948 ([#1948](https://github.com/mobile-dev-inc/maestro/pull/1948) by [Tarek Belkahia](https://github.com/tokou))
Chores:
- Chore: clean up logging, make log format configurable with 2 new env vars ([#2041](https://github.com/mobile-dev-inc/maestro/pull/2041))
- Chore: make Maestro build & compile on Java 17 ([#2008](https://github.com/mobile-dev-inc/maestro/pull/2008))
- Chore: Migrate all Gradle buildscripts to Gradle Kotlin DSL ([#1994](https://github.com/mobile-dev-inc/maestro/pull/1994))
## 1.38.1
Released on 2024-08-30
- New experimental AI-powered commands for screenshot testing: [assertWithAI](https://maestro.mobile.dev/api-reference/commands/assertwithai) and [assertNoDefectsWithAI](https://maestro.mobile.dev/api-reference/commands/assertnodefectswithai) ([#1906](https://github.com/mobile-dev-inc/maestro/pull/1906))
- Enable basic support for Maestro uploads while keeping Maestro Cloud functioning ([#1970](https://github.com/mobile-dev-inc/maestro/pull/1970))
## 1.37.9
Released on 2024-08-15
- Revert iOS landscape mode fix ([#1916](https://github.com/mobile-dev-inc/maestro/pull/1916))
## 1.37.8
Released on 2024-08-14
- Fix sharding on Android failing on all but one devices (quick hotfix) ([#1867](https://github.com/mobile-dev-inc/maestro/pull/1867))
- Fix CLI crash when flow is canceled on Maestro Cloud ([#1912](https://github.com/mobile-dev-inc/maestro/pull/1912))
- Fix iOS landscape mode ([caveats apply](https://github.com/mobile-dev-inc/maestro/pull/1809#issuecomment-2249917209)) ([#1809](https://github.com/mobile-dev-inc/maestro/pull/1809))
- Skip search engine selection when running on the web ([#1869](https://github.com/mobile-dev-inc/maestro/pull/1869))
## 1.37.7
Released on 2024-08-03
- Fix cryptic "Socket Exception" when `CI` env var is set, once and for all ([#1882](https://github.com/mobile-dev-inc/maestro/pull/1882))
## 1.37.6
Released on 2024-08-02
- Print stack trace on 3rd retry ([#1877](https://github.com/mobile-dev-inc/maestro/pull/1877))
## 1.37.5
Released on 2024-08-02
- Fix cryptic "SocketException" when API token is invalid ([#1871](https://github.com/mobile-dev-inc/maestro/pull/1871))
## 1.37.4
Released on 2024-07-30
- Don't ask for analytics permission on CI + add `MAESTRO_CLI_NO_ANALYTICS` env var ([#1848](https://github.com/mobile-dev-inc/maestro/pull/1848))
## 1.37.3
Released on 2024-07-29
### Bug fixes
- Fix `FileNotFoundException: ~.maestro/sessions` ([#1843](https://github.com/mobile-dev-inc/maestro/pull/1843))
## 1.37.2 - 2024-07-29
### Bug fixes
- Fix `UnsupportedOperationException: Empty collection can't be reduced` ([#1840](https://github.com/mobile-dev-inc/maestro/pull/1840))
## 1.37.1 - 2024-07-29
### Bug fixes
- Fix crash when `flutter` or `xcodebuild` is not installed ([#1839](https://github.com/mobile-dev-inc/maestro/pull/1839))
## 1.37.0 - 2024-07-29
### New features
- **Sharding tests for parallel execution on many devices 🎉** ([#1732](https://github.com/mobile-dev-inc/maestro/pull/1732) by [Kaan](https://github.com/sdfgsdfgd))
You can now pass `--shards` argument to `maestro test` to split up your test suite into chunks that run in parallel. If you have feedback or suggestions about this huge new feature, please share them with us in [issue #1818](https://github.com/mobile-dev-inc/maestro/issues/1818).
- **Reports in HTML** ([#1750](https://github.com/mobile-dev-inc/maestro/pull/1750) by [Depa Panjie Purnama](https://github.com/depapp))
To see it, run `maestro test --format HTML <your-flow.yaml>`
- **Homebrew is back!**
If you prefer to switch your installation of Maestro to use Homebrew:
1. `rm -rf ~/.maestro`
2. `brew tap mobile-dev-inc/tap && brew install maestro` 🎉
Script install method is still supported.
- **Current platform exposed in JavaScript** ([#1747](https://github.com/mobile-dev-inc/maestro/pull/1747) by [Dan Caseley](https://github.com/Fishbowler))
In JavaScript, you can now access `maestro.platform` to express logic that depends on whether the test runs on iOS or Android.
- **Control airplane mode** ([#1672](https://github.com/mobile-dev-inc/maestro/pull/1672) by [NyCodeGHG](https://github.com/NyCodeGHG))
New commands: `setAirplaneMode` and `toggleAirplaneMode`. Android-only because of iOS simulator restrictions.
- **New `killApp` command** ([#1727](https://github.com/mobile-dev-inc/maestro/pull/1727) by [Alexandre Favre](https://github.com/alexandrefavre4))
To trigger a System-Initiated Process Death on Android. On iOS, works the same as `stopApp`.
### Bug fixes
- Fix cleaning up retries in iOS driver ([#1669](https://github.com/mobile-dev-inc/maestro/pull/1669))
- Fix some commands not respecting custom labels ([#1762](https://github.com/mobile-dev-inc/maestro/pull/1762) by [Dan Caseley](https://github.com/Fishbowler))
- Fix “Protocol family unavailable” when rerunning iOS tests ([#1671](https://github.com/mobile-dev-inc/maestro/pull/1671) by [Stanisław Chmiela](https://github.com/sjchmiela))
## 1.36.0 - 2024-02-15
- Feature: Add support for extra keys to Android TV
- Feature: Add support for pressing tab key on Android
- Feature: Add status and time to report.xml
- Fix: Extend retry to handle 404 in upload status call
- Fix: Crashes caused by toasts on Android API < 30
## 1.35.0 - 2024-01-08
- Change: Adds view class to Android hierarchy output
- Change: Improves description of maestro start-device command to include device locale as well
- Change: Adds scrollable attribute to Android view hierarchy output
- Feature: Adds childOf attribute to selector to select from children of a container
- Feature: Adds label attribute to customize the CLI output of maestro commands
- Fix: Fixing “Unsupported architecture UNKNOWN” on linux environment when calling maestro attempts to create devices
- Fix: Allow maestro to work below API level 25 for Android
- Fix: IllegalArgumentException on swipe operation for iOS if the coordinates beyond device width and height are selected
## 1.34.5 - 2024-01-04
- Feature: Adds a parameter to exclude all the keyboard elements from hierarchy
## 1.34.4 - 2023-12-27
- Fix: Failures due to swipe ranges going beyond screen dimensions
- Change: Adding escape key in `pressKey` API
- Tweak: Avoid returning `Result` in IOSDriver install and clearAppState
## 1.34.3 - 2023-11-21
- Tweak: Include scrollable attribute in view hierarchy from Android Driver
- Feature: Custom labels for readability of maestro commands
- Feature: Adding childOf selector
- Tweak: Message of start-device command to show locale as well
## 1.34.2 - 2023-11-13
- Tweak: Include view class in view hierarchy attributes from the Android driver
## 1.34.1 - 2023-11-9
- Feature: add support `--device-locale` parameter for `maestro cloud` command
- Feature: add support iOS17 for `maestro start-device` command
- Feature: add support Android API level 34 for `maestro start-device` command
## 1.34.0 - 2023-10-24
- Feature: support `--device-locale` parameter for `maestro start-device`
- Feature: add `centerElement` parameter for `scrollUntilVisible`. Center element will attempt to stop scrolling when the element is near the center of the screen.
- Feature: add `power` button support for `pressKey` on Android
- Change: add `tapOn` parameter `waitToSettleTimeoutMs` to control how long it waits to move on to the next command. Helpful for animation heavy apps.
- Change: improve executionOrder planning
- Change: improve retry mechanism to ensure openness of XCUITest Server
- Fix: improve `TimeoutException` for driver startup
## 1.33.1 - 2023-10-03
- Feature: support for multipart form data file upload in Javascript, thanks @maciejkrolik
- Fix: setPermissions produces error on Xcode 15
- Fix: Maestro studio - include enter key in command editor on initial paste
## 1.33.0 - 2023-09-21
- Feature: Adds MAESTRO_DRIVER_STARTUP_TIMEOUT to iOS driver to configure timeout to start iOS driver, used in CI/CD environment with performance limitations. Thanks, Jesse Farsong for contributing.
- Feature: Introducing the "addMedia" command that enables adding images and videos directly to the devices.
- Change: Improved Studio's user interface:
- Updated fonts to align with company branding.
- Introduced a distinct loading animation for better clarity when AI is processing commands.
- Fix: Crash resulting in Error: No matches found for first query match sequence: `Children matching type Other` due to resolving root element for a snapshot operation on iOS
- Fix: Android driver getting stuck when the device was disconnected
- Fix: XCTestUnreachable exceptions due to missing IPv6 config on /etc/hosts
- Fix: Handling app crash errors from XCUITest drivers gracefully
- Fix: Timeouts can be separated with `_`. For example 10_000 for 10000
## 1.32.0 - 2023-09-06
Studio
- Feature: Support writing Flows using AI (more info to come 🚀)
- Feature: Maestro Studio can now run in multiple tabs simultaneously
- Feature: Added element id and copy option for it
- Tweak: Hide action buttons till command is hovered
- Tweak: Hide Unnecessary Scrollbars
- Tweak: Repl view scroll improvements
- Tweak: Improve Maestro Studio performance
- Fix: Selected element size
- Fix: Performance issues with maestro studio device refresh
- Fix: Fixed dark mode for element id
CLI
- Feature: New command to start or create a Maestro recommended device (docs)
- Feature: Support id selection for testID with react-native-web (community contribution)
- Feature: Control if browser automatically opens when running Maestro Studio via --no-window (community contribution)
- Tweak: Show cancellation reason when available (Maestro Cloud)
- Tweak: Update selenium-java and remove webdrivermanager to support Chrome 116+
- Tweak: Show device type when running on Maestro Cloud
- Tweak: Added better messaging and recovery options for Maestro Cloud uploads (useful for CI)
- Tweak: Added better error messages for missing workspace and yaml validation errors
- Tweak: Added file name and line number in yaml parsing error messages
- Fix: Input text and erase text stability improvements for iOS
- Fix: Leaking response body on iOS & better error handling for iOS Driver
- Fix: Fixed Maestro Cloud wrong exit code when flow failed
- Fix: Debug commands parsing would crash maestro
- Fix: Cleaning up debug logs
## 1.31.0 - 2023-08-10
- Fix: Warning shown from OkHttp for leaking response bodies on CLI
- Closing response bodies for retries done on the XCUITest driver
- Closing response bodies for permissions
- Removing different thread execution done on hideKeyboard
- Fix: Scroll for React native apps on screens with large view hierarchies on iOS
- Fix: Showing more descriptive errors on flow file not found during maestro cloud command.
- Fix: Input text characters being skipped or being appended later in the test on iOS
- Fix: Crash in debug output generation when maestro flow contains "/"’
- Fix: Resolved issue where tapping on the device in maestro studio produced inaccurate click locations due to incorrect coordinates. Now fixed for accurate device interaction
- Fix: In Maestro Studio, the issue of window resizing causing devices to overflow off the screen has been resolved.
- Feature: Add headers to HTTP response for API calls done with Maestro. Thanks, Jesse Willoughby! for this contribution.
- Feature: Now it is possible to configure the path with the –debug-output option for debugging information that maestro dumps in the user directory by default.
- Feature: Enhanced Maestro Studio with keyboard accessibility, streamlining navigation and facilitating the copy, run, and edit commands using the keyboard.
- Change: Fail the test if any of the onFlowStart or onFlowComplete hooks fail
- Change: Removed IDB on iOS. This may impact the performance of maestro commands like tapOn and assertVisible on iOS screens with large view hierarchies.
- Studio and CLI will now provide insights and warnings in case the hierarchy of these screens becomes extensive.
- Change: In Maestro Studio, we've integrated screenshots of selected elements alongside their corresponding commands.
- Change: In Maestro Studio, double-clicking will now execute the command.
## 1.30.4 - 2023-07-19
- Fix: correctly resolve external parameters for onStart/Complete hooks
- Fix: reuse JSEngine for all executeCommands (hooks, main commands, subflows) actions
## 1.30.3 - 2023-07-17
- Update: Maestro Studio revamp improvements
- wrapped element names in sidebar
- sidebar text always visible
- add "hintText" and "accessibilityText" in sidebar
- improve sidebar search
- fixed highlight issues in search
- various other small improvements
## 1.30.2 - 2023-07-14
- Revert connection improvements (from 1.30.1)
## 1.30.1 - 2023-07-14
- Fix: Allow running `maestro studio` and `maestro test` simultaneously
- Fix: Connection improvements
## 1.30.0 - 2023-07-13
- Feature: onFlowStart / onFlowComplete hooks
- Feature: Maestro Studio revamp
- improved design
- search components panel
- improved drag-and-drop
- Feature: Introduce `--app-binary-id` parameter for Maestro Cloud upload action to be able to re-use a previously uploaded app for different flows
- Feature: Implement Experimental GraalJsEngine (ECMAScript 2022 compliant)
- Fix: Save xctest xcodebuild logs output to system temp dir
- Fix: Close existing screen recording if it was left open.
- Thanks, @carlosmuvi, for the contribution!
- Fix: Execute sequential Flows even if no other Flows are present
- Fix: Various XCTestClient connection improvements
- Deprecate: `assertOutgoingRequestsCommand`
- Deprecate: Network Mocking feature
- Deprecate: Maestro Mock Server feature
## 1.29.0 - 2023-06-19
- Feature: Add test duration measurement and display
- Feature: New screen recording commands
- Thanks, @tokou, for the contribution!
- Feature: Add support for sequential execution
- Feature: Add support for double taps + multiple taps in tapOn
- Feature: Add support for custom Android driver startup timeout
- Thanks, @arildojr7, for the contribution!
- Fix: Validate workspace prior to upload to Maestro Cloud
- Fix: Resolve Android scrollUntilVisible flakiness
- Fix: Resolve inputText flakiness
- Fix: iOS url arguments
- Thanks, @tokou, for the contribution!
## 1.28.0 - 2023-05-18
- Feature: runScript command now support conditional execution
- Feature: Improved debug output:
- Shows failure reason when command fails
- Generates screenshot when command fails
- Unified most logs under ~/.maestro/tests/<date>/maestro.log
- Change: Launch arguments support for long values
- Tweak: JUnit report naming changes. Local and Cloud should now have the same naming convention.
- Tweak: Added deprecation notice for experimental features
- Fix: maestro record command was not working on iOS
- Fix: WebDriver, only scroll to elements outside of the window before tapping
- Fix: close request leaking body
- Fix: maestro cloud now will fail on timeout if configured as such
## 1.27.0 - 2023-05-02
- Feature: Adds assertOutgoingRequests to assert the network requests from the app
- Feature: Add platform condition in runFlow command to do platform-specific orchestration. Thanks, Larry Ng for your contribution!
- Feature: Adds a new selector containsDescendants. Thanks, Larry Ng for your contribution!
- Feature: iOS and Android launch arguments
- Change: Include the update command instead of update instructions in the update message. Thanks @bobpozun for your contribution!
- Fix: Fixes swipe flakiness caused due to waiting for animations to complete on XCTest
- Fix: Correctly resolving `maestro.copiedText`
- Fix: Using deviceId instead of booted, potentially resolving XCTestUnreachable exceptions.
- Fix: Improving waitForAppToSettle for Android by accounting window updates. Resolves maestro command interaction in Android 13.
- Fix: Notification permissions not getting granted
- Fix: Use correct documentation URLs in Studio
## 1.26.1 - 2023-04-13
- Fix: hideKeyboard crashing on react native apps because swipe fails on some screens
## 1.26.0 - 2023-04-13
- Feature: Adds Travel command to mock motion for app
- Feature: Adds a capability to match the toast messages
- Feature: Add support for console.log in javascript
- Feature: Allow writing inline flows with runFlow command
- Change: Adds sms permission to permission names which can be used to allow/deny: android.permission.READ_SMS, android.permission.RECIEVE_SMS, android.permission.SEND_SMS. Thanks, @depapp for the contribution.
- Change: Maestro can now also match hint text and values of text field.
- Change: Maestro can now also match elements with their accessibility text.
- Commands moved away from IDB:
- Long press is now done with XCTest instead of idb
- Installation of app is now done with simctl commands
- Hide keyboard with help of XCTest. We now scroll up and down from the middle of the screen to close the keyboard.
- Press key now is done with XCTest.
- Note that with this change pressKey: Enter now only wraps on new line - earlier it also closed the keyboard
- Erase text is now done with XCTest.
- Use simctl to record screen
- Fix: Web driver no longer crashes when using latest Chrome
- Fix: Fixes hideKeyboard on android by appropriately dispatching proper event. Thanks, @nhaarman for contribution
- Fix: Properly shutting down studio by listening to SIGTSP signal
- Fix: Update granting of notifications and health permissions causing simulator restarts and XCTestUnreachableExceptions.
## 1.25.0 - 2023-03-13
- Fix: Shell environment variables can no longer crash the javascript runtime
- Fix: XCTestRunner and IDB are restarted on connection error
- Feature: Add support for setLocation
## 1.24.0 - 2023-03-07
- Change: LaunchApp command sets all app permissions to allow ([documentation](https://maestro.mobile.dev/reference/app-lifecycle))
- Feature: LaunchApp supports specifying app permission state
- Feature: On Android it is now possible to force links to be opened in the browser
- Fix: Autocorrect is no longer applied to inputText on iOS
- Fix: iOS apps with big view hierarchies (common with ReactNative and Flutter) caused an error in XCTest.framework
- Fix: Studio UI fixes for Firefox and Safari
- Fix: Element selection behavior in Maestro Studio
## 1.23.0 - 2023-02-15
- Feature: Maestro Studio - Action Modal
- Feature: Maestro Studio - Dark Mode
- Feature: assertion on `enabled`, `selected`, `checked`, `focused` properties ([documentation](https://maestro.mobile.dev/reference/assertions#assertvisible))
- Feature: running tests in a deterministic order ([documentation](https://maestro.mobile.dev/cli/test-suites-and-reports#deterministic-ordering))
- Feature: default global tags can now be set in `config.yaml` ([documentation](https://maestro.mobile.dev/cli/tags#global-tags))
- Feature: allow to configure what flows should be included into a run at `config.yaml` level ([documentation](https://maestro.mobile.dev/cli/test-suites-and-reports#controlling-what-tests-to-include))
- Tweak: considerable speed-up of iOS tests due to removal of unnecessary hierarchy polling
- Tweak: wait for app to settle before proceeding with iOS test
- Tweak: UX improvements in "delete command" confirmation dialog
- Tweak: using `xcrun` for uninstall command on iOS
- Tweak: using `xcrun` for clearKeychain command on iOS
- Tweak: using `.maestro` directory by default for mockserver deploy command
- Fix: errors were clipped in Maestro Studio
- Fix: use element title as id in Web driver
- Fix: Repeat-while-true did not work properly with JavaScript conditions
- Fix: Repeat-times did not work properly with JavaScript input
- Fix: added artificial delay after key presses (i.e. "back" key) on Android
## 1.22.1 - 2023-02-09
- Early Access Feature: Maestro Mock Server and Maestro SDK (Android preview)
- Tweak: added visibility threshold and scroll speed to `scrollUntilVisible` command
- Tweak: speed up `tapOn` command on iOS
- Fix: removing view hierarchy elements that are out of screen bounds
- Fix: `inputText` command skipping characters on iOS
- Fix: Reworked `clearAppState` behaviour on iOS, solving issue that caused crashes after clearing the state
- Fix: crash when running multiple Maestro sessions in parallel while using iOS device
- Fix: a rare crash in React Native apps when trying to input a long string on iOS
- Fix: properly handling linebreaks in Maestro Studio
## 1.21.3 - 2023-01-30
- Fix: `scrollUntilVisible` was not always working on iOS
- Tweak: speed up tests by skipping an unnecessary hierarchy poll
- Tweak: iOS screenshot no longer depends on IDB and is faster
## 1.21.2 - 2023-01-26
- Hotfix: Move iOS tap() implementation back to IDB to resolve problems with React Native apps
- Fix: running multiple Maestro instances would sometimes result in Connection exception
- Fix: support JS injection in `scrollUntilVisible` command
## 1.21.1 - 2023-01-25
- Fix: Increase typing speed for iOS text input
## 1.21.0 - 2023-01-25
- Feature: Next evolution of Maestro Studio
- Fix: More robust implementation of inputText on iOS
- Fix: More robust implementation of tap on iOS
- Experimental: Added web driver
## 1.20.0 - 2023-01-24
- Feature: Maestro Studio - use percentage-based swiping
- Feature: Scroll until view element is visible
- Feature: Relatively swipe with percentage based start and end coordinates
- Fix: Android tap was not always working
- Fix: Bottom of Android hierarchy was cut off
- Fix: idb_companion fails to start due to gRPC timeout exception
- Tweak: Improve Android Screenshot Internal Logic
- Tweak: Change the end coordinates for swipe element
- Tweak: Update sample flows
## 1.19.5 - 2023-01-19
- Fix: inputText was not working on iOS React Native apps
- Fix: Maestro fails to launch on iOS if --device parameter is present
- Fix: Evaluate JS scripts with element selector in swipe command
- Tweak: added tags to sample flows
- Tweak: indicating whether build is running on CI in analytics
## 1.19.2 - 2023-01-17
- Hotfix: Maestro Studio was not working
## 1.19.1 - 2023-01-17
- Feature: generating test report from `maestro cloud` output
- Fix: in rare cases, maestro cloud was computing progress bar as negative value
- Fix: local test suite included non-flow files
- Fix: some special characters were not allowed in env variables (i.e. `&`)
- Fix: vertical scrolling was sometimes not working on iOS
- Fix: if a text string is an invalid regex, treat it as a regular string instead
- Fix: scroll and swipe commands on iOS were throwing an error when running in parallel with Maestro Studio
- Tweak: print out valid inputs for `--format` parameter in `maestro test` and `maestro upload`
- Tweak: removed Maestro Studio warning related to parallel execution
- Refactor: making XCTestDriver configurable
## 1.19.0 - 2023-01-13
- Feature: iOS unicode input support + non-English keyboards
- Feature: `swipe` command now supports `from` argument to swipe from a given view
- Feature: `repeat` command now supports `while` condition
- Feature: Allowing `extendedWaitUntil` command to use env values in `timeout` property
- Tweak: assert commands now respect `optional` flag
- Tweak: error analytics
- Fix: scroll not working reliably on iOS
- Fix: `openLink` was opening Google Maps on Android
- Fix: sub-flows are now included regardless of their tags
- Fix: Maestro Studio was not always computing `index` field correctly
- Fix: `maestro upload` was ignoring JS files
- Fix: `openLink` command now supports query parameters
## 1.18.5 - 2023-01-10
- Feature: tags
- Tweak: allow running other maestro commands alongside Maestro Studio
- Tweak: improved matching for strings with linebreaks
- Fix: creating maestro logs directory was not always working properly
- Fix: maestro studio was not working properly on Kubuntu
## 1.18.3 - 2022-12-27
- XCUITest driver improvements and fixes:
- Close the response when validating server up
- Add logs to uninstall of runner
- Remove redundant import and library from maestro-ios
- Kills the process before we uninstall it
- Redirect runner logs in xctest_runner_logs directory
## 1.18.2 - 2022-12-27
- Fix: Wait for XCUITest server to start before proceeding
## 1.18.1 - 2022-12-27
- Fix: Create XCUITest driver HTTP server on loopback address
- Fix: Create parity with idb for `text` attribute with following priority:
- Title
- Label
- Value
## 1.18.0 - 2022-12-26
- Feature: Adds new XcUITest driver to capture view hierarchy on iOS.
- Fixes stability issues on iOS 16
- Fixes not identified bottom navigation tabs
- Gets view hierarchy natively from XCUITest
- Fix: Missing letter j and y in inputRandomText command
- Tweak: Un-deprecate the hierarchy command, inform about Studio
- Tweak: Match negative bounds as well in maestro studio
- Feature: Adds replay functionality in maestro studio
- Feature: Adding device interaction to interact page in Maestro Studio
## 1.17.4 - 2022-12-15
- Fix: Maestro commands were failing if Android SDK wasn't installed
## 1.17.3 - 2022-12-15
- Feature: no-ansi version for terminals that do not ANSI
- Feature: Android Maven artifact for setting up network mocking
- Fix: Android emulator was not discovered properly if it wasn't on PATH
- Fix: missing favicon
## 1.17.2 - 2022-12-13
- Tweak: Deprecate hierarchy and query CLI commands
## 1.17.1 - 2022-12-12
- Tweak: Remove Maestro Studio icon from Mac dock
- Tweak: Prefer port 9999 for Maestro Studio app
- Fix: Fix Maestro Studio conditional code snippet
## 1.17.0 - 2022-12-12
- Feature: Maestro Studio
- Feature: Print a message when an update is available
- Feature: Support percentages for tapOn
- Fix: Maestro commands execute faster now
- Fix: Fix environment variable substitution in certain cases
- Fix: Use actual android device screen size (including nav bar)
## 1.16.4 - 2022-12-02
- Fix: Add error message for when an Android screen recording fails
## 1.16.3 - 2022-12-02
- Fix: Fix iOS `clearState` not working in certain cases
- Fix: Fix `maestro record` not capturing full launch screen recording
## 1.16.2 - 2022-12-02
- Fix: older version of Maestro Driver on Android was not always updated
## 1.16.1 - 2022-11-30
- Feature: `maestro record` command
- Fix: `z` character was not inputted correctly on Android
## 1.16.0 - 2022-11-29
- Feature: Javascript injection support
- `runScript` and `evalScript` commands to run scripts
- `assertTrue` command to assert based on Javascript
- `runFlow` can be launched based on Javascript condition
- `copyTextFrom` now also stores result in `maestro.copiedText` variable
- Env parameters are now treated as Javascript variables
- Feature: HTTP(s) requests
- `http.request()` Javascript API that allows to make HTTP requests as part of Maestro flows
- Feature: Maestro Cloud `--android-api-level` parameter to select API version to be used
- Feature: `waitForAnimationToEnd` command to wait until animations/videos are finished
- Tweak: test reports can now be generated for single test runs (and not just folders)
- Tweak: `inputText` on Android was reworked to increase speed and input stability
- Tweak: `eraseText` is now much faster
- Tweak: `maestro cloud` will automatically retry upload up to 3 times
- Fix: running on Samsung devices was sometimes failing because of wrong user being used
## 1.15.0 - 2022-11-17
- Feature: run all tests in a folder as a suite
- Feature: XML test report in JUnit-compatible format
- Feature: `copyTextFrom` command for copying text from a view
- Feature: `maestro bugreport` command for capturing Maestro logs
- **Breaking change**: Removed `clipboardPaste` command in favour of new `pasteText` command
- Fix: Java 8 compatibility issue for M1 users
- Fix: `_` character was mapped incorrectly on iOS
- Fix: first `tapOn` command was failing unless it was preceded by `launchApp` or `openLink`
- Tweak: Maestro no longer kills running `idb_companion` processes
- Tweak: updated gRPC version to 1.52.0
## 1.14.0 - 2022-11-14
- Fix: passing env parameters to subflows and other env params
- Speeding up maestro flows
- Checking in maestro sample flows and adds sample updating guide
- Maestro is now compatible with java 8!
- Launching app without stopping the app
- Fixing launching app when resolving launcher activity throws `NullPointerException`
## 1.13.2 - 2022-11-10
- Fix: Fallback properly on monkey when start-activity command fails, when launching app.
## 1.13.1 - 2022-11-09
- Fix: Fix maestro hanging with message "Waiting for idb service to start.."
- Fix: Fix clearState operation not working on iOS
## 1.13.0 - 2022-11-08
- Feature: Option to set direction and speed for swipe command
- Fix: Fix duplicate and unavailable iOS simulators in list
- Fix: Longer timeout for iOS simulator boot
## 1.12.0 - 2022-11-06
- Feature: `maestro cloud` command added
## 1.11.4 - 2022-11-02
- Fix: Use absolute path to prevent NullPointerException when .app folder is in the cwd
- Fix: Create parent directory if not exists when generating adb key pair, updates dadb to 1.2.6
- Fix: Opening of leak canary app
- Tweak: send agent: ci when known CI environment variables are set
## 1.11.3 - 2022-10-29
- Fix: updating to dadb 1.2.4
## 1.11.2 - 2022-10-29
- Fix: updating to dadb 1.2.3 to fix an occasional device connection issue
- Fix: injecting `env` parameters into conditions (i.e. in `runFlow`)
## 1.11.1 - 2022-10-27
- Fix: closing `idb_companion` after `maestro` completes
## 1.11.0 - 2022-10-26
- Feature: `maestro` will offer user to select a device if one is not running already
- Feature: `env` variables can be inlined in flow file or in `runFlow` command
- **Breaking change**: `--platform` option is deprecated. CLI now prompts user to pick a device.
- Tweak: auto-starting `idb_companion`. No need to start it manually anymore.
- Tweak: tripled Android Driver launch timeout
- Tweak: customisable error resolution in Orchestra
- Fix: `maestro upload` was not ignoring `-e` parameters
## 1.10.1 - 2022-10-12
- Fix: login command fails with java.lang.IllegalStateException: closed
## 1.10.0 - 2022-10-12
- Feature: `repeat` command that allows to create loops
- Feature: conditional `runFlow` execution that allows to create if-conditions
- Feature: `inputRandomText`, `inputRandomNumber`, `inputRandomEmail` and `inputRandomPersonName` commands (thanks @ttpho !)
- Feature: `clipboardPaste` command (thanks @depapp !)
- Feature: Added `enabled` property to element selector
- Feature: Added `download-samples` command to allow quickstart without having to build your own app
- Feature: Added `login` and `logout` commands for interacting with mobile.dev
- **Breaking change:** `upload` now takes 1 less argument. `uploadName` parameter was replaced with `--name` optional argument
- Tweak: `upload` command automatically zips iOS apps
- Tweak: sending `agent: cli` value alongside `upload` and `login` commands
- Fix: properly compare fields that contain regex special symbols
- Fix: input text on Android was sometimes missing characters
## 1.9.0 - 2022-09-30
- Feature: USB support for Android devices
## 1.8.3 - 2022-09-28
- Fix: occasional crash when an iOS layout has a view group with a 0 width
- Fix: properly mapping top-level syntax errors
## 1.8.2 - 2022-09-27
- Tweak: prioritise clickable elements over non-clickable ones
- Fix: close TCP forwarder if it is already in use
- Fix: hideKeyboard on Android did not always work
## 1.8.1 - 2022-09-27
- Fix: Timeout exception while opening port for tcp forwarding
## 1.8.0 - 2022-09-22
- Feature: `runFlow` command
- Tweak: support of Tab Bar on iOS
- Tweak: added `--mapping` option to `upload` CLI command
- Fix: open the main launcher screen on Android instead of Leak Canary
- Fix: input character-by-character on Android to counter adb issue where not the whole text gets transferred to the device
## 1.7.2 - 2022-09-20
- Fix: `tapOn` command was failing due to a failure during screenshot capture
## 1.7.1 - 2022-09-19
- Feature: `clearState` command
- Feature: `clearKeychain` command
- Feature: `stopApp` command
- Tweak: Maestro now compares screenshots to decide whether screen has been updated
- Tweak: `launchApp` command now supports env parameters
## 1.7.0 - 2022-09-16
- Feature: `maestro upload` command for uploading your builds to mobile.dev
- Feature: `takeScreenshot` command
- Feature: `extendedWaitUntil` command
- Fix: waiting for Android gRPC server to properly start before interacting with it
- Fix: brought back multi-window support on Android
- Fix: `hideKeyboard` command did not always work
- Fix: make project buildable on Java 14
- Refactoring: make `MaestroCommand` serializable without custom adapters
- Refactoring: migrated to JUnit 5
## 1.6.0 - 2022-09-13
- Feature: hideKeyboard command
- Feature: add Android TV Remote navigation
- Tweak: allowing to skip package name when searching by `id`
- Fix: Android WebView contents were sometimes not reported as part of the view hierarchy
- Fix: iOS inputText race condition
- Fix: populate iOS accessibility value
- Refactoring: simplified `MaestroCommand` serialization
## 1.5.0 - 2022-09-08
- Temporary fix: showing an error when unicode characters are passed to `inputText`
- Feature: `eraseText` command
## 1.4.2 - 2022-09-06
- Fix: Android devices were not discoverable in some cases
## 1.4.1 - 2022-09-05
- Fix: relative position selectors (i.e. `below`) were sometimes picking a wrong view
- Fix: await channel termination when closing a gRPC ManagedChannel
- Fix: Android `inputText` did not work properly when a string had whitespaces in it
- Fix: race condition in iOS `inputText`
## 1.4.0 - 2022-08-29
- Added `traits` selector.
- Relative selectors (such as `above`, `below`, etc.) are now picking the closest element.
- Fix: continuous mode did not work for paths without a parent directory
- Fix: workaround for UiAutomator content descriptor crash
- Fix: `tapOn: {int}` did not work
## 1.3.6 - 2022-08-25
- Added `longPressOn` command
- Decreased wait time in apps that have a screen overlay
- Fixed CLI issue where status updates would not propagate correctly
## 1.3.3 - 2022-08-23
- Fix: iOS accessibility was not propagated to Maestro
## 1.3.2 - 2022-08-22
- Fix: env parameters did not work with init flows when using `Maestro` programmatically
## 1.3.1 - 2022-08-19
- Added support for externally supplied parameters
- Added `openLink` command
## 1.2.6 - 2022-08-18
- Fail launching an iOS app if the app is already running
## 1.2.4 - 2022-08-17
- Add support for cli to specify what platform, host and port to connect to
## 1.2.3 - 2022-08-15
- Added support of iOS state restoration
- Exposing `appId` field as part of `MaestroConfig`
## 1.2.2 - 2022-08-08
- Update `Orchestra` to support state restoration
## 1.2.1 - 2022-08-04
- Update `YamlCommandReader` to accept Paths instead of Files to support zip Filesystems
## 1.2.0 - 2022-08-04
- Config is now defined via a document separator
- launchApp no longer requires and appId
- initFlow config implemented
## 1.1.0 - 2022-07-28
- `launchApp` command now can optionally clear app state
- `config` command to allow Orchestra consumers a higher degree of customization
- Fixed a bug where `ElementNotFound` hierarchy field was not declared as public
## 1.0.0 - 2022-07-20
- Initial Maestro release (formerly known as Conductor)
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Maestro
Thank you for considering contributing to the project!
We welcome contributions from everyone and generally try to be as accommodating as possible. However, to make sure that your time is well spent, we separate the types of
contributions in the following types:
- Type A: Simple fixes (bugs, typos) and cleanups
- You can open a pull request directly, chances are high (though never guaranteed) that it will be merged.
- Type B: Features and major changes (i.e. refactoring)
- Unless you feel adventurous and wouldn't mind discarding your work in the worst-case scenario, we advise to open an issue or a PR with a suggestion first where you will
describe the problem you are trying to solve and the solution you have in mind. This will allow us to discuss the problem and the solution you have in mind.
### Side-note on refactoring
Our opinion on refactorings is generally that of - don't fix it if it isn't broken. Though we acknowledge that there are multiple areas where code could've been structured in a
cleaner way, we believe there are no massive tech debt issues in the codebase. As each change has a probability of introducing a problem (despite all the test coverage), be
mindful of that when working on a refactoring and have a strong justification prepared.
## Lead times
We strive towards having all public PRs reviewed within a week, typically even faster than that. If you believe that your PR requires more urgency, please contact us on a
public Maestro Slack channel.
Once your PR is merged, it usually takes about a week until it becomes publicly available and included into the next release.
## Developing
### Requirements
Maestro's minimal deployment target is Java 17, and for development, you need to use Java 17 or newer.
If you made changes to the CLI, rebuilt it with `./gradlew :maestro-cli:installDist`. This will generate a startup shell
script in `./maestro-cli/build/install/maestro/bin/maestro`. Use it instead of globally installed `maestro`.
If you made changes to the iOS XCTest runner app, make sure they are compatible with the version of Xcode used by the GitHub Actions build step. It is currently built using the default version of Xcode listed in the macos runner image [readme][macos_builder_readme].
If you introduce changes that work locally but fail to build when you make a PR, check if you used a feature used in a newer version of Swift or some other new Xcode setting.
### Debugging
Maestro stores logs for every test run in the following locations:
- CLI Logs: `~/.maestro/tests/*/maestro.log`
- iOS test runner logs: `~/Library/Logs/maestro/xctest_runner_logs`
### Android artifacts
Maestro requires 2 artifacts to run on Android:
- `maestro-app.apk` - the host app. Does nothing.
- `maestro-server.apk` - the test runner app. Starts an HTTP server inside an infinite JUnit/UIAutomator test.
These artifacts are built by `./gradlew :maestro-android:assemble` and `./gradlew :maestro-android:assembleAndroidTest`, respectively.
They are placed in `maestro-android/build/outputs/apk`, and are copied over to `maestro-client/src/main/resources`.
### iOS artifacts
Maestro requires 3 artifacts to run on iOS:
- `maestro-driver-ios` - the host app for the test runner. Does nothing and is not installed.
- `maestro-driver-iosUITests-Runner.app` - the test runner app. Starts an HTTP server inside an infinite XCTest.
- `maestro-driver-ios-config.xctestrun` - the configuration file required to run the test runner app.
These artifacts are built by the `build-maestro-ios-runner.sh` script. It places them in `maestro-ios-driver/src/main/resources`.
### Running standalone iOS XCTest runner app
The iOS XCTest runner can be run without Maestro CLI. To do so, make sure you built the artifacts, and then run:
```console
./maestro-ios-xctest-runner/run-maestro-ios-runner.sh
```
This will use `xcodebuild test-without-building` to run the test runner on the connected iOS device. Now, you can reach
the HTTP server that runs inside the XCTest runner app (by default on port 22087):
```console
curl -fsSL -X GET localhost:22087/deviceInfo | jq
```
<details>
<summary>See example output</summary>
```json
{
"heightPoints": 852,
"heightPixels": 2556,
"widthPixels": 1179,
"widthPoints": 393
}
```
</details>
```console
curl -fsSL -X POST localhost:22087/touch -d '
{
"x": 150,
"y": 150,
"duration": 0.2
}'
```
```console
curl -sSL -X GET localhost:22087/swipe -d '
{
"startX": 150,
"startY": 426,
"endX": 426,
"endY": 350,
"duration": 1
}'
```
### Artifacts and the CLI
`maestro-cli` depends on both `maestro-ios-driver` and `maestro-client`. This is how the CLI gets these artifacts.
## Linting
```bash
./gradlew detekt # Run detekt code quality checks
./gradlew detektMain # Run detekt with type resolution
./gradlew detektBaseline # Generate baseline
```
## Testing
There are 3 ways to test your changes:
- Integration tests
- Run them via `./gradlew :maestro-test:test` (or from IDE)
- Tests are using real implementation of most components except for `Driver`. We use `FakeDriver` which pretends to be a real device.
- Manual testing
- Run `./maestro` instead of `maestro` to use your local code.
- Unit tests
- All the other tests in the projects. Run them via `./gradlew test` (or from IDE)
If you made changes to the iOS XCUITest driver, rebuild it by running `./maestro-ios-xctest-runner/build-maestro-ios-runner.sh`.
## Module structure
| Module | Purpose |
|--------|---------|
| `maestro-cli` | CLI entry point and user-facing commands |
| `maestro-client` | `Maestro` class, `Driver` interface, core API |
| `maestro-orchestra` | Flow execution, YAML parsing, scripting |
| `maestro-orchestra-models` | Command data classes (serializable) |
| `maestro-android` | Android driver implementation |
| `maestro-ios` | iOS driver implementation |
| `maestro-ios-xctest-runner` | Swift/Xcode XCTest runner app |
| `maestro-web` | Web/CDP driver implementation |
| `maestro-studio` | Studio IDE server |
| `maestro-ai` | AI-powered test capabilities |
| `maestro-test` | `FakeDriver` and testing utilities |
| `maestro-utils` | Shared utilities |
| `maestro-proto` | Protocol buffer definitions |
| `e2e` | End-to-end test suites |
### Processing flow
```
YAML Flow File → YamlCommandReader → List<MaestroCommand> → Orchestra.executeFlow() → Maestro API → Driver → Device
```
## Architectural considerations
Keep the following things in mind when working on a PR:
- `Maestro` class is serving as a target-agnostic API between you and the device.
- `Maestro` itself should not know or care about the concept of commands.
- `Orchestra` class is a layer that translates Maestro commands (represented by `MaestroCommand`) to actual calls to `Maestro` API.
- `Maestro` and `Orchestra` classes should remain completely target (Android/iOS/Web) agnostic.
- Use `Driver` interface to provide target-specific functionality.
- Maestro commands should be as platform-agnostic as possible, though we do allow for exceptions where they are justified.
- Maestro CLI is supposed to be cross-platform (Mac OS, Linux, Windows).
- Maestro is designed to run locally as well as on Maestro Cloud. That means that code should assume that it is running in a sandbox environment and shouldn't call out or spawn
arbitrary processes based on user's input
- For that reason we are not allowing execution of bash scripts from Maestro commands.
- For that reason, `MaestroCommand` class should be JSON-serializable (and is a reason we haven't moved to `sealed class`)
- Prefer fakes over mocks (e.g. `FakeDriver`). Mocks (MockK) are used in some modules but fakes are the preferred approach for driver-level testing.
This graph (generated with [`./gradlew :generateDependencyGraph`][graph_plugin] in [PR #1834][pr_1834]) may be helpful
to visualize relations between subprojects:

## How to
### Add new command
Follow these steps:
- Define a new command in `Commands.kt` file, implementing `Command` interface.
- Add a new field to `MaestroCommand` class, following the example set by other commands.
- Add a new field to `YamlFluentCommand` to map between yaml representation and `MaestroCommand` representation.
- Handle command in `Orchestra` class.
- If this is a new functionality, you might need to add new methods to `Maestro` and `Driver` APIs.
- Add a new test to `IntegrationTest`.
[macos_builder_readme]: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
[graph_plugin]: https://github.com/vanniktech/gradle-dependency-graph-generator-plugin
[pr_1834]: https://github.com/mobile-dev-inc/maestro/pull/1834
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
> [!TIP]
> Great things happen when testers connect — [Join the Maestro Community](https://maestrodev.typeform.com/to/FelIEe8A)
<p align="center">
<a href="https://www.maestro.dev">
<img width="1200" alt="Maestro logo" src="https://github.com/mobile-dev-inc/Maestro/blob/main/assets/banne_logo.png" />
</a>
</p>
<p align="center">
<strong>Maestro</strong> is an open-source framework that makes UI and end-to-end testing for Android, iOS, and web apps simple and fast.<br/>
Write your first test in under five minutes using YAML flows and run them on any emulator, simulator, or browser.
</p>
<img src="https://user-images.githubusercontent.com/847683/187275009-ddbdf963-ce1d-4e07-ac08-b10f145e8894.gif" />
---
## Table of Contents
- [Why Maestro?](#why-maestro)
- [Getting Started](#getting-started)
- [Resources & Community](#resources--community)
- [Contributing](#contributing)
- [Maestro Studio – Test IDE](#maestro-studio--test-ide)
- [Maestro Cloud – Parallel Execution & Scalability](#maestro-cloud--parallel-execution--scalability)
---
## Why Maestro?
Maestro is built on learnings from its predecessors (Appium, Espresso, UIAutomator, XCTest, Selenium, Playwright) and allows you to easily define and test your Flows.
By combining a human-readable YAML syntax with an interpreted execution engine, it lets you write, run, and scale cross-platform end-to-end tests for mobile and web with ease.
- **Cross-platform coverage** – test Android, iOS, and web apps (React Native, Flutter, hybrid) on emulators, simulators, or real devices.
- **Human-readable YAML flows** – express interactions as commands like `launchApp`, `tapOn`, and `assertVisible`.
- **Resilience & smart waiting** – built-in flakiness tolerance and automatic waiting handle dynamic UIs without manual `sleep()` calls.
- **Fast iteration & simple install** – flows are interpreted (no compilation) and installation is a single script.
**Simple Example:**
```
# flow_contacts_android.yaml
appId: com.android.contacts
---
- launchApp
- tapOn: "Create new contact"
- tapOn: "First Name"
- inputText: "John"
- tapOn: "Last Name"
- inputText: "Snow"
- tapOn: "Save"
```
---
## Getting Started
Maestro requires Java 17 or higher to be installed on your system. You can verify your Java version by running:
```
java -version
```
Installing the CLI:
Run the following command to install Maestro on macOS, Linux or Windows (WSL):
```
curl -fsSL "https://get.maestro.mobile.dev" | bash
```
The links below will guide you through the next steps.
- [Installing Maestro](https://docs.maestro.dev/getting-started/installing-maestro) (includes regular Windows installation)
- [Build and install your app](https://docs.maestro.dev/getting-started/build-and-install-your-app)
- [Run a sample flow](https://docs.maestro.dev/getting-started/run-a-sample-flow)
- [Writing your first flow](https://docs.maestro.dev/getting-started/writing-your-first-flow)
---
## Resources & Community
- 💬 [Join the Slack Community](https://maestrodev.typeform.com/to/FelIEe8A)
- 📘 [Documentation](https://docs.maestro.dev)
- 📰 [Blog](https://maestro.dev/blog?utm_source=github-readme)
- 🐦 [Follow us on X](https://twitter.com/maestro__dev)
---
## Contributing
Maestro is open-source under the Apache 2.0 license — contributions are welcome!
- Check [good first issues](https://github.com/mobile-dev-inc/maestro/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
- Read the [Contribution Guide](https://github.com/mobile-dev-inc/Maestro/blob/main/CONTRIBUTING.md)
- Fork, create a branch, and open a Pull Request.
If you find Maestro useful, ⭐ star the repository to support the project.
---
## Maestro Studio – Test IDE
**Maestro Studio Desktop** is a lightweight IDE that lets you design and execute tests visually — no terminal needed.
It is also free, even though Studio is not an open-source project. So you won't find the Maestro Studio code here.
- **Simple setup** – just download the native app for macOS, Windows, or Linux.
- **Visual flow builder & inspector** – record interactions, inspect elements, and build flows visually.
- **AI assistance** – use MaestroGPT to generate commands and answer questions while authoring tests.
[Download Maestro Studio](https://maestro.dev/?utm_source=github-readme#maestro-studio)
---
## Maestro Cloud – Parallel Execution & Scalability
When your test suite grows, run hundreds of tests in parallel on dedicated infrastructure, cutting execution times by up to 90%. Includes built-in notifications, deterministic environments, and complete debugging tools.
Pricing for Maestro Cloud is completely transparent and can be found on the [pricing page](https://maestro.dev/pricing?utm_source=github-readme).
👉 [Start your free 7-day trial](https://maestro.dev/cloud?utm_source=github-readme)
```
Built with ❤️ by Maestro.dev
```
================================================
FILE: RELEASING.md
================================================
# Production Releases
## Prepare
1. Define the next semantic version
Semantic versioning: a.b.c
- a: major breaking changes
- b: new functionality, new features
- c: any other small changes
2. Checkout the main branch and make sure it is up-to-date: `git checkout main && git pull`
3. Create a new branch
4. Update the CHANGELOG.md file with changes of this release, you should add a new section with your version number and the relevant updates, like the ones that exist on the previous versions
5. Change the version in `gradle.properties`
6. Change the version in `maestro-cli/gradle.properties`
7. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version)
8. Submit a PR with the changes against the main branch
9. Merge the PR
## Tag
1. `git tag -a vX.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version)
2. `git push --tags`
3. Wait until all Publish actions have completed https://github.com/mobile-dev-inc/maestro/actions
## Publish Maven Central
1. Trigger the [Publish Release action](https://github.com/mobile-dev-inc/maestro/actions/workflows/publish-release.yml)
- ATTENTION: Wait for it to finish
3. Go to [OSS Sonatype](https://s01.oss.sonatype.org/) and login with user/password
4. Go to Staging Repositories, select the repository uploaded from the trigger above.
5. Click "Close" and then "Release". Each of these operations take a couple minutes to complete
____________________________________________________________________________________________________________________________________________________
**CAUTION:** You should go back to the [notion document](https://www.notion.so/Maestro-Release-Run-Book-78159c6f80de4492a6e9e05bb490cf60?pvs=4) to see how to update the **Robin** and **Maestro Cloud** versions before updating the **CLI**
____________________________________________________________________________________________________________________________________________________
## Publish CLI
1. Trigger the [Publish CLI Github action](https://github.com/mobile-dev-inc/Maestro/actions/workflows/publish-cli.yaml)
2. Test installing the cli by running `curl -Ls "https://get.maestro.mobile.dev" | bash`
3. Check the version number `maestro --version`
================================================
FILE: build.gradle.kts
================================================
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.protobuf) apply false
alias(libs.plugins.mavenPublish)
alias(libs.plugins.detekt)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.named("compileKotlin", KotlinCompilationTask::class.java) {
compilerOptions {
freeCompilerArgs.addAll("-Xjdk-release=17")
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
detekt {
buildUponDefaultConfig = true
allRules = false
autoCorrect = true
config = files("${rootDir}/detekt.yml")
}
================================================
FILE: detekt.yml
================================================
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors:
active: true
exclude:
- 'DetektProgressListener'
# - 'KtFileCountProcessor'
# - 'PackageCountProcessor'
# - 'ClassCountProcessor'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ProjectComplexityProcessor'
# - 'ProjectCognitiveComplexityProcessor'
# - 'ProjectLLOCProcessor'
# - 'ProjectCLOCProcessor'
# - 'ProjectLOCProcessor'
# - 'ProjectSLOCProcessor'
# - 'LicenseHeaderLoaderExtension'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
# - 'FindingsReport'
- 'FileBasedFindingsReport'
- 'LiteFindingsReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
comments:
active: true
AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
OutdatedDocumentation:
active: false
matchTypeParameters: true
matchDeclarationsOrder: true
UndocumentedPublicClass:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
UndocumentedPublicProperty:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: [ ]
LargeClass:
active: true
threshold: 600
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
LongMethod:
active: true
threshold: 120
LongParameterList:
active: false
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotatedParameter: [ ]
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
NestedBlockDepth:
active: true
threshold: 4
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: false
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: false
SuspendFunWithFlowReturnType:
active: false
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: true
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
formatting:
active: true
android: false
autoCorrect: true
AnnotationOnSeparateLine:
active: false
autoCorrect: true
AnnotationSpacing:
active: false
autoCorrect: true
ArgumentListWrapping:
active: false
autoCorrect: true
indentSize: 4
maxLineLength: 160
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
EnumEntryNameCase:
active: false
autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
insertFinalNewLine: true
ImportOrdering:
active: true
autoCorrect: true
layout: '*,java.**,javax.**,kotlin.**,^'
Indentation:
active: true
autoCorrect: true
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
maxLineLength: 160
ignoreBackTickedIdentifier: false
ModifierOrdering:
active: true
autoCorrect: true
MultiLineIfElse:
active: false
autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
NoConsecutiveBlankLines:
active: true
autoCorrect: true
NoEmptyClassBody:
active: true
autoCorrect: true
NoEmptyFirstLineInMethodBlock:
active: false
autoCorrect: true
NoLineBreakAfterElse:
active: true
autoCorrect: true
NoLineBreakBeforeAssignment:
active: true
autoCorrect: true
NoMultipleSpaces:
active: true
autoCorrect: true
NoSemicolons:
active: true
autoCorrect: true
NoTrailingSpaces:
active: true
autoCorrect: true
NoUnitReturn:
active: true
autoCorrect: true
NoUnusedImports:
active: true
autoCorrect: true
NoWildcardImports:
active: true
PackageName:
active: false
autoCorrect: true
ParameterListWrapping:
active: true
autoCorrect: true
indentSize: 4
maxLineLength: 160
SpacingAroundAngleBrackets:
active: false
autoCorrect: true
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundComma:
active: true
autoCorrect: true
SpacingAroundCurly:
active: true
autoCorrect: true
SpacingAroundDot:
active: true
autoCorrect: true
SpacingAroundDoubleColon:
active: false
autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
SpacingAroundOperators:
active: true
autoCorrect: true
SpacingAroundParens:
active: true
autoCorrect: true
SpacingAroundRangeOperator:
active: true
autoCorrect: true
SpacingAroundUnaryOperator:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithAnnotations:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithComments:
active: false
autoCorrect: true
StringTemplate:
active: true
autoCorrect: true
naming:
active: true
BooleanPropertyNaming:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
allowedPattern: '^(is|has|are)'
ClassNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
forbiddenName: [ ]
FunctionMaxLength:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
rootPackage: ''
LambdaParameterNaming:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NoNameShadowing:
active: false
NonBooleanPropertyPrefixedWithIs:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
ObjectPropertyNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
maximumVariableNameLength: 64
VariableMinLength:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
minimumVariableNameLength: 1
VariableNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: true
ForEachOnRange:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
SpreadOperator:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: false
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
active: false
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: false
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: false
IgnoredReturnValue:
active: false
restrictToAnnotatedMethods: true
returnValueAnnotations:
- '*.CheckResult'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- '*.CanIgnoreReturnValue'
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: false
MissingPackageDeclaration:
active: false
excludes: [ '**/*.kts' ]
MissingWhenCase:
active: true
allowElseExpression: true
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnsafeCast:
active: true
UnusedUnaryOperator:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: true
style:
active: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: false
maxDestructuringEntries: 3
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values:
- 'FIXME:'
- 'STOPSHIP:'
- 'TODO:'
allowedPatterns: ''
customMessage: ''
ForbiddenImport:
active: false
imports: [ ]
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- 'kotlin.io.print'
- 'kotlin.io.println'
ForbiddenPublicDataClass:
active: true
excludes: [ '**' ]
ignorePackages:
- '*.internal'
- '*.internal.*'
ForbiddenVoid:
active: false
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: ''
LibraryCodeMustSpecifyReturnType:
active: true
excludes: [ '**' ]
LibraryEntitiesShouldNotBePublic:
active: true
excludes: [ '**' ]
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesIfStatements:
active: false
MandatoryBracesLoops:
active: false
MaxLineLength:
active: true
maxLineLength: 160
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
active: false
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
ObjectLiteralToLambda:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: false
max: 2
excludedFunctions: 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: false
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableLength: 4
UnnecessaryAbstractClass:
active: false
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryFilter:
active: false
UnnecessaryInheritance:
active: true
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: '(_|ignored|expected|serialVersionUID)'
UseAnyOrNoneInsteadOfFind:
active: false
UseArrayLiteralsInAnnotations:
active: false
UseCheckNotNull:
active: false
UseCheckOrError:
active: false
UseDataClass:
active: false
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: false
UseOrEmpty:
active: false
UseRequire:
active: false
UseRequireNotNull:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
WildcardImport:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
excludeImports:
- 'java.util.*'
================================================
FILE: e2e/.gitignore
================================================
apps/
samples/
samples.zip
================================================
FILE: e2e/README.md
================================================
# e2e
This directory contains glue code for testing Maestro itself.
## Testing
Typical workflow is:
1. Start Android emulator and iOS simulator
2. `download_apps`
3. `install_apps`
4. `run_tests`
We try to keep shell code in separate files, so we don't get too tightly coupled
to GitHub Actions.
### Expected failures
Let's say a critical bug is introduced that causes Maestro to always mark all
tests as passed. If our e2e test suite only was only checking if all tests pass
(i.e. `maestro test` exit code is 0), then wouldn't catch such a bug.
To prevent this, all flows in this directory MUST have a `passing` or `failing`
label, so the correct outcome can be asserted.
## Samples
This directory also contains samples that are downloaded by the `maestro download-samples` command,
and some glue code to facilitate updating those samples.
`maestro download-samples` provides a set of flows and apps so that users can
quickly try out Maestro, without having to write any flows for their own app.
`download-samples` downloads these files and apps from our publicly-available
Google Cloud Storage bucket (hosted on `storage.googleapis.com`).
### Intro
The samples are automatically updated by the GitHub Action on every new commit
to the `main` branch.
There zip archive that is downloaded by `download-samples` consists of 2 things:
- the Maestro workspace with flows (located in the `workspaces/wikipedia` directory)
- the app binary files that are used in the flows (located in the `apps` directory)
App binary files are heavy, so we don't store them in the repository. Instead, they are hosted
on publicly available directory in Google Cloud Storage:
### Update the samples
Run the script:
```console
./update_samples
```
================================================
FILE: e2e/download_apps
================================================
#!/usr/bin/env sh
set -eu
# Download apps from URLs listed in manifest.txt.
#
# We assume that if the downloaded file is a zip file, it's an iOS app and must
# be unzipped.
[ "$(basename "$PWD")" = "e2e" ] || { echo "must be run from e2e directory" && exit 1; }
command -v curl >/dev/null 2>&1 || { echo "curl is required" && exit 1; }
platform="${1:-}" # android or ios or an empty string (no filter)
platform="$(echo "$platform" | tr '[:upper:]' '[:lower:]')" # Normalize to lowercase
mkdir -p ./apps
while read -r url; do
case "$platform" in
android)
echo "$url" | grep -qi '\.apk$' || continue # Skip if not an APK
;;
ios)
echo "$url" | grep -qi '\.zip$' || continue # Skip if not a ZIP file
;;
*)
# No filter
;;
esac
echo "download $url"
app_file="$(curl -fsSL --output-dir ./apps --write-out "%{filename_effective}" -OJ "$url")"
extension="${app_file##*.}"
if [ "$extension" = "zip" ]; then
unzip -qq -o -d ./apps "$app_file" -x "__MACOSX/*"
fi
done <manifest.txt
================================================
FILE: e2e/install_apps
================================================
#!/usr/bin/env sh
set -eu
# Install all apps from apps/ directory (that was previously created with
# download_apps).
#
# Matches .apk files to install on Android devices and .app files to install on
# iOS simulators.
[ "$(basename "$PWD")" = "e2e" ] || { echo "must be run from e2e directory" && exit 1; }
platform="${1:-}"
if [ "$platform" != "android" ] && [ "$platform" != "ios" ]; then
echo "usage: $0 <android|ios>"
exit 1
fi
command -v adb >/dev/null 2>&1 || { echo "adb is required" && exit 1; }
for file in ./apps/*; do
filename="$(basename "$file")"
extension="${file##*.}"
if [ "$platform" = android ] && [ "$extension" = "apk" ]; then
echo "install $filename"
adb install -r "$file" >/dev/null || echo "adb: could not install $filename"
elif [ "$platform" = ios ] && [ "$extension" = "app" ] && [ "$(uname)" = "Darwin" ]; then
echo "install $filename"
xcrun simctl install booted "$file" || echo "xcrun: could not install $filename"
fi
done
================================================
FILE: e2e/manifest.txt
================================================
https://storage.googleapis.com/mobile.dev/cli_e2e/wikipedia.apk
https://storage.googleapis.com/mobile.dev/cli_e2e/wikipedia.zip
https://storage.googleapis.com/mobile.dev/cli_e2e/demo_app.apk
https://storage.googleapis.com/mobile.dev/cli_e2e/demo_app.zip
https://storage.googleapis.com/mobile.dev/cli_e2e/setOrientation.apk
https://storage.googleapis.com/mobile.dev/cli_e2e/SimpleWebViewApp.zip
================================================
FILE: e2e/run_tests
================================================
#!/usr/bin/env sh
set -eu
# Runs all tests in the workspaces directory.
command -v maestro >/dev/null 2>&1 || { echo "maestro is required" && exit 1; }
[ "$(basename "$PWD")" = "e2e" ] || { echo "must be run from e2e directory" && exit 1; }
ALL_PASS=true
_h1() { printf "=>\n=> %s\n=>\n" "$1"; }
_h2() { printf "==> [%s] %s\n" "$1" "$2"; }
_h3() { printf "==> [%s] [%s] => %s\n" "$1" "$2" "$3"; }
cloud="android_device_configuration,ios_device_configuration" # Maestro Cloud specific tests
platform="${1:-}"
case "$platform" in
android) exclude_tags="ios,web,$cloud" ;;
ios) exclude_tags="android,$cloud,web" ;;
web) exclude_tags="android,ios,$cloud" ;;
*) echo "usage: $0 <android|ios|web>"; exit 1 ;;
esac
mkfifo pipe
trap 'rm -f pipe' EXIT
export MAESTRO_EXAMPLE="test-value" # Relied upon in a test
if [ "$platform" = "web" ]; then
###
### Web: run web-tagged tests for demo_app only
###
workspace_dir="./workspaces/demo_app"
app_name="demo_app"
_h1 "run tests for app \"$app_name\" on platform \"$platform\""
_h2 "$app_name" "run web tests"
while IFS= read -r line; do
_h3 "$app_name" "web" "$line"
done < pipe &
maestro --verbose --platform "$platform" test --headless --include-tags web --exclude-tags "$exclude_tags" "$workspace_dir" 1>pipe 2>&1 \
|| { _h2 "$app_name" "FAIL! Expected all pass, but at least some failed instead"; ALL_PASS=false; }
else
for workspace_dir in ./workspaces/*; do
app_name="$(basename "$workspace_dir")"
case $app_name in
# demo_app has OOM issues on GHA
demo_app|setOrientation) [ "$platform" = "ios" ] && continue ;;
simple_web_view) [ "$platform" = "android" ] && continue ;;
esac
_h1 "run tests for app \"$app_name\" on platform \"$platform\""
###
### Run passing tests
###
_h2 "$app_name" "run passing tests"
while IFS= read -r line; do
_h3 "$app_name" "passing" "$line"
done < pipe &
maestro --verbose --platform "$platform" test --include-tags passing --exclude-tags "$exclude_tags" "$workspace_dir" 1>pipe 2>&1 \
|| { _h2 "$app_name" "FAIL! Expected all pass, but at least some failed instead"; ALL_PASS=false; }
###
### Run failing tests (skip workspaces with no failing flows)
###
case $app_name in
wikipedia|setOrientation|simple_web_view) continue ;;
esac
_h2 "$app_name" "run failing tests"
while IFS= read -r line; do
_h3 "$app_name" "failing" "$line"
done < pipe &
maestro --verbose --platform "$platform" test --include-tags failing --exclude-tags "$exclude_tags" "$workspace_dir" 1>pipe 2>&1 \
&& { _h2 "$app_name" "FAIL! Expected all to fail, but at least some passed instead"; ALL_PASS=false; }
done
fi
if [ "$ALL_PASS" = "false" ]; then
_h1 "FAILURE: some tests failed!"
exit 1
else
_h1 "SUCCESS: all tests passed!"
fi
================================================
FILE: e2e/update_samples
================================================
#!/usr/bin/env sh
set -eu
# Updates the samples that are hosted in mobile.dev's GCS bucket ($SAMPLES_URL).
# The samples are for use with `maestro download-samples` command.
[ "$(basename "$PWD")" = "e2e" ] || { echo "must be run from e2e directory" && exit 1; }
command -v curl >/dev/null 2>&1 || { echo "curl is required" && exit 1; }
command -v gsutil >/dev/null 2>&1 || { echo "gsutil is required" && exit 1; }
SAMPLES_URL="gs://mobile.dev/samples/samples.zip"
if [ ! -d apps/ ]; then
./download_apps
fi
rm -rf samples/ samples.zip
mkdir -p samples/
cp -r apps/wikipedia.apk apps/wikipedia.zip samples/
cp -r workspaces/wikipedia/* samples/
cp samples/wikipedia.apk samples/sample.apk # The name is being depended upon.
cp samples/wikipedia.zip samples/sample.zip # The name is being depended upon.
cd samples/
zip -r -q ../samples.zip . -x "/**/.*" -x "__MACOSX"
cd ..
gsutil cp samples.zip "$SAMPLES_URL"
gsutil acl ch -r -u AllUsers:R "$SAMPLES_URL"
================================================
FILE: e2e/workspaces/setOrientation/test-set-orientation-flow.yaml
================================================
appId: com.example.maestro.orientation
env:
orientationLandscapeLeft: LANDSCAPE_LEFT
orientationLandscapeRight: LANDSCAPE_RIGHT
orientationUpsideDown: UPSIDE_DOWN
orientationPortrait: PORTRAIT
tags:
- android
- passing
---
- launchApp
- setOrientation: LANDSCAPE_LEFT
- assertVisible: "LANDSCAPE_LEFT"
- setOrientation: LANDSCAPE_RIGHT
- assertVisible: "LANDSCAPE_RIGHT"
- setOrientation: UPSIDE_DOWN
- assertVisible: "UPSIDE_DOWN"
- setOrientation: PORTRAIT
- assertVisible: "PORTRAIT"
- setOrientation: ${orientationLandscapeLeft}
- assertVisible: "LANDSCAPE_LEFT"
- setOrientation: ${orientationLandscapeRight}
- assertVisible: "LANDSCAPE_RIGHT"
- setOrientation: ${orientationUpsideDown}
- assertVisible: "UPSIDE_DOWN"
- setOrientation: ${orientationPortrait}
- assertVisible: "PORTRAIT"
================================================
FILE: e2e/workspaces/simple_web_view/webview.yaml
================================================
appId: com.example.SimpleWebViewApp
tags:
- passing
- ios
---
- launchApp:
clearState: true
- tapOn: Open Login Page
- extendedWaitUntil:
visible: Login
timeout: 30000
label: Wait for Login page to load
- assertVisible: Sign In
- assertVisible: Forgot your password?
================================================
FILE: e2e/workspaces/wikipedia/android-advanced-flow.yaml
================================================
appId: org.wikipedia
tags:
- android
- passing
- advanced
---
- runFlow: subflows/onboarding-android.yaml
- tapOn:
id: "org.wikipedia:id/search_container"
- tapOn:
text: "Non existent view"
optional: true
- runScript: scripts/getSearchQuery.js
- inputText: ${output.result}
- assertVisible: ${output.result}
- runFlow: subflows/launch-clearstate-android.yaml
================================================
FILE: e2e/workspaces/wikipedia/android-flow.yaml
================================================
appId: org.wikipedia
tags:
- android
- passing
---
- launchApp
================================================
FILE: e2e/workspaces/wikipedia/ios-advanced-flow.yaml
================================================
appId: org.wikimedia.wikipedia
tags:
- ios
- passing
- advanced
---
- runFlow: subflows/onboarding-ios.yaml
- runFlow:
when:
visible:
text: Explore your Wikipedia Year in Review
commands:
- tapOn: Done
label: Dismiss Year In Review popup, if visible
- runFlow:
when:
visible: "You have been logged out"
commands:
- tapOn:
text: "Continue without logging in"
label: Dismiss the auth modal if visible
- tapOn:
text: "Non existent view"
optional: true
- tapOn: Search Wikipedia
- runScript: scripts/getSearchQuery.js
- inputText: ${output.result}
- eraseText
- inputText: qwerty
- assertVisible: ${output.result}
- runFlow: subflows/launch-clearstate-ios.yaml
================================================
FILE: e2e/workspaces/wikipedia/ios-flow.yaml
================================================
appId: org.wikimedia.wikipedia
tags:
- ios
- passing
---
- launchApp
================================================
FILE: e2e/workspaces/wikipedia/scripts/getSearchQuery.js
================================================
output.result = 'qwerty';
================================================
FILE: e2e/workspaces/wikipedia/subflows/launch-clearstate-android.yaml
================================================
appId: org.wikipedia
---
- launchApp:
clearState: true
- assertVisible: "Continue"
- assertVisible: "Skip"
================================================
FILE: e2e/workspaces/wikipedia/subflows/launch-clearstate-ios.yaml
================================================
appId: org.wikimedia.wikipedia
---
- launchApp:
clearState: true
- assertVisible: "Next"
- assertVisible: "Skip"
================================================
FILE: e2e/workspaces/wikipedia/subflows/onboarding-android.yaml
================================================
appId: org.wikipedia
---
- launchApp:
clearState: true
- tapOn:
text: "Non existent view"
optional: true
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_forward_button"
- tapOn:
id: "org.wikipedia:id/fragment_onboarding_done_button"
================================================
FILE: e2e/workspaces/wikipedia/subflows/onboarding-ios.yaml
================================================
appId: org.wikimedia.wikipedia
---
- launchApp:
clearState: true
- repeat:
times: 3
commands:
- swipe:
direction: LEFT
duration: 400
- waitForAnimationToEnd
- tapOn: Get started
- tapOn:
text: "Non existent view"
optional: true
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/login.yml
================================================
appId: org.wikipedia
---
- tapOn: "More"
- tapOn: "LOG IN.*"
- tapOn:
id: ".*create_account_login_button"
- runScript: "../scripts/fetchTestUser.js"
- tapOn: "Username"
- inputText: "${output.test_user.username}"
- tapOn: "Password"
- inputText: "No provided"
- tapOn: "LOG IN"
- back
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/signup.yml
================================================
appId: org.wikipedia
---
- tapOn: "More"
- tapOn: "LOG IN.*"
- runScript: "../scripts/generateCredentials.js"
- tapOn: "Username"
- inputText: "${output.credentials.username}"
- tapOn: "Password"
- inputText: "${output.credentials.password}"
- tapOn: "Repeat password"
- inputText: "${output.credentials.password}"
- tapOn: "Email.*"
- inputText: "${output.credentials.email}"
# We won't actually create the account
- back
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/copy-paste.yml
================================================
appId: org.wikipedia
---
- tapOn: "Explore"
- scrollUntilVisible:
element: "Top read"
- copyTextFrom:
id: ".*view_list_card_item_title"
index: 0
- tapOn: "Explore"
- tapOn: "Search Wikipedia"
- inputText: "${maestro.copiedText}"
- back
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/feed.yml
================================================
appId: org.wikipedia
---
- tapOn: "Explore"
- scrollUntilVisible:
element: "Today on Wikipedia.*"
- tapOn: "Today on Wikipedia.*"
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/main.yml
================================================
appId: org.wikipedia
---
- runFlow: "search.yml"
- runFlow: "saved.yml"
- runFlow: "feed.yml"
- runFlow: "copy-paste.yml"
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/saved.yml
================================================
appId: org.wikipedia
---
- tapOn: "Saved"
- tapOn: "Default list for your saved articles"
- assertVisible: "Sun"
- assertVisible: "Star at the center of the Solar System"
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/search.yml
================================================
appId: org.wikipedia
---
- tapOn: "Search Wikipedia"
- inputText: "Sun"
- assertVisible: "Star at the center of the Solar System"
- tapOn:
id: ".*page_list_item_title"
- tapOn:
id: ".*page_save"
- back
- back
- back
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/add-language.yml
================================================
appId: org.wikipedia
---
- tapOn: "ADD OR EDIT.*"
- tapOn: "ADD LANGUAGE"
- tapOn:
id: ".*menu_search_language"
- inputText: "Greek"
- assertVisible: "Ελληνικά"
- tapOn: "Ελληνικά"
- tapOn: "Navigate up"
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/main.yml
================================================
appId: org.wikipedia
---
- runFlow: "add-language.yml"
- runFlow: "remove-language.yml"
- tapOn: "Continue"
- assertVisible: "New ways to explore"
- tapOn: "Continue"
- assertVisible: "Reading lists with sync"
- tapOn: "Continue"
- assertVisible: "Send anonymous data"
- tapOn: "Get started"
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/remove-language.yml
================================================
appId: org.wikipedia
---
- tapOn: "ADD OR EDIT.*"
- tapOn: "More options"
- tapOn: "Remove language"
- tapOn:
id: ".*wiki_language_checkbox"
index: 1
- tapOn:
id: ".*menu_delete_selected"
- tapOn: "OK"
- assertNotVisible: "Ελληνικά"
- tapOn: "Navigate up"
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/run-test.yml
================================================
appId: org.wikipedia
tags:
- android
- passing
---
- launchApp:
clearState: true
- runFlow: "onboarding/main.yml"
- runFlow: "dashboard/main.yml"
- runFlow: "auth/signup.yml"
- runFlow: "auth/login.yml"
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/fetchTestUser.js
================================================
// Fetches test user from API
function getTestUserFromApi() {
const url = `https://jsonplaceholder.typicode.com/users/1`;
var response = http.get(url);
var data = json(response.body);
return {
username: data.username,
email: data.email,
};
}
output.test_user = getTestUserFromApi();
================================================
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/generateCredentials.js
================================================
function username() {
var date = new Date().getTime().toString();
var username = `test_user_placeholder`.replace("placeholder", date);
return username;
}
function email() {
var date = new Date().getTime().toString();
var email = `test-user-placeholder@test.com`.replace("placeholder", date);
return email;
}
function password() {
var date = new Date().getTime().toString();
var password = `test-user-password-placeholder`.replace("placeholder", date);
return password;
}
output.credentials = {
email: email(),
password: password(),
username: username(),
};
================================================
FILE: gradle/libs.versions.toml
================================================
# File should be sorted by alphabet for each section
# How to sort with AS:
# "Select all in block" -> "Edit" -> "Sort lines"
# File should follow the naming convention:
# https://blog.gradle.org/best-practices-naming-version-catalog-entries
[versions]
androidPlugin = "8.13.2"
androidxEspresso = "3.6.1"
androidxTestJunit = "1.2.1"
androidxUiautomator = "2.3.0"
apkParser = "2.6.10"
appdirs = "1.2.1"
axml = "2.1.2"
commons-codec = "1.17.0"
commons-lang3 = "3.13.0" # 3.14.0 causes weird crashes during dexing
commons-io = "2.16.1"
dadb = "1.2.10"
datafaker = "2.5.3"
ddPlist = "1.23"
detekt = "1.23.8"
googleFindbugs = "3.0.2"
googleGson = "2.11.0"
googleProtobuf = "3.21.9"
googleProtobufPlugin = "0.9.4"
googleTruth = "1.4.2"
graaljs = "24.2.0"
grpc = "1.50.2"
grpcKotlinStub = "1.4.1"
imageComparison = "4.4.0"
hiddenapibypass = "4.3"
jackson = "2.17.1"
jansi = "2.4.1"
jarchivelib = "1.2.0"
jcodec = "0.2.5"
junit = "5.10.2"
kotlin = "2.2.0"
kotlinRetry = "2.0.1"
kotlinResult = "2.0.1"
kotlinx-serialization-json = "1.5.0"
ktor = "2.3.6"
micrometerObservation = "1.13.4"
micrometerCore = "1.13.4"
mockk = "1.12.0"
mordant = "3.0.2"
mozillaRhino = "1.7.14"
picocli = "4.6.3"
posthog = "1.0.3"
selenium = "4.40.0"
selenium-devtools = "4.40.0"
skiko = "0.8.18"
squareOkhttp = "4.12.0"
squareOkio = "3.16.2"
squareMockWebServer = "4.11.0"
wiremock = "2.35.0"
log4j = "2.25.3"
coroutines = "1.8.0"
kotlinx-html = "0.8.0"
clikt = "4.2.2"
gmsLocation = "21.3.0"
mcpKotlinSdk = "steviec/kotlin-1.8"
[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html", version.ref = "kotlinx-html" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspresso" }
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestJunit" }
androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidxUiautomator" }
apk-parser = { module = "net.dongliu:apk-parser", version.ref = "apkParser" }
appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" }
axml = { module = "de.upb.cs.swt:axml", version.ref = "axml" }
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" }
commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" }
dadb = { module = "dev.mobile:dadb", version.ref = "dadb" }
datafaker = { module = "net.datafaker:datafaker", version.ref = "datafaker" }
dd-plist = { module = "com.googlecode.plist:dd-plist", version.ref = "ddPlist" }
google-findbugs = { module = "com.google.code.findbugs:jsr305", version.ref = "googleFindbugs" }
google-gson = { module = "com.google.code.gson:gson", version.ref = "googleGson" }
google-protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "googleProtobuf" }
google-protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "googleProtobuf" }
google-truth = { module = "com.google.truth:truth", version.ref = "googleTruth" }
graaljs = { module = "org.graalvm.js:js", version.ref = "graaljs" }
graaljsEngine = { module = "org.graalvm.js:js-scriptengine", version.ref = "graaljs" }
graaljsLanguage = { module = "org.graalvm.js:js-language", version.ref = "graaljs" }
grpc-kotlin-stub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlinStub" }
grpc-netty = { module = "io.grpc:grpc-netty", version.ref = "grpc" }
grpc-netty-shaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" }
grpc-okhttp = { module = "io.grpc:grpc-okhttp", version.ref = "grpc" }
grpc-protobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" }
grpc-protobuf-lite = { module = "io.grpc:grpc-protobuf-lite", version.ref = "grpc" }
grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" }
hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" }
image-comparison = { module = "com.github.romankh3:image-comparison", version.ref = "imageComparison" }
jackson-core-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-dataformat-xml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version.ref = "jackson" }
jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" }
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" }
jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" }
jarchivelib = { module = "org.rauschig:jarchivelib", version.ref = "jarchivelib" }
jcodec = { module = "org.jcodec:jcodec", version.ref = "jcodec" }
jcodec-awt = { module = "org.jcodec:jcodec-javase", version.ref = "jcodec" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
kotlin-result = { module = "com.michael-bull.kotlin-result:kotlin-result", version.ref = "kotlinResult" }
kotlin-retry = { module = "com.michael-bull.kotlin-retry:kotlin-retry", version.ref = "kotlinRetry" }
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-serial-gson = { module = "io.ktor:ktor-serialization-gson", version.ref = "ktor" }
ktor-serial-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
micrometer-core = { module = "io.micrometer:micrometer-core", version.ref = "micrometerCore" }
micrometer-observation = { module = "io.micrometer:micrometer-observation", version.ref = "micrometerObservation" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mordant = { module = "com.github.ajalt.mordant:mordant", version.ref = "mordant" }
mozilla-rhino = { module = "org.mozilla:rhino", version.ref = "mozillaRhino" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
picocli-codegen = { module = "info.picocli:picocli-codegen", version.ref = "picocli" }
posthog = { module = "com.posthog:posthog-server", version.ref = "posthog" }
selenium = { module = "org.seleniumhq.selenium:selenium-java", version.ref = "selenium" }
selenium-devtools = { module = "org.seleniumhq.selenium:selenium-devtools-v142", version.ref = "selenium-devtools" }
skiko-macos-arm64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-macos-arm64", version.ref = "skiko" }
skiko-macos-x64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-macos-x64", version.ref = "skiko" }
skiko-linux-arm64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-linux-arm64", version.ref = "skiko" }
skiko-linux-x64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-linux-x64", version.ref = "skiko" }
skiko-windows-arm64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-windows-arm64", version.ref = "skiko" }
skiko-windows-x64 = { module = "org.jetbrains.skiko:skiko-awt-runtime-windows-x64", version.ref = "skiko" }
square-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "squareOkhttp" }
square-okhttp-logs = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "squareOkhttp" }
square-okio = { module = "com.squareup.okio:okio", version.ref = "squareOkio" }
square-okio-jvm = { module = "com.squareup.okio:okio-jvm", version.ref = "squareOkio" }
square-mock-server = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "squareMockWebServer" }
wiremock-jre8 = { module = "com.github.tomakehurst:wiremock-jre8", version.ref = "wiremock" }
logging-sl4j = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
logging-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
logging-layout-template = { module = "org.apache.logging.log4j:log4j-layout-template-json", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
gmsLocation = { module = "com.google.android.gms:play-services-location", version.ref = "gmsLocation" }
mcp-kotlin-sdk = { module = "io.modelcontextprotocol:kotlin-sdk" }
[bundles]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidPlugin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
protobuf = { id = "com.google.protobuf", version.ref = "googleProtobufPlugin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.33.0" }
jreleaser = { id = "org.jreleaser", version = "1.13.1" }
shadow = { id = "com.github.johnrengelman.shadow", version = "7.1.2" }
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
GROUP=dev.mobile
VERSION_NAME=2.4.0
POM_DESCRIPTION=Maestro is a server-driven platform-agnostic library that allows to drive tests for both iOS and Android using the same implementation through an intuitive API.
POM_URL=https://github.com/mobile-dev-inc/maestro
POM_SCM_URL=https://github.com/mobile-dev-inc/maestro
POM_SCM_CONNECTION=scm:git:git://github.com/mobile-dev-inc/maestro.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/mobile-dev-inc/maestro.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=mobile-dev-inc
POM_DEVELOPER_NAME=mobile.dev inc.
SONATYPE_STAGING_PROFILE=dev.mobile
org.gradle.jvmargs=-Xmx2000m
================================================
FILE: gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collec
gitextract_0f8ml73h/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yaml │ │ └── feature_request.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── scripts/ │ │ └── boot_simulator.sh │ └── workflows/ │ ├── close-inactive-issues.yaml │ ├── lock-closed-issues.yaml │ ├── publish-cli.yaml │ ├── publish-release.yaml │ ├── publish-snapshot.yaml │ ├── test-e2e-ios-intel.yaml │ ├── test-e2e-prod.yaml │ ├── test-e2e.yaml │ ├── test.yaml │ └── update-samples.yaml ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── .name │ └── dictionaries/ │ └── project.xml ├── .run/ │ ├── cli-version.run.xml │ └── cli.run.xml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── build.gradle.kts ├── debug.keystore ├── detekt.yml ├── e2e/ │ ├── .gitignore │ ├── README.md │ ├── download_apps │ ├── install_apps │ ├── manifest.txt │ ├── run_tests │ ├── update_samples │ └── workspaces/ │ ├── setOrientation/ │ │ └── test-set-orientation-flow.yaml │ ├── simple_web_view/ │ │ └── webview.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 ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── installLocally.sh ├── 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 │ │ └── screenshot/ │ │ ├── ScreenshotService.kt │ │ └── ScreenshotServiceTest.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 │ ├── jvm-version.jar │ └── 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 │ │ │ │ ├── AnalyticsStateManager.kt │ │ │ │ └── PostHogEvents.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 │ │ │ │ ├── ListCloudDevicesCommand.kt │ │ │ │ ├── ListDevicesCommand.kt │ │ │ │ ├── LoginCommand.kt │ │ │ │ ├── LogoutCommand.kt │ │ │ │ ├── McpCommand.kt │ │ │ │ ├── PrintHierarchyCommand.kt │ │ │ │ ├── QueryCommand.kt │ │ │ │ ├── RecordCommand.kt │ │ │ │ ├── StartDeviceCommand.kt │ │ │ │ ├── StudioCommand.kt │ │ │ │ └── TestCommand.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 │ │ │ ├── mcp/ │ │ │ │ ├── McpServer.kt │ │ │ │ ├── README.md │ │ │ │ └── tools/ │ │ │ │ ├── BackTool.kt │ │ │ │ ├── CheatSheetTool.kt │ │ │ │ ├── CheckFlowSyntaxTool.kt │ │ │ │ ├── InputTextTool.kt │ │ │ │ ├── InspectViewHierarchyTool.kt │ │ │ │ ├── LaunchAppTool.kt │ │ │ │ ├── ListDevicesTool.kt │ │ │ │ ├── QueryDocsTool.kt │ │ │ │ ├── RunFlowFilesTool.kt │ │ │ │ ├── RunFlowTool.kt │ │ │ │ ├── StartDeviceTool.kt │ │ │ │ ├── StopAppTool.kt │ │ │ │ ├── TakeScreenshotTool.kt │ │ │ │ ├── TapOnTool.kt │ │ │ │ └── ViewHierarchyFormatters.kt │ │ │ ├── model/ │ │ │ │ ├── FlowStatus.kt │ │ │ │ ├── RunningFlow.kt │ │ │ │ └── TestExecutionSummary.kt │ │ │ ├── promotion/ │ │ │ │ └── PromotionStateManager.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 │ │ │ │ ├── DependencyResolver.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 │ │ │ │ ├── WorkingDirectory.kt │ │ │ │ └── WorkspaceUtils.kt │ │ │ ├── view/ │ │ │ │ ├── ErrorViewUtils.kt │ │ │ │ ├── ProgressBar.kt │ │ │ │ ├── TestSuiteStatusView.kt │ │ │ │ └── ViewUtils.kt │ │ │ └── web/ │ │ │ └── WebInteractor.kt │ │ └── resources/ │ │ ├── ai_report.css │ │ ├── deps/ │ │ │ └── applesimutils │ │ ├── html-detailed.css │ │ ├── logback-test.xml │ │ └── tailwind.config.js │ └── test/ │ ├── kotlin/ │ │ └── maestro/ │ │ └── cli/ │ │ ├── android/ │ │ │ └── AndroidDeviceProvider.kt │ │ ├── cloud/ │ │ │ └── CloudInteractorTest.kt │ │ ├── command/ │ │ │ └── TestCommandTest.kt │ │ ├── driver/ │ │ │ ├── DriverBuilderTest.kt │ │ │ └── RealDeviceDriverTest.kt │ │ ├── report/ │ │ │ ├── HtmlTestSuiteReporterTest.kt │ │ │ ├── JUnitTestSuiteReporterTest.kt │ │ │ ├── TestDebugReporterTest.kt │ │ │ └── TestSuiteReporterTest.kt │ │ ├── runner/ │ │ │ └── resultview/ │ │ │ └── PlainTextResultViewTest.kt │ │ └── util/ │ │ ├── ChangeLogUtilsTest.kt │ │ ├── DependencyResolverTest.kt │ │ └── WorkspaceUtilsTest.kt │ ├── mcp/ │ │ ├── README.md │ │ ├── full-evals.yaml │ │ ├── inspect-view-hierarchy-evals.yaml │ │ ├── launch_app_with_env_replacement.yaml │ │ ├── maestro-mcp.json │ │ ├── mcp-server-config.json │ │ ├── run_mcp_evals.sh │ │ ├── run_mcp_tool_tests.sh │ │ ├── setup/ │ │ │ ├── check-maestro-cli-built.sh │ │ │ ├── download-and-install-apps.sh │ │ │ ├── flows/ │ │ │ │ ├── launch-demo-app-ios.yaml │ │ │ │ ├── launch-safari-ios.yaml │ │ │ │ ├── setup-wikipedia-search-android.yaml │ │ │ │ ├── setup-wikipedia-search-ios.yaml │ │ │ │ └── verify-ready-state.yaml │ │ │ ├── launch-simulator.sh │ │ │ └── setup_and_run_eval.sh │ │ ├── tool-tests-with-device.yaml │ │ └── tool-tests-without-device.yaml │ └── resources/ │ ├── apps/ │ │ └── web-manifest.json │ ├── location/ │ │ └── assert_multiple_locations.yaml │ ├── travel/ │ │ └── assert_travel_command.yaml │ └── workspaces/ │ ├── cloud_test/ │ │ ├── android/ │ │ │ └── flow.yaml │ │ ├── ios/ │ │ │ └── flow.yaml │ │ ├── tagged/ │ │ │ ├── regression.yaml │ │ │ └── smoke.yaml │ │ └── web/ │ │ └── flow.yaml │ └── test_command_test/ │ ├── 00_mixed_web_mobile_flow_tests/ │ │ ├── mobileflow.yaml │ │ ├── mobileflow2.yaml │ │ ├── webflow.yaml │ │ └── webflow2.yaml │ ├── 01_web_only/ │ │ ├── webflow.yaml │ │ └── webflow2.yaml │ ├── 02_mobile_only/ │ │ ├── mobileflow1.yaml │ │ └── mobileflow2.yaml │ ├── 03_mixed_with_config_execution_order/ │ │ ├── config.yaml │ │ └── subFolder/ │ │ ├── mobileflow.yaml │ │ ├── mobileflow2.yaml │ │ ├── webflow.yaml │ │ └── webflow2.yaml │ └── 04_web_only_with_config_execution_order/ │ ├── config.yaml │ └── subFolder/ │ ├── mobileflow.yaml │ ├── mobileflow2.yaml │ ├── webflow.yaml │ └── webflow2.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 │ │ │ ├── OnDeviceElementQuery.kt │ │ │ ├── OnDeviceElementQueryResult.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 │ │ │ ├── auth/ │ │ │ │ └── ApiKey.kt │ │ │ ├── debuglog/ │ │ │ │ ├── DebugLogStore.kt │ │ │ │ └── LogConfig.kt │ │ │ ├── device/ │ │ │ │ ├── Device.kt │ │ │ │ ├── DeviceError.kt │ │ │ │ ├── DeviceOrientation.kt │ │ │ │ ├── DeviceService.kt │ │ │ │ ├── DeviceSpec.kt │ │ │ │ ├── Platform.kt │ │ │ │ ├── locale/ │ │ │ │ │ ├── AndroidLocale.kt │ │ │ │ │ ├── DeviceLocale.kt │ │ │ │ │ ├── IosLocale.kt │ │ │ │ │ ├── LocaleValidationException.kt │ │ │ │ │ └── WebLocale.kt │ │ │ │ ├── serialization/ │ │ │ │ │ ├── DeviceLocaleSerializer.kt │ │ │ │ │ └── DeviceSpecModule.kt │ │ │ │ └── util/ │ │ │ │ ├── AndroidEnvUtils.kt │ │ │ │ ├── AvdDevice.kt │ │ │ │ ├── CommandLineUtils.kt │ │ │ │ ├── EnvUtils.kt │ │ │ │ ├── PrintUtils.kt │ │ │ │ ├── SimctlList.kt │ │ │ │ └── SystemInfo.kt │ │ │ ├── drivers/ │ │ │ │ ├── AndroidDriver.kt │ │ │ │ ├── CdpWebDriver.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/ │ │ ├── FiltersTest.kt │ │ ├── PointTest.kt │ │ ├── UiElementTest.kt │ │ ├── android/ │ │ │ ├── AndroidAppFilesTest.kt │ │ │ ├── AndroidLaunchArgumentsTest.kt │ │ │ └── chromedevtools/ │ │ │ └── AndroidWebViewHierarchyClientTest.kt │ │ ├── device/ │ │ │ ├── DeviceServiceTest.kt │ │ │ ├── DeviceSpecTest.kt │ │ │ └── serialization/ │ │ │ └── DeviceSpecSerializationTest.kt │ │ ├── ios/ │ │ │ └── MockXCTestInstaller.kt │ │ ├── locale/ │ │ │ └── DeviceLocaleTest.kt │ │ ├── utils/ │ │ │ ├── HttpUtilsTest.kt │ │ │ └── StringUtilsTest.kt │ │ └── xctestdriver/ │ │ └── XCTestDriverClientTest.kt │ └── resources/ │ └── logback-test.xml ├── maestro-ios/ │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ └── src/ │ └── main/ │ └── java/ │ └── ios/ │ ├── IOSDeviceErrors.kt │ ├── LocalIOSDevice.kt │ ├── devicectl/ │ │ └── DeviceControlIOSDevice.kt │ └── xctest/ │ └── XCTestIOSDevice.kt ├── 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 │ │ │ │ ├── SetOrientationRequest.kt │ │ │ │ ├── SetPermissionsRequest.kt │ │ │ │ ├── SwipeRequest.kt │ │ │ │ ├── TerminateAppRequest.kt │ │ │ │ ├── TouchRequest.kt │ │ │ │ └── ViewHierarchyRequest.kt │ │ │ └── installer/ │ │ │ ├── IOSBuildProductsExtractor.kt │ │ │ ├── LocalXCTestInstaller.kt │ │ │ └── XCTestInstaller.kt │ │ └── resources/ │ │ ├── driver-iPhoneSimulator/ │ │ │ └── maestro-driver-ios-config.xctestrun │ │ ├── driver-iphoneos/ │ │ │ └── maestro-driver-ios-config.xctestrun │ │ └── screenrecord.sh │ └── test/ │ └── kotlin/ │ ├── DeviceCtlResponseTest.kt │ ├── IOSBuildProductsExtractorTest.kt │ └── IOSLaunchArgumentsTest.kt ├── maestro-ios-xctest-runner/ │ ├── .gitignore │ ├── MaestroDriverLib/ │ │ ├── Info.plist │ │ ├── Package.swift │ │ ├── Sources/ │ │ │ └── MaestroDriverLib/ │ │ │ ├── Helpers/ │ │ │ │ └── PermissionButtonFinder.swift │ │ │ ├── MaestroDriverLib.swift │ │ │ └── Models/ │ │ │ ├── AXElement.swift │ │ │ ├── AXFrame.swift │ │ │ ├── ElementType.swift │ │ │ └── PermissionValue.swift │ │ └── Tests/ │ │ └── MaestroDriverLibTests/ │ │ ├── AXElementTests.swift │ │ ├── AXFrameTests.swift │ │ └── PermissionButtonFinderTests.swift │ ├── build-maestro-ios-runner-all.sh │ ├── build-maestro-ios-runner.sh │ ├── 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-ios.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm/ │ │ │ └── Package.resolved │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ ├── maestro-driver-ios.xcscheme │ │ └── maestro-driver-iosTests.xcscheme │ ├── maestro-driver-iosTests/ │ │ ├── Info.plist │ │ ├── SnapshotParametersTests.swift │ │ └── maestro-driver-iosTests-Bridging-Header.h │ ├── 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 │ │ │ │ ├── SetOrientationHandler.swift │ │ │ │ ├── SetPermissionsHandler.swift │ │ │ │ ├── StatusHandler.swift │ │ │ │ ├── SwipeRouteHandler.swift │ │ │ │ ├── SwipeRouteHandlerV2.swift │ │ │ │ ├── TerminateAppHandler.swift │ │ │ │ ├── TouchRouteHandler.swift │ │ │ │ └── ViewHierarchyHandler.swift │ │ │ ├── Helpers/ │ │ │ │ ├── AppError.swift │ │ │ │ ├── ScreenSizeHelper.swift │ │ │ │ ├── SystemPermissionHelper.swift │ │ │ │ └── TextInputHelper.swift │ │ │ ├── Models/ │ │ │ │ ├── AXElement.swift │ │ │ │ ├── DeviceInfoResponse.swift │ │ │ │ ├── EraseTextRequest.swift │ │ │ │ ├── GetRunningAppRequest.swift │ │ │ │ ├── InputTextRequest.swift │ │ │ │ ├── KeyboardHandlerRequest.swift │ │ │ │ ├── KeyboardHandlerResponse.swift │ │ │ │ ├── LaunchAppRequest.swift │ │ │ │ ├── PressButtonRequest.swift │ │ │ │ ├── PressKeyRequest.swift │ │ │ │ ├── SetOrientationRequest.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 │ │ ├── maestro_driver_iosUITests.swift │ │ └── maestro_driver_iosUITestsLaunchTests.swift │ ├── run-maestro-ios-runner.sh │ └── test-maestro-ios-runner.sh ├── 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 │ │ ├── util/ │ │ │ ├── AppMetadataAnalyzer.kt │ │ │ └── ElementCoordinateUtil.kt │ │ ├── validation/ │ │ │ ├── AppValidationException.kt │ │ │ ├── AppValidator.kt │ │ │ ├── WorkspaceValidationException.kt │ │ │ └── WorkspaceValidator.kt │ │ ├── workspace/ │ │ │ ├── ExecutionOrderPlanner.kt │ │ │ ├── Filters.kt │ │ │ ├── WorkspaceExecutionPlanner.kt │ │ │ ├── WorkspaceValidator.kt │ │ │ └── YamlCommandsPathValidator.kt │ │ └── yaml/ │ │ ├── MaestroFlowParser.kt │ │ ├── YamlAction.kt │ │ ├── YamlAddMedia.kt │ │ ├── YamlAssertNoDefectsWithAI.kt │ │ ├── YamlAssertScreenshot.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 │ │ ├── YamlSetClipboard.kt │ │ ├── YamlSetLocation.kt │ │ ├── YamlSetOrientation.kt │ │ ├── YamlSetPermissions.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 │ │ ├── util/ │ │ │ ├── AppMetadataAnalyzerTest.kt │ │ │ └── ElementCoordinateUtilTest.kt │ │ ├── validation/ │ │ │ ├── AppValidatorTest.kt │ │ │ └── WorkspaceValidatorTest.kt │ │ ├── workspace/ │ │ │ ├── ExecutionOrderPlannerTest.kt │ │ │ ├── WorkspaceExecutionPlannerErrorsTest.kt │ │ │ ├── WorkspaceExecutionPlannerTest.kt │ │ │ └── WorkspaceValidatorTest.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_labels.yaml │ │ ├── 023_runScript_test.js │ │ ├── 024_string_non_string_commands.yaml │ │ ├── 025_killApp.yaml │ │ ├── 027_waitToSettleTimeoutMs.yaml │ │ ├── 028_inputRandomAnimal.yaml │ │ ├── 029_command_descriptions.yaml │ │ ├── 029_double_tap_element_relative.yaml │ │ ├── 029_element_relative_tap_css.yaml │ │ ├── 029_element_relative_tap_enabled.yaml │ │ ├── 029_element_relative_tap_id_absolute.yaml │ │ ├── 029_element_relative_tap_index.yaml │ │ ├── 029_element_relative_tap_label.yaml │ │ ├── 029_element_relative_tap_size.yaml │ │ ├── 029_element_relative_tap_text_percentage.yaml │ │ ├── 029_element_relative_tap_with_repeat.yaml │ │ ├── 029_pure_point_tap.yaml │ │ ├── 029_regular_element_tap.yaml │ │ ├── 030_setPermissions.yaml │ │ ├── 031_setOrientation.yaml │ │ └── 032_setOrientation_error.yaml │ ├── 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 │ ├── 013_execution_order/ │ │ ├── config.yaml │ │ ├── flowA.yaml │ │ ├── flowB.yaml │ │ ├── flowCWithCustomName.yaml │ │ └── flowD.yaml │ ├── 014_config_not_null/ │ │ ├── config/ │ │ │ └── another_config.yaml │ │ ├── config.yaml │ │ ├── flowA.yaml │ │ └── flowB.yaml │ ├── 015_workspace_cloud_configs/ │ │ ├── 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 │ ├── 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 │ └── workspace_validator_flow.yaml ├── 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 │ └── test/ │ └── kotlin/ │ └── maestro/ │ └── orchestra/ │ ├── CommandsTest.kt │ ├── ElementSelectorTest.kt │ └── util/ │ └── EnvTest.kt ├── 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 │ ├── build.gradle │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ └── index.html │ ├── src/ │ │ ├── App.tsx │ │ ├── api/ │ │ │ └── api.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 │ │ └── style/ │ │ └── index.css │ ├── 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/ │ │ ├── DeepestMatchingElementTest.kt │ │ ├── FlowControllerTest.kt │ │ ├── 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 │ ├── 098_runscript_conditionals_eager.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 │ ├── 122_pause_resume.yaml │ ├── 123_pause_resume_preserves_js_engine.yaml │ ├── 124_cancellation_during_flow_execution.yaml │ ├── 125_assert_by_css.yaml │ ├── 126_set_orientation.yaml │ ├── 126_set_orientation_with_env.yaml │ ├── 127_env_vars_isolation_graaljs.yaml │ ├── 127_env_vars_isolation_rhinojs.yaml │ ├── 127_script.js │ ├── 127_script_mutate_env_var.js │ ├── 128_datafaker_graaljs.yaml │ ├── 129_text_and_id.yaml │ ├── 130_text_and_index.yaml │ ├── 131_setPermissions.yaml │ ├── 132_repeat_while_timeout.yaml │ ├── 133_setClipboard.yaml │ ├── 134_take_screenshot_with_path.yaml │ ├── 135_screen_recording_with_path.yaml │ ├── 136_js_http_multi_part_requests.yaml │ ├── 137_shard_device_env_vars.yaml │ ├── 138_take_cropped_screenshot.yaml │ └── script/ │ └── multipart_request_file_script.js ├── 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 │ │ ├── TempFileHandler.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/ │ ├── cdp/ │ │ └── CdpClient.kt │ ├── record/ │ │ ├── JcodecVideoEncoder.kt │ │ ├── VideoEncoder.kt │ │ └── WebScreenRecorder.kt │ └── selenium/ │ ├── ChromeSeleniumFactory.kt │ └── SeleniumFactory.kt ├── scripts/ │ └── install.sh ├── settings.gradle.kts └── tmp.sh
SYMBOL INDEX (198 symbols across 108 files)
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/fetchTestUser.js
function getTestUserFromApi (line 2) | function getTestUserFromApi() {
FILE: e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/generateCredentials.js
function username (line 1) | function username() {
function email (line 7) | function email() {
function password (line 13) | function password() {
FILE: maestro-client/src/main/resources/maestro-web.js
function animate (line 278) | function animate(now) {
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/CDStructures.h
type CDStruct_a561fd19 (line 11) | typedef struct {
type CDStruct_27a325c0 (line 21) | typedef struct {
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCAXClient_iOS.h
function interface (line 12) | interface XCAXClient_iOS : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCActivityRecord.h
function interface (line 9) | interface XCActivityRecord : NSObject <NSSecureCoding>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCApplicationMonitor.h
function interface (line 11) | interface XCApplicationMonitor : NSObject <XCTUIApplicationMonitor>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCApplicationMonitor_iOS.h
function interface (line 9) | interface XCApplicationMonitor_iOS : XCApplicationMonitor
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCApplicationQuery.h
function interface (line 11) | interface XCApplicationQuery : XCUIElementQuery
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCEventGenerator.h
function interface (line 17) | interface XCEventGenerator : NSObject
type CGPoint (line 47) | struct CGPoint
type __CGEvent (line 66) | struct __CGEvent
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCKeyMappingPath.h
function interface (line 9) | interface XCKeyMappingPath : NSObject <NSCopying>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCKeyboardInputSolver.h
function interface (line 9) | interface XCKeyboardInputSolver : NSObject <NSCopying>
type _NSRange (line 34) | struct _NSRange
type _NSRange (line 35) | struct _NSRange
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCKeyboardKeyMap.h
function interface (line 11) | interface XCKeyboardKeyMap : NSObject
type _NSRange (line 67) | struct _NSRange
type _NSRange (line 68) | struct _NSRange
type _NSRange (line 70) | struct _NSRange
type _NSRange (line 74) | struct _NSRange
type _NSRange (line 74) | struct _NSRange
type _NSRange (line 75) | struct _NSRange
type _NSRange (line 76) | struct _NSRange
type __GSKeyboard (line 98) | struct __GSKeyboard
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCKeyboardLayout.h
function interface (line 9) | interface XCKeyboardLayout : NSObject
type __GSKeyboard (line 23) | struct __GSKeyboard
type __GSKeyboard (line 32) | struct __GSKeyboard
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCPointerEvent.h
function interface (line 7) | interface XCPointerEvent : NSObject <NSSecureCoding>
type CGPoint (line 22) | struct CGPoint
type CGPoint (line 23) | struct CGPoint
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCPointerEventPath.h
function interface (line 9) | interface XCPointerEventPath : NSObject <NSSecureCoding>
type CGPoint (line 27) | struct CGPoint
type CGPoint (line 30) | struct CGPoint
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSourceCodeRecording.h
function interface (line 9) | interface XCSourceCodeRecording : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSourceCodeTreeNode.h
function interface (line 9) | interface XCSourceCodeTreeNode : NSObject <NSSecureCoding>
type _NSRange (line 42) | struct _NSRange
type _NSRange (line 43) | struct _NSRange
type _NSRange (line 43) | struct _NSRange
type _NSRange (line 43) | struct _NSRange
type _NSRange (line 43) | struct _NSRange
type _NSRange (line 44) | struct _NSRange
type _NSRange (line 44) | struct _NSRange
type _NSRange (line 62) | struct _NSRange
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSourceCodeTreeNodeEnumerator.h
function interface (line 9) | interface XCSourceCodeTreeNodeEnumerator : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSymbolicationRecord.h
function interface (line 9) | interface XCSymbolicationRecord : NSObject
type _CSTypeRef (line 25) | struct _CSTypeRef
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSymbolicatorHolder.h
function interface (line 7) | interface XCSymbolicatorHolder : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h
function interface (line 9) | interface XCSynthesizedEventRecord : NSObject <NSSecureCoding>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTAsyncActivity.h
function interface (line 13) | interface XCTAsyncActivity : XCTestExpectation <XCTAsyncActivity>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTDarwinNotificationExpectation.h
function interface (line 11) | interface XCTDarwinNotificationExpectation : XCTestExpectation
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTKVOExpectation.h
function interface (line 11) | interface XCTKVOExpectation : XCTestExpectation
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTMetric.h
function interface (line 11) | interface XCTMetric : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTNSNotificationExpectation.h
function interface (line 11) | interface XCTNSNotificationExpectation : XCTestExpectation
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTNSPredicateExpectation.h
function interface (line 11) | interface XCTNSPredicateExpectation : XCTestExpectation
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTRunnerAutomationSession.h
function interface (line 13) | interface XCTRunnerAutomationSession : NSObject <XCTRunnerAutomationSess...
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h
function interface (line 19) | interface XCTRunnerDaemonSession : NSObject <XCTestManager_TestsInterface>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTTestRunSession.h
function interface (line 11) | interface XCTTestRunSession : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTWaiterManager.h
function interface (line 11) | interface XCTWaiterManager : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTest.h
function interface (line 9) | interface XCTest : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestCase.h
function interface (line 13) | interface XCTestCase()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestCaseRun.h
function interface (line 9) | interface XCTestCaseRun : XCTestRun
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestCaseSuite.h
function interface (line 9) | interface XCTestCaseSuite : XCTestSuite
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestConfiguration.h
function interface (line 9) | interface XCTestConfiguration : NSObject <NSSecureCoding>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestContext.h
function interface (line 9) | interface XCTestContext : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestContextScope.h
function interface (line 9) | interface XCTestContextScope : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestExpectation.h
function interface (line 11) | interface XCTestExpectation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestExpectationWaiter.h
function interface (line 9) | interface XCTestExpectationWaiter : XCTWaiter
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestLog.h
function interface (line 13) | interface XCTestLog : XCTestObserver <XCTestObservation>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestManager_ManagerInterface-Protocol.h
type CGRect (line 48) | struct CGRect
type CGRect (line 49) | struct CGRect
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestMisuseObserver.h
function interface (line 13) | interface XCTestMisuseObserver : NSObject <XCTestObservation>
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestObservationCenter.h
function interface (line 9) | interface XCTestObservationCenter : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestObserver.h
function interface (line 7) | interface XCTestObserver : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestProbe.h
function interface (line 7) | interface XCTestProbe : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestRun.h
function interface (line 11) | interface XCTestRun ()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestSuite.h
function interface (line 11) | interface XCTestSuite : XCTest
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestSuiteRun.h
function interface (line 11) | interface XCTestSuiteRun : XCTestRun
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCTestWaiter.h
function interface (line 9) | interface XCTestWaiter : XCTWaiter
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIApplication.h
function interface (line 11) | interface XCUIApplication ()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIApplicationImpl.h
function interface (line 11) | interface XCUIApplicationImpl : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIApplicationProcess.h
function interface (line 17) | interface XCUIApplicationProcess : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUICoordinate.h
function interface (line 13) | interface XCUICoordinate ()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIElement.h
function interface (line 11) | interface XCUIElement ()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIElementAsynchronousHandlerWrapper.h
function interface (line 9) | interface XCUIElementAsynchronousHandlerWrapper : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIElementHitPointCoordinate.h
function interface (line 12) | interface XCUIElementHitPointCoordinate : XCUICoordinate
type CGPoint (line 17) | struct CGPoint
type CGVector (line 18) | struct CGVector
type CGVector (line 19) | struct CGVector
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIElementQuery.h
function interface (line 13) | interface XCUIElementQuery ()
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIHitPointResult.h
function interface (line 10) | interface XCUIHitPointResult : NSObject
type CGPoint (line 18) | struct CGPoint
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIRecorderNodeFinder.h
function interface (line 9) | interface XCUIRecorderNodeFinder : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIRecorderNodeFinderMatch.h
function interface (line 9) | interface XCUIRecorderNodeFinderMatch : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIRecorderTimingMessage.h
function interface (line 9) | interface XCUIRecorderTimingMessage : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIRecorderUtilities.h
function interface (line 9) | interface XCUIRecorderUtilities : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIScreen.h
function interface (line 7) | interface XCUIScreen()
type CGRect (line 15) | struct CGRect
type CGRect (line 16) | struct CGRect
type CGRect (line 17) | struct CGRect
type CGRect (line 18) | struct CGRect
type CGRect (line 19) | struct CGRect
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/XCUIScreenDataSource-Protocol.h
type CGRect (line 11) | struct CGRect
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCInternalTestRun.h
function interface (line 9) | interface _XCInternalTestRun : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCKVOExpectationImplementation.h
function interface (line 11) | interface _XCKVOExpectationImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTDarwinNotificationExpectationImplementation.h
function interface (line 11) | interface _XCTDarwinNotificationExpectationImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTNSNotificationExpectationImplementation.h
function interface (line 11) | interface _XCTNSNotificationExpectationImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTNSPredicateExpectationImplementation.h
function interface (line 11) | interface _XCTNSPredicateExpectationImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTWaiterImpl.h
function interface (line 11) | interface _XCTWaiterImpl : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestCaseImplementation.h
function interface (line 11) | interface _XCTestCaseImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestCaseInterruptionException.h
function interface (line 7) | interface _XCTestCaseInterruptionException : NSException
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestExpectationImplementation.h
function interface (line 9) | interface _XCTestExpectationImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestImplementation.h
function interface (line 9) | interface _XCTestImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestObservationCenterImplementation.h
function interface (line 9) | interface _XCTestObservationCenterImplementation : NSObject
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/PrivateHeaders/XCTest/_XCTestSuiteImplementation.h
function interface (line 11) | interface _XCTestSuiteImplementation : XCTest
FILE: maestro-ios-xctest-runner/maestro-driver-iosUITests/Utilities/XCAccessibilityElement.h
type __AXUIElement (line 10) | struct __AXUIElement
type __AXUIElement (line 17) | struct __AXUIElement
FILE: maestro-studio/web/src/api/api.ts
class HttpError (line 13) | class HttpError extends Error {
method constructor (line 14) | constructor(public status: number, public message: string) {
constant API (line 79) | const API = {
FILE: maestro-studio/web/src/components/commands/CommandCreator.tsx
type CommandCreatorProps (line 14) | type CommandCreatorProps = {
function CommandCreator (line 23) | function CommandCreator({
FILE: maestro-studio/web/src/components/commands/CommandInput.tsx
type CommandInputProps (line 7) | interface CommandInputProps {
type CombinedProps (line 15) | type CombinedProps = CommandInputProps &
FILE: maestro-studio/web/src/components/commands/CommandList.tsx
type CommandListProps (line 8) | interface CommandListProps {
function CommandList (line 15) | function CommandList({
FILE: maestro-studio/web/src/components/commands/CommandRow.tsx
type CommandRowProps (line 9) | interface CommandRowProps {
function CommandRow (line 16) | function CommandRow({
FILE: maestro-studio/web/src/components/commands/ReplHeader.tsx
type ReplHeaderProps (line 8) | interface ReplHeaderProps {
function ReplHeader (line 19) | function ReplHeader({
FILE: maestro-studio/web/src/components/common/AuthModal.tsx
function AuthModal (line 13) | function AuthModal({
FILE: maestro-studio/web/src/components/common/ChatGptApiKeyModal.tsx
function ChatGptApiKeyModal (line 17) | function ChatGptApiKeyModal({
FILE: maestro-studio/web/src/components/common/theme.tsx
type Theme (line 4) | type Theme = "light" | "dark";
FILE: maestro-studio/web/src/components/design-system/button.tsx
type CommonProps (line 96) | interface CommonProps extends React.ButtonHTMLAttributes<HTMLButtonEleme...
type ConditionalProps (line 122) | type ConditionalProps =
type ButtonProps (line 144) | type ButtonProps = CommonProps & ConditionalProps;
type ButtonGroupProps (line 146) | interface ButtonGroupProps extends React.ComponentPropsWithoutRef<"div"> {}
FILE: maestro-studio/web/src/components/design-system/checkbox.tsx
type CheckboxProps (line 32) | interface CheckboxProps
FILE: maestro-studio/web/src/components/design-system/dropdown-menu.tsx
type DropdownMenuContentType (line 61) | type DropdownMenuContentType = Omit<
type DropdownMenuPortalType (line 65) | type DropdownMenuPortalType = Omit<
type DropdownCombinedProps (line 69) | interface DropdownCombinedProps
FILE: maestro-studio/web/src/components/design-system/icon.tsx
type IconProps (line 25) | interface IconProps
function Icon (line 31) | function Icon({ className, iconName, size, ...props }: IconProps) {
FILE: maestro-studio/web/src/components/design-system/input.tsx
type InputWrapperProps (line 138) | interface InputWrapperProps extends LabelHTMLAttributes<HTMLLabelElement> {
type InpurLabelProps (line 145) | interface InpurLabelProps extends HtmlHTMLAttributes<HTMLElement> {
type InputProps (line 152) | interface InputProps
type TextareaProps (line 166) | interface TextareaProps
type InputHintProps (line 176) | interface InputHintProps extends HtmlHTMLAttributes<HTMLElement> {
function InputWrapper (line 185) | function InputWrapper({
function InputLabel (line 226) | function InputLabel({
function InputHint (line 425) | function InputHint({
FILE: maestro-studio/web/src/components/design-system/keyboard-key.tsx
type KeyboardKeyProps (line 3) | interface KeyboardKeyProps {
FILE: maestro-studio/web/src/components/design-system/link.tsx
type AnchorProps (line 56) | type AnchorProps = React.DetailedHTMLProps<
type ButtonProps (line 60) | type ButtonProps = React.DetailedHTMLProps<
type DivProps (line 64) | type DivProps = React.DetailedHTMLProps<
type CustomProps (line 69) | type CustomProps =
type CommonProps (line 74) | interface CommonProps extends React.HtmlHTMLAttributes<HTMLElement> {
type TagProps (line 84) | type TagProps = CustomProps & CommonProps;
FILE: maestro-studio/web/src/components/design-system/spinner.tsx
type SpinnerProps (line 42) | interface SpinnerProps
function Spinner (line 46) | function Spinner({ className, size, ...props }: SpinnerProps) {
FILE: maestro-studio/web/src/components/device-and-device-elements/ActionModal.tsx
type ActionModalProps (line 40) | interface ActionModalProps {
function ActionModal (line 46) | function ActionModal({
type ActionCommandListItemProps (line 416) | interface ActionCommandListItemProps {
FILE: maestro-studio/web/src/components/device-and-device-elements/AnnotatedScreenshot.tsx
type AnnotationState (line 6) | type AnnotationState = "default" | "hidden" | "hovered" | "selected";
FILE: maestro-studio/web/src/components/device-and-device-elements/DeviceWrapperAspectRatio.tsx
type AspectRatioContainerProps (line 4) | interface AspectRatioContainerProps
FILE: maestro-studio/web/src/components/device-and-device-elements/ElementsPanel.tsx
type ElementsPanelProps (line 16) | interface ElementsPanelProps {
function ElementsPanel (line 20) | function ElementsPanel({ closePanel }: ElementsPanelProps) {
type ElementListItemProps (line 263) | interface ElementListItemProps
FILE: maestro-studio/web/src/components/device-and-device-elements/InteractableDevice.tsx
function InteractableDevice (line 13) | function InteractableDevice({
type GestureEvent (line 68) | type GestureEvent = {
FILE: maestro-studio/web/src/components/device-and-device-elements/SelectedElementViewer.tsx
function SelectedElementViewer (line 8) | function SelectedElementViewer({
FILE: maestro-studio/web/src/components/interact/InteractPageLayout.tsx
function computeWidthClass (line 118) | function computeWidthClass(deviceScreen: DeviceScreen, showElementsPanel...
FILE: maestro-studio/web/src/context/AuthContext.tsx
type AuthProviderProps (line 5) | interface AuthProviderProps {
type AuthState (line 9) | interface AuthState {
FILE: maestro-studio/web/src/context/DeviceContext.tsx
type DeviceContextType (line 13) | interface DeviceContextType {
type DeviceProviderProps (line 28) | interface DeviceProviderProps {
FILE: maestro-studio/web/src/helpers/commandExample.ts
constant YAML_STRINGIFY_OPTIONS (line 4) | const YAML_STRINGIFY_OPTIONS: YAML.SchemaOptions = {
type CommandExample (line 16) | type CommandExample = {
type Selector (line 23) | type Selector =
FILE: maestro-studio/web/src/helpers/models.ts
type HTMLProps (line 3) | type HTMLProps<T> = React.DetailedHTMLProps<React.HTMLAttributes<T>, T>;
type TextAreaProps (line 4) | type TextAreaProps = React.DetailedHTMLProps<
type DivProps (line 8) | type DivProps = HTMLProps<HTMLDivElement>;
type UIElementBounds (line 10) | type UIElementBounds = {
type UIElement (line 17) | type UIElement = {
type DeviceScreen (line 28) | type DeviceScreen = {
type ReplCommandStatus (line 37) | type ReplCommandStatus =
type ReplCommand (line 44) | type ReplCommand = {
type Repl (line 50) | type Repl = {
type FormattedFlow (line 54) | type FormattedFlow = {
type BannerMessage (line 59) | type BannerMessage = {
type AttributesType (line 64) | type AttributesType = {
type ViewHierarchyType (line 78) | type ViewHierarchyType = {
type AiResponseType (line 87) | type AiResponseType = {
type AuthType (line 91) | type AuthType = {
FILE: maestro-studio/web/src/pages/InteractPage.tsx
function InteractPage (line 5) | function InteractPage() {
Condensed preview — 1166 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,025K chars).
[
{
"path": ".editorconfig",
"chars": 335,
"preview": "# Copied from https://youtrack.jetbrains.com/issue/FL-15599/No-way-of-disabling-Java-Kotlin-wildcard-imports\n\n[*.java]\ni"
},
{
"path": ".gitattributes",
"chars": 154,
"preview": "#\n# https://help.github.com/articles/dealing-with-line-endings/\n#\n# These are explicitly windows files and should use cr"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
"chars": 7411,
"preview": "name: Report a bug\ndescription: You have a problem with Maestro.\nbody:\n - type: markdown\n attributes:\n value: >"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
"chars": 2291,
"preview": "name: Suggest a feature\ndescription: You want to share a new idea to improve Maestro.\nbody:\n - type: markdown\n attri"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 263,
"preview": "## Proposed changes\n\ncopilot:summary\n\n## Testing\n\n<!--- Please describe how you tested your changes. -->\n\n> **Does this "
},
{
"path": ".github/scripts/boot_simulator.sh",
"chars": 1046,
"preview": "#!/bin/bash\n\n# Specify the device type and runtime as per your requirements\nDEVICE_TYPE=\"${DEVICE_TYPE:-iPhone 15 Pro}\"\n"
},
{
"path": ".github/workflows/close-inactive-issues.yaml",
"chars": 1436,
"preview": "# Close issues that have had \"waiting for customer response\" label for too long.\n\n# This workflow is based on a very sim"
},
{
"path": ".github/workflows/lock-closed-issues.yaml",
"chars": 1087,
"preview": "# Lock closed issues that have been inactive for a while.\n\n# This workflow is copied from Flutter\n# https://github.com/f"
},
{
"path": ".github/workflows/publish-cli.yaml",
"chars": 683,
"preview": "name: Publish CLI\n\non:\n workflow_dispatch:\n\njobs:\n publish:\n runs-on: ubuntu-latest\n if: github.repository == 'm"
},
{
"path": ".github/workflows/publish-release.yaml",
"chars": 6652,
"preview": "name: Publish Release\n\non:\n workflow_dispatch:\n push:\n tags:\n - 'v*'\n\nenv:\n ORG_GRADLE_PROJECT_mavenCentralUs"
},
{
"path": ".github/workflows/publish-snapshot.yaml",
"chars": 3891,
"preview": "name: Publish Snapshot\n\non:\n workflow_dispatch:\n push:\n branches:\n - main\n\njobs:\n publish:\n runs-on: ubunt"
},
{
"path": ".github/workflows/test-e2e-ios-intel.yaml",
"chars": 3914,
"preview": "name: Test E2E on iOS (Intel)\n\non:\n workflow_dispatch:\n\njobs:\n build:\n name: Build on Java ${{ matrix.java-version "
},
{
"path": ".github/workflows/test-e2e-prod.yaml",
"chars": 2701,
"preview": "name: Test E2E (prod)\n\non:\n workflow_dispatch:\n schedule:\n - cron: '0 * * * *'\n\njobs:\n test-cloud-production:\n "
},
{
"path": ".github/workflows/test-e2e.yaml",
"chars": 13083,
"preview": "name: Test E2E\n\non:\n workflow_dispatch:\n pull_request:\n\njobs:\n build:\n name: Build on Java ${{ matrix.java-version"
},
{
"path": ".github/workflows/test.yaml",
"chars": 2126,
"preview": "name: Test\n\non:\n workflow_dispatch:\n pull_request:\n\njobs:\n unit-test:\n name: Unit Test on Java ${{ matrix.java-ver"
},
{
"path": ".github/workflows/update-samples.yaml",
"chars": 808,
"preview": "name: Update samples\n\non:\n workflow_dispatch:\n push:\n branches: [main]\n\njobs:\n main:\n runs-on: ubuntu-latest\n "
},
{
"path": ".gitignore",
"chars": 259,
"preview": ".DS_Store\n\n# Ignore Gradle project-specific cache directory\n.gradle\n\n# Ignore Gradle build output directory\nbuild\n\n# Ign"
},
{
"path": ".idea/.gitignore",
"chars": 679,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local sto"
},
{
"path": ".idea/.name",
"chars": 7,
"preview": "maestro"
},
{
"path": ".idea/dictionaries/project.xml",
"chars": 1390,
"preview": "<component name=\"ProjectDictionaryState\">\n <dictionary name=\"project\">\n <words>\n <w>addmedia</w>\n <w>amanj"
},
{
"path": ".run/cli-version.run.xml",
"chars": 451,
"preview": "<component name=\"ProjectRunConfigurationManager\">\n <configuration default=\"false\" name=\"CLI | version\" type=\"JetRunConf"
},
{
"path": ".run/cli.run.xml",
"chars": 502,
"preview": "<component name=\"ProjectRunConfigurationManager\">\n <configuration default=\"false\" name=\"CLI\" type=\"JetRunConfigurationT"
},
{
"path": "CHANGELOG.md",
"chars": 56766,
"preview": "# Changelog\n\n## Unreleased\n\n## 2.4.0\n\n- Add new device config flags for cloud and start-device\n - Deprecated `--ios-v"
},
{
"path": "CONTRIBUTING.md",
"chars": 8802,
"preview": "# Contributing to Maestro\n\nThank you for considering contributing to the project!\n\nWe welcome contributions from everyon"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 4898,
"preview": "> [!TIP]\n> Great things happen when testers connect — [Join the Maestro Community](https://maestrodev.typeform.com/to/Fe"
},
{
"path": "RELEASING.md",
"chars": 2239,
"preview": "# Production Releases\n\n## Prepare\n\n1. Define the next semantic version\n\n Semantic versioning: a.b.c\n\n - a: major bre"
},
{
"path": "build.gradle.kts",
"chars": 848,
"preview": "import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask\n\n@Suppress(\"DSL_SCOPE_VIOLATION\")\nplugins {\n alias(lib"
},
{
"path": "detekt.yml",
"chars": 21443,
"preview": "build:\n maxIssues: 0\n excludeCorrectable: false\n weights:\n # complexity: 2\n # LongParameterList: 1\n # style: 1\n #"
},
{
"path": "e2e/.gitignore",
"chars": 28,
"preview": "apps/\n\nsamples/\nsamples.zip\n"
},
{
"path": "e2e/README.md",
"chars": 1744,
"preview": "# e2e\n\nThis directory contains glue code for testing Maestro itself.\n\n## Testing\n\nTypical workflow is:\n\n1. Start Android"
},
{
"path": "e2e/download_apps",
"chars": 1040,
"preview": "#!/usr/bin/env sh\nset -eu\n\n# Download apps from URLs listed in manifest.txt.\n#\n# We assume that if the downloaded file i"
},
{
"path": "e2e/install_apps",
"chars": 974,
"preview": "#!/usr/bin/env sh\nset -eu\n\n# Install all apps from apps/ directory (that was previously created with\n# download_apps).\n#"
},
{
"path": "e2e/manifest.txt",
"chars": 394,
"preview": "https://storage.googleapis.com/mobile.dev/cli_e2e/wikipedia.apk\nhttps://storage.googleapis.com/mobile.dev/cli_e2e/wikipe"
},
{
"path": "e2e/run_tests",
"chars": 2811,
"preview": "#!/usr/bin/env sh\nset -eu\n\n# Runs all tests in the workspaces directory.\n\ncommand -v maestro >/dev/null 2>&1 || { echo \""
},
{
"path": "e2e/update_samples",
"chars": 967,
"preview": "#!/usr/bin/env sh\nset -eu\n\n# Updates the samples that are hosted in mobile.dev's GCS bucket ($SAMPLES_URL).\n# The sample"
},
{
"path": "e2e/workspaces/setOrientation/test-set-orientation-flow.yaml",
"chars": 803,
"preview": "appId: com.example.maestro.orientation\nenv:\n orientationLandscapeLeft: LANDSCAPE_LEFT\n orientationLandscapeRight: LAND"
},
{
"path": "e2e/workspaces/simple_web_view/webview.yaml",
"chars": 289,
"preview": "appId: com.example.SimpleWebViewApp\ntags:\n - passing\n - ios\n---\n- launchApp:\n clearState: true\n\n- tapOn: Open Login"
},
{
"path": "e2e/workspaces/wikipedia/android-advanced-flow.yaml",
"chars": 377,
"preview": "appId: org.wikipedia\ntags:\n - android\n - passing\n - advanced\n---\n- runFlow: subflows/onboarding-android.yaml\n- tapOn:"
},
{
"path": "e2e/workspaces/wikipedia/android-flow.yaml",
"chars": 67,
"preview": "appId: org.wikipedia\ntags:\n - android\n - passing\n---\n- launchApp\n"
},
{
"path": "e2e/workspaces/wikipedia/ios-advanced-flow.yaml",
"chars": 740,
"preview": "appId: org.wikimedia.wikipedia\ntags:\n - ios\n - passing\n - advanced\n---\n- runFlow: subflows/onboarding-ios.yaml\n\n- run"
},
{
"path": "e2e/workspaces/wikipedia/ios-flow.yaml",
"chars": 73,
"preview": "appId: org.wikimedia.wikipedia\ntags:\n - ios\n - passing\n---\n- launchApp\n"
},
{
"path": "e2e/workspaces/wikipedia/scripts/getSearchQuery.js",
"chars": 26,
"preview": "output.result = 'qwerty';\n"
},
{
"path": "e2e/workspaces/wikipedia/subflows/launch-clearstate-android.yaml",
"chars": 110,
"preview": "appId: org.wikipedia\n---\n- launchApp:\n clearState: true\n- assertVisible: \"Continue\"\n- assertVisible: \"Skip\""
},
{
"path": "e2e/workspaces/wikipedia/subflows/launch-clearstate-ios.yaml",
"chars": 116,
"preview": "appId: org.wikimedia.wikipedia\n---\n- launchApp:\n clearState: true\n- assertVisible: \"Next\"\n- assertVisible: \"Skip\""
},
{
"path": "e2e/workspaces/wikipedia/subflows/onboarding-android.yaml",
"chars": 402,
"preview": "appId: org.wikipedia\n---\n- launchApp:\n clearState: true\n- tapOn:\n text: \"Non existent view\"\n optional: true"
},
{
"path": "e2e/workspaces/wikipedia/subflows/onboarding-ios.yaml",
"chars": 280,
"preview": "appId: org.wikimedia.wikipedia\n---\n- launchApp:\n clearState: true\n- repeat:\n times: 3\n commands:\n - swipe:"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/login.yml",
"chars": 296,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"More\"\n- tapOn: \"LOG IN.*\"\n- tapOn:\n id: \".*create_account_login_button\"\n- runScrip"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/auth/signup.yml",
"chars": 431,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"More\"\n- tapOn: \"LOG IN.*\"\n- runScript: \"../scripts/generateCredentials.js\"\n- tapOn: \""
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/copy-paste.yml",
"chars": 255,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"Explore\"\n- scrollUntilVisible:\n element: \"Top read\"\n- copyTextFrom:\n id: \".*vie"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/feed.yml",
"chars": 141,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"Explore\"\n- scrollUntilVisible:\n element: \"Today on Wikipedia.*\"\n- tapOn: \"Today on"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/main.yml",
"chars": 122,
"preview": "appId: org.wikipedia\n---\n- runFlow: \"search.yml\"\n- runFlow: \"saved.yml\"\n- runFlow: \"feed.yml\"\n- runFlow: \"copy-paste.yml"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/saved.yml",
"chars": 178,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"Saved\"\n- tapOn: \"Default list for your saved articles\"\n- assertVisible: \"Sun\"\n- asser"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/dashboard/search.yml",
"chars": 224,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"Search Wikipedia\"\n- inputText: \"Sun\"\n- assertVisible: \"Star at the center of the Sola"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/add-language.yml",
"chars": 208,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"ADD OR EDIT.*\"\n- tapOn: \"ADD LANGUAGE\"\n- tapOn:\n id: \".*menu_search_language\"\n- in"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/main.yml",
"chars": 292,
"preview": "appId: org.wikipedia\n---\n- runFlow: \"add-language.yml\"\n- runFlow: \"remove-language.yml\"\n- tapOn: \"Continue\"\n- assertVisi"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/onboarding/remove-language.yml",
"chars": 268,
"preview": "appId: org.wikipedia\n---\n- tapOn: \"ADD OR EDIT.*\"\n- tapOn: \"More options\"\n- tapOn: \"Remove language\"\n- tapOn:\n id: \"."
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/run-test.yml",
"chars": 211,
"preview": "appId: org.wikipedia\ntags:\n - android\n - passing\n---\n- launchApp:\n clearState: true\n- runFlow: \"onboarding/main.yml"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/fetchTestUser.js",
"chars": 303,
"preview": "// Fetches test user from API\nfunction getTestUserFromApi() {\n const url = `https://jsonplaceholder.typicode.com/users/"
},
{
"path": "e2e/workspaces/wikipedia/wikipedia-android-advanced/scripts/generateCredentials.js",
"chars": 584,
"preview": "function username() {\n var date = new Date().getTime().toString();\n var username = `test_user_placeholder`.replace(\"pl"
},
{
"path": "gradle/libs.versions.toml",
"chars": 10172,
"preview": "# File should be sorted by alphabet for each section\n\n# How to sort with AS:\n# \"Select all in block\" -> \"Edit\" -> \"Sort "
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 251,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 823,
"preview": "android.useAndroidX=true\nandroid.enableJetifier=true\nkotlin.code.style=official\nGROUP=dev.mobile\nVERSION_NAME=2.4.0\nPOM_"
},
{
"path": "gradlew",
"chars": 8729,
"preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "gradlew.bat",
"chars": 2966,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "installLocally.sh",
"chars": 213,
"preview": "#!/bin/sh\n\n./gradlew :maestro-cli:installDist\n\nrm -rf ~/.maestro/bin\nrm -rf ~/.maestro/lib\n\ncp -r ./maestro-cli/build/in"
},
{
"path": "maestro",
"chars": 178,
"preview": "#!/usr/bin/env bash\n\nset -e\n\nif [ -t 0 ]; then\n input=\"\"\nelse\n input=$(cat -)\nfi\n\n./gradlew :maestro-cli:installDist -"
},
{
"path": "maestro-ai/README.md",
"chars": 925,
"preview": "# maestro-ai\n\nThis project implements AI support for use in Maestro.\n\nIt's both a library and an executable demo app.\n\n#"
},
{
"path": "maestro-ai/build.gradle.kts",
"chars": 1604,
"preview": "import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask\n\nplugins {\n application\n id(\"maven-publish\")\n al"
},
{
"path": "maestro-ai/gradle.properties",
"chars": 65,
"preview": "POM_NAME=Maestro AI\nPOM_ARTIFACT_ID=maestro-ai\nPOM_PACKAGING=jar\n"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/AI.kt",
"chars": 1602,
"preview": "package maestro.ai\n\nimport io.ktor.client.HttpClient\nimport io.ktor.client.plugins.HttpTimeout\nimport io.ktor.client.plu"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/CloudPredictionAIEngine.kt",
"chars": 613,
"preview": "package maestro.ai\n\nimport maestro.ai.cloud.Defect\nimport maestro.ai.Prediction\n\nclass CloudAIPredictionEngine(private v"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/DemoApp.kt",
"chars": 7111,
"preview": "package maestro.ai\n\nimport com.github.ajalt.clikt.core.CliktCommand\nimport com.github.ajalt.clikt.parameters.arguments.a"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/IAPredictionEngine.kt",
"chars": 296,
"preview": "package maestro.ai\n\nimport maestro.ai.cloud.Defect\n\ninterface AIPredictionEngine {\n suspend fun findDefects(screen: B"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/Prediction.kt",
"chars": 835,
"preview": "package maestro.ai\n\nimport maestro.ai.cloud.ApiClient\nimport maestro.ai.cloud.Defect\n\nobject Prediction {\n private va"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/anthropic/Client.kt",
"chars": 4062,
"preview": "package maestro.ai.anthropic\n\nimport Response\nimport io.ktor.client.HttpClient\nimport io.ktor.client.plugins.HttpTimeout"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/anthropic/Common.kt",
"chars": 471,
"preview": "package maestro.ai.anthropic\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n@Serial"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/anthropic/Request.kt",
"chars": 255,
"preview": "package maestro.ai.anthropic\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n@Serial"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/anthropic/Response.kt",
"chars": 148,
"preview": "import kotlinx.serialization.Serializable\nimport maestro.ai.anthropic.Content\n\n@Serializable\ndata class Response(\n va"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/cloud/ApiClient.kt",
"chars": 4189,
"preview": "package maestro.ai.cloud\n\nimport io.ktor.client.*\nimport io.ktor.client.plugins.*\nimport io.ktor.client.plugins.contentn"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/common/Image.kt",
"chars": 155,
"preview": "package maestro.ai.common\n\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class Base64Image(\n val url:"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/openai/Client.kt",
"chars": 4000,
"preview": "package maestro.ai.openai\n\nimport io.ktor.client.HttpClient\nimport io.ktor.client.request.post\nimport io.ktor.client.req"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/openai/Request.kt",
"chars": 857,
"preview": "package maestro.ai.openai\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport kotl"
},
{
"path": "maestro-ai/src/main/java/maestro/ai/openai/Response.kt",
"chars": 1012,
"preview": "package maestro.ai.openai\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n@Serializa"
},
{
"path": "maestro-ai/src/main/resources/askForDefects_schema.json",
"chars": 742,
"preview": "{\n \"name\": \"askForDefects\",\n \"description\": \"Returns a list of possible defects found in the mobile app's UI\",\n \"stri"
},
{
"path": "maestro-ai/src/main/resources/extractText_schema.json",
"chars": 305,
"preview": "{\n \"name\": \"extractText\",\n \"description\": \"Extracts text from an image based on a given query\",\n \"strict\": true,\n \"s"
},
{
"path": "maestro-android/build.gradle.kts",
"chars": 5072,
"preview": "import org.jetbrains.kotlin.config.JvmTarget\n\nplugins {\n alias(libs.plugins.android.application)\n alias(libs.plugi"
},
{
"path": "maestro-android/src/androidTest/AndroidManifest.xml",
"chars": 786,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:tools=\"http://schemas.android.com/tools\"\n xmlns:android="
},
{
"path": "maestro-android/src/androidTest/java/androidx/test/uiautomator/UiDeviceExt.kt",
"chars": 391,
"preview": "package androidx.test.uiautomator\n\nobject UiDeviceExt {\n\n /**\n * Fix for a UiDevice.click() method that discards "
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/AccessibilityNodeInfoExt.kt",
"chars": 750,
"preview": "package dev.mobile.maestro\n\nimport android.os.Build\nimport android.view.accessibility.AccessibilityNodeInfo\n\nobject Acce"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/MaestroDriverService.kt",
"chars": 23665,
"preview": "package dev.mobile.maestro\n\nimport android.app.UiAutomation\nimport android.content.Context\nimport android.content.Contex"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/Media.kt",
"chars": 1266,
"preview": "package dev.mobile.maestro\n\nimport android.content.ContentValues\nimport android.provider.MediaStore\nimport androidx.test"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/ToastAccessibilityListener.kt",
"chars": 2326,
"preview": "package dev.mobile.maestro\n\nimport android.app.UiAutomation\nimport android.os.Build\nimport android.util.Log\nimport andro"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/ViewHierarchy.kt",
"chars": 12413,
"preview": "package dev.mobile.maestro\n\nimport android.app.UiAutomation\nimport android.content.Context\nimport android.graphics.Rect\n"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/location/FusedLocationProvider.kt",
"chars": 793,
"preview": "package dev.mobile.maestro.location\n\nimport android.location.Location\nimport com.google.android.gms.location.FusedLocati"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/location/LocationManagerProvider.kt",
"chars": 1365,
"preview": "package dev.mobile.maestro.location\n\nimport android.location.Location\nimport android.location.LocationManager\n\nclass Loc"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/location/MockLocationProvider.kt",
"chars": 218,
"preview": "package dev.mobile.maestro.location\n\nimport android.location.Location\n\ninterface MockLocationProvider {\n\n fun setLoca"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/location/PlayServices.kt",
"chars": 460,
"preview": "package dev.mobile.maestro.location\n\nimport android.content.Context\nimport com.google.android.gms.common.ConnectionResul"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/screenshot/ScreenshotService.kt",
"chars": 3354,
"preview": "package dev.mobile.maestro.screenshot\n\nimport android.graphics.Bitmap\nimport com.github.michaelbull.retry.policy.binaryE"
},
{
"path": "maestro-android/src/androidTest/java/dev/mobile/maestro/screenshot/ScreenshotServiceTest.kt",
"chars": 4417,
"preview": "package dev.mobile.maestro.screenshot\n\nimport android.graphics.Bitmap\nimport androidx.test.ext.junit.runners.AndroidJUni"
},
{
"path": "maestro-android/src/main/AndroidManifest.xml",
"chars": 1253,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:tools=\"http://schemas.android.com/tools\"\n xmlns:android=\"http:"
},
{
"path": "maestro-android/src/main/java/dev/mobile/maestro/handlers/AbstractSettingHandler.kt",
"chars": 893,
"preview": "package dev.mobile.maestro.handlers\n\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport andr"
},
{
"path": "maestro-android/src/main/java/dev/mobile/maestro/handlers/LocaleSettingHandler.kt",
"chars": 2604,
"preview": "package dev.mobile.maestro.handlers\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport androi"
},
{
"path": "maestro-android/src/main/java/dev/mobile/maestro/receivers/HasAction.kt",
"chars": 86,
"preview": "package dev.mobile.maestro.receivers\n\ninterface HasAction {\n fun action(): String\n}"
},
{
"path": "maestro-android/src/main/java/dev/mobile/maestro/receivers/LocaleSettingReceiver.kt",
"chars": 4882,
"preview": "package dev.mobile.maestro.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport and"
},
{
"path": "maestro-android/src/main/res/values/stub.xml",
"chars": 77,
"preview": "<resources>\n <string name=\"app_name\">Maestro Driver</string>\n</resources>\n"
},
{
"path": "maestro-cli/build.gradle.kts",
"chars": 10830,
"preview": "import org.jreleaser.model.Active.ALWAYS\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask\nimport org.jrele"
},
{
"path": "maestro-cli/gradle.properties",
"chars": 18,
"preview": "CLI_VERSION=2.4.0\n"
},
{
"path": "maestro-cli/src/jreleaser/distributions/maestro/brew/formula.rb.tpl",
"chars": 904,
"preview": "# {{jreleaserCreationStamp}}\n{{#brewRequireRelative}}\nrequire_relative \"{{.}}\"\n{{/brewRequireRelative}}\n\nclass {{brewFor"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/App.kt",
"chars": 6620,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/CliError.kt",
"chars": 94,
"preview": "package maestro.cli\n\nclass CliError(override val message: String) : RuntimeException(message)\n"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/Dependencies.kt",
"chars": 336,
"preview": "package maestro.cli\n\nimport maestro.cli.util.Unpacker.binaryDependency\nimport maestro.cli.util.Unpacker.unpack\n\nobject D"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/DisableAnsiMixin.kt",
"chars": 1989,
"preview": "package maestro.cli\n\nimport org.fusesource.jansi.Ansi\nimport org.fusesource.jansi.AnsiConsole\nimport org.fusesource.jans"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/ShowHelpMixin.kt",
"chars": 233,
"preview": "package maestro.cli\n\nimport picocli.CommandLine\n\nclass ShowHelpMixin {\n @CommandLine.Option(\n names = [\"-h\", \""
},
{
"path": "maestro-cli/src/main/java/maestro/cli/analytics/Analytics.kt",
"chars": 8659,
"preview": "package maestro.cli.analytics\n\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.fasterxml.jackson"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/analytics/AnalyticsStateManager.kt",
"chars": 8474,
"preview": "package maestro.cli.analytics\n\nimport com.fasterxml.jackson.annotation.JsonFormat\nimport com.fasterxml.jackson.annotatio"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/analytics/PostHogEvents.kt",
"chars": 8988,
"preview": "package maestro.cli.analytics\n\nimport maestro.cli.model.FlowStatus\nimport maestro.cli.util.EnvUtils\nimport maestro.cli.u"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/api/ApiClient.kt",
"chars": 33698,
"preview": "package maestro.cli.api\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties\nimport com.fasterxml.jackson.core."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/api/Chatbot.kt",
"chars": 801,
"preview": "package maestro.cli.api\n\nimport com.fasterxml.jackson.annotation.JsonProperty\n\ndata class MessageRequest(\n @JsonPrope"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/auth/Auth.kt",
"chars": 4865,
"preview": "package maestro.cli.auth\n\nimport io.ktor.http.*\nimport io.ktor.server.application.*\nimport io.ktor.server.engine.*\nimpor"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/cloud/CloudInteractor.kt",
"chars": 28268,
"preview": "package maestro.cli.cloud\n\nimport maestro.cli.CliError\nimport maestro.cli.analytics.Analytics\nimport maestro.cli.analyti"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/BugReportCommand.kt",
"chars": 858,
"preview": "package maestro.cli.command\n\nimport maestro.cli.DisableAnsiMixin\nimport maestro.cli.ShowHelpMixin\nimport maestro.debuglo"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/ChatCommand.kt",
"chars": 2746,
"preview": "package maestro.cli.command\n\nimport maestro.auth.ApiKey\nimport maestro.cli.api.ApiClient\nimport maestro.cli.auth.Auth\nim"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/CheckSyntaxCommand.kt",
"chars": 1134,
"preview": "package maestro.cli.command\n\nimport maestro.cli.CliError\nimport maestro.orchestra.error.SyntaxError\nimport maestro.orche"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/CloudCommand.kt",
"chars": 12279,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/DownloadSamplesCommand.kt",
"chars": 2708,
"preview": "package maestro.cli.command\n\nimport kotlinx.coroutines.runBlocking\nimport maestro.cli.DisableAnsiMixin\nimport maestro.cl"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/DriverCommand.kt",
"chars": 1389,
"preview": "package maestro.cli.command\n\nimport maestro.cli.driver.DriverBuilder\nimport maestro.cli.driver.RealIOSDeviceDriver\nimpor"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/ListCloudDevicesCommand.kt",
"chars": 3122,
"preview": "package maestro.cli.command\n\nimport maestro.cli.App\nimport maestro.cli.CliError\nimport maestro.cli.ShowHelpMixin\nimport "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/ListDevicesCommand.kt",
"chars": 3286,
"preview": "package maestro.cli.command\n\nimport maestro.cli.App\nimport maestro.cli.CliError\nimport maestro.cli.ShowHelpMixin\nimport "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/LoginCommand.kt",
"chars": 1317,
"preview": "package maestro.cli.command\n\nimport maestro.auth.ApiKey\nimport maestro.cli.DisableAnsiMixin\nimport maestro.cli.ShowHelpM"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/LogoutCommand.kt",
"chars": 1106,
"preview": "package maestro.cli.command\n\nimport maestro.cli.DisableAnsiMixin\nimport maestro.cli.ShowHelpMixin\nimport maestro.cli.ana"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/McpCommand.kt",
"chars": 851,
"preview": "package maestro.cli.command\n\nimport picocli.CommandLine\nimport java.util.concurrent.Callable\nimport maestro.cli.mcp.runM"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/PrintHierarchyCommand.kt",
"chars": 8264,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/QueryCommand.kt",
"chars": 3391,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/RecordCommand.kt",
"chars": 6990,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/StartDeviceCommand.kt",
"chars": 4107,
"preview": "package maestro.cli.command\n\nimport maestro.cli.App\nimport maestro.cli.CliError\nimport maestro.cli.ShowHelpMixin\nimport "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/StudioCommand.kt",
"chars": 4038,
"preview": "package maestro.cli.command\n\nimport maestro.cli.App\nimport maestro.cli.DisableAnsiMixin\nimport maestro.cli.ShowHelpMixin"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt",
"chars": 28386,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/db/KeyValueStore.kt",
"chars": 1187,
"preview": "package maestro.cli.db\n\nimport java.io.File\nimport java.util.concurrent.locks.ReentrantReadWriteLock\nimport kotlin.concu"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/device/DeviceCreateUtil.kt",
"chars": 7004,
"preview": "package maestro.cli.device\n\nimport maestro.device.DeviceService\nimport maestro.device.Device\nimport maestro.device.Platf"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/device/PickDeviceInteractor.kt",
"chars": 4090,
"preview": "package maestro.cli.device\n\nimport maestro.cli.CliError\nimport maestro.device.DeviceService\nimport maestro.device.Device"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/device/PickDeviceView.kt",
"chars": 2701,
"preview": "package maestro.cli.device\n\nimport maestro.cli.CliError\nimport maestro.cli.util.PrintUtils\nimport maestro.device.Device\n"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/driver/DriverBuildConfig.kt",
"chars": 437,
"preview": "package maestro.cli.driver\n\nimport maestro.cli.api.CliVersion\n\ndata class DriverBuildConfig(\n val teamId: String,\n "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/driver/DriverBuilder.kt",
"chars": 5966,
"preview": "package maestro.cli.driver\n\nimport maestro.MaestroException\nimport java.io.File\nimport java.nio.file.*\nimport java.util."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/driver/RealIOSDeviceDriver.kt",
"chars": 3566,
"preview": "package maestro.cli.driver\n\nimport maestro.MaestroException\nimport maestro.cli.api.CliVersion\nimport maestro.cli.util.En"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/driver/Spinner.kt",
"chars": 608,
"preview": "package maestro.cli.driver\n\nclass Spinner(private val message: String = \"Processing\") {\n private val frames = listOf("
},
{
"path": "maestro-cli/src/main/java/maestro/cli/driver/XcodeBuildProcessBuilderFactory.kt",
"chars": 351,
"preview": "package maestro.cli.driver\n\nimport java.io.File\n\nclass XcodeBuildProcessBuilderFactory {\n\n fun createProcess(commands"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/AWTUtils.kt",
"chars": 668,
"preview": "package maestro.cli.graphics\n\nimport org.jcodec.api.FrameGrab\nimport org.jcodec.api.awt.AWTSequenceEncoder\nimport org.jc"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/LocalVideoRenderer.kt",
"chars": 3347,
"preview": "package maestro.cli.graphics\n\nimport maestro.cli.runner.resultview.AnsiResultView\nimport maestro.cli.view.ProgressBar\nim"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/RemoteVideoRenderer.kt",
"chars": 3429,
"preview": "package maestro.cli.graphics\n\nimport maestro.cli.api.ApiClient\nimport maestro.cli.runner.resultview.AnsiResultView\nimpor"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/SkiaFrameRenderer.kt",
"chars": 7425,
"preview": "package maestro.cli.graphics\n\nimport org.jetbrains.skia.Canvas\nimport org.jetbrains.skia.Color\nimport org.jetbrains.skia"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/SkiaTextClipper.kt",
"chars": 2216,
"preview": "package maestro.cli.graphics\n\nimport org.jetbrains.skia.Canvas\nimport org.jetbrains.skia.Color\nimport org.jetbrains.skia"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/SkiaUtils.kt",
"chars": 3000,
"preview": "package maestro.cli.graphics\n\nimport org.jetbrains.skia.Bitmap\nimport org.jetbrains.skia.Canvas\nimport org.jetbrains.ski"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/graphics/VideoRenderer.kt",
"chars": 232,
"preview": "package maestro.cli.graphics\n\nimport maestro.cli.runner.resultview.AnsiResultView\nimport java.io.File\n\ninterface VideoRe"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/insights/TestAnalysisManager.kt",
"chars": 6471,
"preview": "package maestro.cli.insights\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties\nimport com.fasterxml.jackson."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/McpServer.kt",
"chars": 2932,
"preview": "package maestro.cli.mcp\n\nimport io.ktor.utils.io.streams.*\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelco"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/README.md",
"chars": 3594,
"preview": "# Maestro MCP Server\n\n## Overview\n\nThe Maestro MCP (Model Context Protocol) server enables LLM-driven automation and orc"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/BackTool.kt",
"chars": 2714,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/CheatSheetTool.kt",
"chars": 2768,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/CheckFlowSyntaxTool.kt",
"chars": 2274,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/InputTextTool.kt",
"chars": 3114,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/InspectViewHierarchyTool.kt",
"chars": 2753,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/LaunchAppTool.kt",
"chars": 3363,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/ListDevicesTool.kt",
"chars": 2692,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/QueryDocsTool.kt",
"chars": 4284,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/RunFlowFilesTool.kt",
"chars": 8291,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/RunFlowTool.kt",
"chars": 6764,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/StartDeviceTool.kt",
"chars": 5151,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/StopAppTool.kt",
"chars": 3125,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/TakeScreenshotTool.kt",
"chars": 2887,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/TapOnTool.kt",
"chars": 6978,
"preview": "package maestro.cli.mcp.tools\n\nimport io.modelcontextprotocol.kotlin.sdk.*\nimport io.modelcontextprotocol.kotlin.sdk.ser"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/mcp/tools/ViewHierarchyFormatters.kt",
"chars": 16205,
"preview": "package maestro.cli.mcp.tools\n\nimport com.fasterxml.jackson.annotation.JsonInclude\nimport com.fasterxml.jackson.module.k"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/model/FlowStatus.kt",
"chars": 784,
"preview": "package maestro.cli.model\n\nimport maestro.cli.api.UploadStatus\n\nenum class FlowStatus {\n PENDING,\n PREPARING,\n "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/model/RunningFlow.kt",
"chars": 351,
"preview": "package maestro.cli.model\n\nimport kotlin.time.Duration\n\ndata class RunningFlows(\n val flows: List<RunningFlow>,\n v"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/model/TestExecutionSummary.kt",
"chars": 1254,
"preview": "package maestro.cli.model\n\nimport kotlin.time.Duration\n\n// TODO: Some properties should be implemented as getters, but i"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/promotion/PromotionStateManager.kt",
"chars": 3190,
"preview": "package maestro.cli.promotion\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties\nimport com.fasterxml.jackson"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/HtmlAITestSuiteReporter.kt",
"chars": 7251,
"preview": "package maestro.cli.report\n\nimport kotlinx.html.a\nimport kotlinx.html.body\nimport kotlinx.html.button\nimport kotlinx.htm"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/HtmlInsightsAnalysisReporter.kt",
"chars": 542,
"preview": "package maestro.cli.report\n\nimport java.nio.file.Files\nimport java.nio.file.Path\n\nclass HtmlInsightsAnalysisReporter {\n\n"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/HtmlTestSuiteReporter.kt",
"chars": 12859,
"preview": "package maestro.cli.report\n\nimport kotlinx.html.*\nimport kotlinx.html.stream.appendHTML\nimport maestro.cli.model.TestExe"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/JUnitTestSuiteReporter.kt",
"chars": 5344,
"preview": "package maestro.cli.report\n\nimport com.fasterxml.jackson.annotation.JsonInclude\nimport com.fasterxml.jackson.annotation."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/ReportFormat.kt",
"chars": 832,
"preview": "package maestro.cli.report\n\nimport picocli.CommandLine\n\nenum class ReportFormat(\n val fileExtension: String?,\n pri"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/ReporterFactory.kt",
"chars": 550,
"preview": "package maestro.cli.report\n\nimport maestro.cli.model.TestExecutionSummary\nimport okio.BufferedSink\n\nobject ReporterFacto"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt",
"chars": 10398,
"preview": "package maestro.cli.report\n\nimport com.fasterxml.jackson.annotation.JsonInclude\nimport com.fasterxml.jackson.annotation."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/report/TestSuiteReporter.kt",
"chars": 1447,
"preview": "package maestro.cli.report\n\nimport maestro.cli.model.TestExecutionSummary\nimport okio.Sink\nimport java.time.Instant\nimpo"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/CliWatcher.kt",
"chars": 1430,
"preview": "package maestro.cli.runner\n\nimport java.io.InputStream\nimport java.nio.file.Path\nimport java.util.concurrent.Completable"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/CommandState.kt",
"chars": 1102,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/CommandStatus.kt",
"chars": 756,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/FileWatcher.kt",
"chars": 1691,
"preview": "package maestro.cli.runner\n\nimport java.nio.file.FileSystems\nimport java.nio.file.Path\nimport java.nio.file.StandardWatc"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/MaestroCommandRunner.kt",
"chars": 9644,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt",
"chars": 7662,
"preview": "package maestro.cli.runner\n\nimport com.github.michaelbull.result.Err\nimport com.github.michaelbull.result.Ok\nimport com."
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/TestSuiteInteractor.kt",
"chars": 12757,
"preview": "package maestro.cli.runner\n\nimport maestro.Maestro\nimport maestro.MaestroException\nimport maestro.cli.CliError\nimport ma"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/resultview/AnsiResultView.kt",
"chars": 13090,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/resultview/PlainTextResultView.kt",
"chars": 5279,
"preview": "package maestro.cli.runner.resultview\n\nimport maestro.cli.runner.CommandState\nimport maestro.cli.runner.CommandStatus\nim"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/resultview/ResultView.kt",
"chars": 97,
"preview": "package maestro.cli.runner.resultview\n\ninterface ResultView {\n fun setState(state: UiState)\n}\n"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/runner/resultview/UiState.kt",
"chars": 477,
"preview": "package maestro.cli.runner.resultview\n\nimport maestro.device.Device\nimport maestro.cli.runner.CommandState\n\nsealed class"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt",
"chars": 16354,
"preview": "/*\n *\n * Copyright (c) 2022 mobile.dev inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * "
},
{
"path": "maestro-cli/src/main/java/maestro/cli/session/SessionStore.kt",
"chars": 2101,
"preview": "package maestro.cli.session\n\nimport maestro.cli.db.KeyValueStore\nimport maestro.device.Platform\nimport java.nio.file.Pat"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/update/Updates.kt",
"chars": 3043,
"preview": "package maestro.cli.update\n\nimport maestro.cli.api.ApiClient\nimport maestro.cli.api.CliVersion\nimport maestro.cli.util.E"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/util/ChangeLogUtils.kt",
"chars": 1558,
"preview": "package maestro.cli.util\n\nimport maestro.cli.util.EnvUtils.CLI_VERSION\nimport maestro.utils.HttpClient\nimport okhttp3.Re"
},
{
"path": "maestro-cli/src/main/java/maestro/cli/util/CiUtils.kt",
"chars": 1281,
"preview": "package maestro.cli.util\n\nobject CiUtils {\n\n // When adding a new CI, also add the first version of Maestro that supp"
}
]
// ... and 966 more files (download for full content)
About this extraction
This page contains the full source code of the mobile-dev-inc/Maestro GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1166 files (13.8 MB), approximately 765.2k tokens, and a symbol index with 198 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.