Full Code of mobile-dev-inc/Maestro for AI

main f24a6f8fdcca cached
1166 files
13.8 MB
765.2k tokens
198 symbols
2 requests
Download .txt
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:

![Project dependency graph](assets/project-dependency-graph.svg)

## 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
Download .txt
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
Download .txt
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.

Copied to clipboard!