From 6eb0e50607dafb0615fae267d6dbf0538fffcd73 Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:26:15 +0700 Subject: [PATCH 1/9] change install script to rasyid7 --- scripts/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index a7f21069df..cf6477a068 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -90,9 +90,9 @@ mkdir -p "$maestro_tmp_folder" if [ -z "$MAESTRO_VERSION" ]; then - download_url="https://github.com/mobile-dev-inc/maestro/releases/latest/download/maestro.zip" + download_url="https://github.com/rasyid7/maestro/releases/latest/download/maestro.zip" else - download_url="https://github.com/mobile-dev-inc/maestro/releases/download/cli-$MAESTRO_VERSION/maestro.zip" + download_url="https://github.com/rasyid7/maestro/releases/download/cli-$MAESTRO_VERSION/maestro.zip" fi maestro_zip_file="${maestro_tmp_folder}/maestro.zip" From 6ca5696f319fa051f7ec37d039e033d94f125d26 Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:26:42 +0700 Subject: [PATCH 2/9] update README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1339c0fd08..0cc068fde1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ Maestro is the easiest way to automate UI testing for your mobile app. +> [!NOTE] +> +> **Full documentation for Maestro can be found at [maestro.mobile.dev](https://maestro.mobile.dev)** +> +> Since this is forked REPO, to install this maestro, please use +> +> `curl -Ls "https://raw.githubusercontent.com/rasyid7/maestro/main/scripts/install.sh" | bash` + ## Why Maestro? From 03e65c2f554cb53e8e2b5a77c6c1ccbbddf50b4e Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:27:27 +0700 Subject: [PATCH 3/9] Adding sleep feature --- .../src/main/java/maestro/Maestro.kt | 6 +++++ .../main/java/maestro/orchestra/Commands.kt | 17 +++++++++++- .../java/maestro/orchestra/MaestroCommand.kt | 5 +++- .../main/java/maestro/orchestra/Orchestra.kt | 6 +++++ .../orchestra/yaml/YamlFluentCommand.kt | 17 ++++++++++-- .../orchestra/yaml/YamlSleepCommand.kt | 16 ++++++++++++ .../MaestroCommandSerializationTest.kt | 26 +++++++++++++++++++ 7 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSleepCommand.kt diff --git a/maestro-client/src/main/java/maestro/Maestro.kt b/maestro-client/src/main/java/maestro/Maestro.kt index 4f7fafe455..506909e864 100644 --- a/maestro-client/src/main/java/maestro/Maestro.kt +++ b/maestro-client/src/main/java/maestro/Maestro.kt @@ -582,6 +582,12 @@ class Maestro( ScreenshotUtils.waitUntilScreenIsStatic(timeout, SCREENSHOT_DIFF_THRESHOLD, driver) } + fun sleep(time: Long?) { + val time = time ?: ANIMATION_TIMEOUT_MS + LOGGER.info("Sleep for $time ms") + Thread.sleep(time) + } + fun setProxy( host: String = SocketUtils.localIp(), port: Int diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt index 24608e18e8..acfb82d5c9 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -37,7 +37,7 @@ sealed interface Command { fun visible(): Boolean = true val label: String? - + val optional: Boolean } @@ -903,6 +903,21 @@ data class WaitForAnimationToEndCommand( } } +data class SleepCommand( + val time: Long?, + override val label: String? = null, + override val optional: Boolean = false, +) : Command { + + override fun description(): String { + return label ?: "Sleep for $time ms" + } + + override fun evaluateScripts(jsEngine: JsEngine): Command { + return this + } +} + data class EvalScriptCommand( val scriptString: String, override val label: String? = null, diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt index 6a20ded467..bb5ae335ab 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt @@ -69,6 +69,7 @@ data class MaestroCommand( val setAirplaneModeCommand: SetAirplaneModeCommand? = null, val toggleAirplaneModeCommand: ToggleAirplaneModeCommand? = null, val retryCommand: RetryCommand? = null, + val sleepCommand: SleepCommand? = null, ) { constructor(command: Command) : this( @@ -111,7 +112,8 @@ data class MaestroCommand( addMediaCommand = command as? AddMediaCommand, setAirplaneModeCommand = command as? SetAirplaneModeCommand, toggleAirplaneModeCommand = command as? ToggleAirplaneModeCommand, - retryCommand = command as? RetryCommand + retryCommand = command as? RetryCommand, + sleepCommand = command as? SleepCommand, ) fun asCommand(): Command? = when { @@ -155,6 +157,7 @@ data class MaestroCommand( setAirplaneModeCommand != null -> setAirplaneModeCommand toggleAirplaneModeCommand != null -> toggleAirplaneModeCommand retryCommand != null -> retryCommand + sleepCommand != null -> sleepCommand else -> null } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index 53c2735637..4d847dbac6 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -302,6 +302,7 @@ class Orchestra( is SetAirplaneModeCommand -> setAirplaneMode(command) is ToggleAirplaneModeCommand -> toggleAirplaneMode() is RetryCommand -> retryCommand(command, config) + is SleepCommand -> sleepCommand(command) else -> true }.also { mutating -> if (mutating) { @@ -442,6 +443,11 @@ class Orchestra( return true } + private fun sleepCommand(command: SleepCommand): Boolean { + maestro.sleep(command.time) + return true + } + private fun defineVariablesCommand(command: DefineVariablesCommand): Boolean { command.env.forEach { (name, value) -> jsEngine.putEnv(name, value) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt index 514221b672..6b9bd11c35 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -80,6 +80,7 @@ data class YamlFluentCommand( val setAirplaneMode: YamlSetAirplaneMode? = null, val toggleAirplaneMode: YamlToggleAirplaneMode? = null, val retry: YamlRetryCommand? = null, + val sleep: YamlSleepCommand? = null, ) { @SuppressWarnings("ComplexMethod") @@ -255,8 +256,16 @@ data class YamlFluentCommand( val tapRepeat = TapRepeat(2, delay) listOf(tapCommand(doubleTapOn, tapRepeat = tapRepeat)) } - setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value, setAirplaneMode.label, setAirplaneMode.optional))) - toggleAirplaneMode != null -> listOf(MaestroCommand(ToggleAirplaneModeCommand(toggleAirplaneMode.label, toggleAirplaneMode.optional))) + setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value, setAirplaneMode.label))) + toggleAirplaneMode != null -> listOf(MaestroCommand(ToggleAirplaneModeCommand(toggleAirplaneMode.label))) + sleep != null -> listOf( + MaestroCommand( + SleepCommand( + time = sleep.time, + label = sleep.label + ) + ) + ) else -> throw SyntaxError("Invalid command: No mapping provided for $this") } } @@ -777,6 +786,10 @@ data class YamlFluentCommand( assertNoDefectsWithAI = YamlAssertNoDefectsWithAI() ) + "sleep" -> YamlFluentCommand( + sleep = YamlSleepCommand(time = null) + ) + else -> throw SyntaxError("Invalid command: \"$stringCommand\"") } } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSleepCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSleepCommand.kt new file mode 100644 index 0000000000..1f938ae691 --- /dev/null +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSleepCommand.kt @@ -0,0 +1,16 @@ +package maestro.orchestra.yaml + +import com.fasterxml.jackson.annotation.JsonCreator + +data class YamlSleepCommand( + val time: Long?, + val label: String? = null, +) { + companion object { + @JvmStatic + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + fun create(time: Long): YamlSleepCommand { + return YamlSleepCommand(time = time) + } + } +} diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index 3339c9a316..c8d432703a 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -590,6 +590,32 @@ internal class MaestroCommandSerializationTest { .isEqualTo(command) } + @Test + fun `serialize SleepCommand`() { + // given + val command = MaestroCommand( + SleepCommand(time = 1000) + ) + + // when + val serializedCommandJson = command.toJson() + val deserializedCommand = objectMapper.readValue(serializedCommandJson, MaestroCommand::class.java) + + // then + @Language("json") + val expectedJson = """ + { + "sleepCommand" : { + "time" : 1000 + } + } + """.trimIndent() + assertThat(serializedCommandJson) + .isEqualTo(expectedJson) + assertThat(deserializedCommand) + .isEqualTo(command) + } + private fun MaestroCommand.toJson(): String = objectMapper .writerWithDefaultPrettyPrinter() From c8a1a36b1134f5179c7b0246393a145290b07b7f Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:27:45 +0700 Subject: [PATCH 4/9] change version checker to own repo --- maestro-cli/src/main/java/maestro/cli/App.kt | 14 ++++---------- .../src/main/java/maestro/cli/api/ApiClient.kt | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/maestro-cli/src/main/java/maestro/cli/App.kt b/maestro-cli/src/main/java/maestro/cli/App.kt index c50a238f9a..7db68d9d57 100644 --- a/maestro-cli/src/main/java/maestro/cli/App.kt +++ b/maestro-cli/src/main/java/maestro/cli/App.kt @@ -148,16 +148,10 @@ fun main(args: Array) { if (newVersion != null) { Updates.fetchChangelogAsync() System.err.println() - val changelog = Updates.getChangelog() - val anchor = newVersion.toString().replace(".", "") - System.err.println(listOf( - "A new version of the Maestro CLI is available ($newVersion).\n", - "See what's new:", - "https://github.com/mobile-dev-inc/maestro/blob/main/CHANGELOG.md#$anchor", - ChangeLogUtils.print(changelog), - "Upgrade command:", - "curl -Ls \"https://get.maestro.mobile.dev\" | bash", - ).joinToString("\n").box()) + System.err.println( + ("A new version of the Maestro CLI is available ($newVersion). Upgrade command:\n" + + "curl -Ls \"https://raw.githubusercontent.com/rasyid7/maestro/main/scripts/install.sh\" | bash").box() + ) } if (commandLine.isVersionHelpRequested) { diff --git a/maestro-cli/src/main/java/maestro/cli/api/ApiClient.kt b/maestro-cli/src/main/java/maestro/cli/api/ApiClient.kt index 6cdf0fa7b8..a0e465fb91 100644 --- a/maestro-cli/src/main/java/maestro/cli/api/ApiClient.kt +++ b/maestro-cli/src/main/java/maestro/cli/api/ApiClient.kt @@ -89,7 +89,7 @@ class ApiClient( fun getLatestCliVersion(): CliVersion { val request = Request.Builder() .header("X-FRESH-INSTALL", if (!Analytics.hasRunBefore) "true" else "false") - .url("$baseUrl/maestro/version") + .url("https://raw.githubusercontent.com/rasyid7/maestro/main/version") .get() .build() From e7e557e7c2b6789c544cc171bbeccbadad0f3bfe Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:27:59 +0700 Subject: [PATCH 5/9] fix tags include from OR to AND --- .../orchestra/workspace/WorkspaceExecutionPlanner.kt | 9 ++++++++- .../orchestra/workspace/WorkspaceExecutionPlannerTest.kt | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/workspace/WorkspaceExecutionPlanner.kt b/maestro-orchestra/src/main/java/maestro/orchestra/workspace/WorkspaceExecutionPlanner.kt index 48b14574f0..cb1a5d5f85 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/workspace/WorkspaceExecutionPlanner.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/workspace/WorkspaceExecutionPlanner.kt @@ -101,10 +101,17 @@ object WorkspaceExecutionPlanner { val config = configPerFlowFile[it] val tags = config?.tags ?: emptyList() - (allIncludeTags.isEmpty() || tags.any(allIncludeTags::contains)) + (allIncludeTags.isEmpty() || allIncludeTags.all { includeTag -> tags.contains(includeTag) }) && (allExcludeTags.isEmpty() || !tags.any(allExcludeTags::contains)) } + println("") + println("Number flows to run: ${allFlows.size}") + println("Flows to run:") + for (flow in allFlows) { + println(" * ${flow.fileName.toString().substringBeforeLast(".")}") + } + if (allFlows.isEmpty()) { val message = """ |Include / Exclude tags did not match any Flows: diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt index f66d1cf3e6..2589dab184 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt @@ -258,7 +258,6 @@ internal class WorkspaceExecutionPlannerTest { assertThat(plan.flowsToRun).containsExactly( path("/workspaces/010_global_include_tags/flowA.yaml"), path("/workspaces/010_global_include_tags/flowA_subflow.yaml"), - path("/workspaces/010_global_include_tags/flowB.yaml"), ) } From 75b23f3c257534979b72f19ea480e3d44741d14d Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 14:28:09 +0700 Subject: [PATCH 6/9] ignore .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c22074e917..f1a52102ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.idea/ # Ignore Gradle project-specific cache directory .gradle From b02c1cc1c5c6453dc7bff75d6cc9a6d9f65c7449 Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Thu, 26 Sep 2024 15:59:59 +0700 Subject: [PATCH 7/9] Fix sleep and tag test --- .../java/maestro/orchestra/MaestroCommandSerializationTest.kt | 3 ++- .../orchestra/workspace/WorkspaceExecutionPlannerTest.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index c8d432703a..2a3370a685 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -606,7 +606,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "sleepCommand" : { - "time" : 1000 + "time" : 1000, + "optional" : false } } """.trimIndent() diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt index 2589dab184..d0eed12dff 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/workspace/WorkspaceExecutionPlannerTest.kt @@ -249,7 +249,7 @@ internal class WorkspaceExecutionPlannerTest { // When val plan = WorkspaceExecutionPlanner.plan( input = paths("/workspaces/010_global_include_tags"), - includeTags = listOf("featureB"), + includeTags = listOf("featureA"), excludeTags = listOf(), config = null, ) From d72c9e1babe155ce64da724e64ea695239d40519 Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Fri, 18 Oct 2024 16:07:58 +0700 Subject: [PATCH 8/9] android waitToSettleTimeoutMs not wait for settle for faster commands --- maestro-client/src/main/java/maestro/Maestro.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/maestro-client/src/main/java/maestro/Maestro.kt b/maestro-client/src/main/java/maestro/Maestro.kt index 506909e864..1d3e7e22cb 100644 --- a/maestro-client/src/main/java/maestro/Maestro.kt +++ b/maestro-client/src/main/java/maestro/Maestro.kt @@ -185,7 +185,7 @@ class Maestro( ) { LOGGER.info("Tapping on element: ${tapRepeat ?: ""} $element") - val hierarchyBeforeTap = waitForAppToSettle(initialHierarchy, appId, waitToSettleTimeoutMs) ?: initialHierarchy + val hierarchyBeforeTap = initialHierarchy val center = ( hierarchyBeforeTap @@ -354,6 +354,12 @@ class Maestro( } else { driver.tap(Point(x, y)) } + + if (waitToSettleTimeoutMs != null && waitToSettleTimeoutMs < 150) { + LOGGER.info("waitToSettleTimeoutMs is less than 150, skip get hierarchy") + return + } + val hierarchyAfterTap = waitForAppToSettle(waitToSettleTimeoutMs = waitToSettleTimeoutMs) if (hierarchyBeforeTap != hierarchyAfterTap) { From 7a6c470369f7e7f69aa5f3d2ad1cb39694cd4fa6 Mon Sep 17 00:00:00 2001 From: Rasyid Ridho Date: Mon, 9 Dec 2024 17:24:32 +0700 Subject: [PATCH 9/9] fix airPlaneMode as existing --- .../src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt index 6b9bd11c35..e4c6559e0a 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -256,8 +256,8 @@ data class YamlFluentCommand( val tapRepeat = TapRepeat(2, delay) listOf(tapCommand(doubleTapOn, tapRepeat = tapRepeat)) } - setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value, setAirplaneMode.label))) - toggleAirplaneMode != null -> listOf(MaestroCommand(ToggleAirplaneModeCommand(toggleAirplaneMode.label))) + setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value, setAirplaneMode.label, setAirplaneMode.optional))) + toggleAirplaneMode != null -> listOf(MaestroCommand(ToggleAirplaneModeCommand(toggleAirplaneMode.label, toggleAirplaneMode.optional))) sleep != null -> listOf( MaestroCommand( SleepCommand(