From 3a626bc04bfa9e5dacdb440786af49a1870cd05d Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Mon, 26 Aug 2024 22:47:16 +0100 Subject: [PATCH 1/7] Add MaestroAppId model --- .../main/java/maestro/orchestra/Commands.kt | 8 +-- .../java/maestro/orchestra/MaestroConfig.kt | 25 ++++++++- .../main/java/maestro/orchestra/Orchestra.kt | 26 ++++++---- .../MaestroCommandSerializationTest.kt | 16 ++++-- .../orchestra/yaml/YamlCommandReaderTest.kt | 52 +++++++++---------- .../main/java/maestro/studio/DeviceService.kt | 7 +-- 6 files changed, 87 insertions(+), 47 deletions(-) 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 5a1efec332..d8b6ae8b7e 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -412,7 +412,7 @@ data class InputTextCommand( } data class LaunchAppCommand( - val appId: String, + val appId: MaestroAppId, val clearState: Boolean? = null, val clearKeychain: Boolean? = null, val stopApp: Boolean? = null, @@ -553,7 +553,7 @@ data class TakeScreenshotCommand( } data class StopAppCommand( - val appId: String, + val appId: MaestroAppId, val label: String? = null ) : Command { @@ -569,7 +569,7 @@ data class StopAppCommand( } data class KillAppCommand( - val appId: String, + val appId: MaestroAppId, val label: String? = null ) : Command { @@ -585,7 +585,7 @@ data class KillAppCommand( } data class ClearStateCommand( - val appId: String, + val appId: MaestroAppId, val label: String? = null, ) : Command { diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt index 8a9325011a..0384a05250 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt @@ -1,12 +1,13 @@ package maestro.orchestra +import maestro.Platform import maestro.js.JsEngine import maestro.orchestra.util.Env.evaluateScripts // Note: The appId config is only a yaml concept for now. It'll be a larger migration to get to a point // where appId is part of MaestroConfig (and factored out of MaestroCommands - eg: LaunchAppCommand). data class MaestroConfig( - val appId: String? = null, + val appId: MaestroAppId? = null, val name: String? = null, val tags: List? = emptyList(), val ext: Map = emptyMap(), @@ -25,6 +26,28 @@ data class MaestroConfig( } +data class MaestroAppId( + val android: String?, + val ios: String?, + val web: String?, +) { + constructor(appId: String) : this(appId, appId, appId) + + fun forPlatform(platform: Platform) = when (platform) { + Platform.ANDROID -> android ?: error("No appId specified for android.") + Platform.IOS -> ios ?: error("No appId specified for ios.") + Platform.WEB -> web ?: error("No appId specified for web.") + } + + fun evaluateScripts(jsEngine: JsEngine): MaestroAppId { + return copy( + android = android?.evaluateScripts(jsEngine), + ios = ios?.evaluateScripts(jsEngine), + web = web?.evaluateScripts(jsEngine), + ) + } +} + data class MaestroOnFlowComplete(val commands: List) { fun evaluateScripts(jsEngine: JsEngine): MaestroOnFlowComplete { return this diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index dbd96fcd66..beb508df6b 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -451,22 +451,25 @@ class Orchestra( } private fun clearAppStateCommand(command: ClearStateCommand): Boolean { - maestro.clearAppState(command.appId) + val appId = command.appId.forPlatform(deviceInfo().platform) + maestro.clearAppState(appId) // Android's clear command also resets permissions // Reset all permissions to unset so both platforms behave the same - maestro.setPermissions(command.appId, mapOf("all" to "unset")) + maestro.setPermissions(appId, mapOf("all" to "unset")) return true } private fun stopAppCommand(command: StopAppCommand): Boolean { - maestro.stopApp(command.appId) + val appId = command.appId.forPlatform(deviceInfo().platform) + maestro.stopApp(appId) return true } private fun killAppCommand(command: KillAppCommand): Boolean { - maestro.killApp(command.appId) + val appId = command.appId.forPlatform(deviceInfo().platform) + maestro.killApp(appId) return true } @@ -766,7 +769,8 @@ class Orchestra( } private fun openLinkCommand(command: OpenLinkCommand, config: MaestroConfig?): Boolean { - maestro.openLink(command.link, config?.appId, command.autoVerify ?: false, command.browser ?: false) + val appId = config?.appId?.forPlatform(deviceInfo().platform) + maestro.openLink(command.link, appId, command.autoVerify ?: false, command.browser ?: false) return true } @@ -777,20 +781,23 @@ class Orchestra( maestro.clearKeychain() } if (command.clearState == true) { - maestro.clearAppState(command.appId) + val appId = command.appId.forPlatform(deviceInfo().platform) + maestro.clearAppState(appId) } // For testing convenience, default to allow all on app launch val permissions = command.permissions ?: mapOf("all" to "allow") - maestro.setPermissions(command.appId, permissions) + val appId = command.appId.forPlatform(deviceInfo().platform) + maestro.setPermissions(appId, permissions) } catch (e: Exception) { throw MaestroException.UnableToClearState("Unable to clear state for app ${command.appId}") } try { + val appId = command.appId.forPlatform(deviceInfo().platform) maestro.launchApp( - appId = command.appId, + appId = appId, launchArguments = command.launchArguments ?: emptyMap(), stopIfRunning = command.stopApp ?: true ) @@ -842,13 +849,14 @@ class Orchestra( ): Boolean { return try { val result = findElement(command.selector) + val appId = config?.appId?.forPlatform(deviceInfo().platform) maestro.tap( result.element, result.hierarchy, retryIfNoChange, waitUntilVisible, command.longPress ?: false, - config?.appId, + appId, tapRepeat = command.repeat, waitToSettleTimeoutMs = command.waitToSettleTimeoutMs ) diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index 1a1b052fc8..09a92910cf 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -273,7 +273,7 @@ internal class MaestroCommandSerializationTest { fun `serialize LaunchAppCommand`() { // given val command = MaestroCommand( - LaunchAppCommand("com.twitter.android") + LaunchAppCommand(MaestroAppId("com.twitter.android")) ) // when @@ -285,7 +285,11 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "launchAppCommand" : { - "appId" : "com.twitter.android" + "appId" : { + "android" : "com.twitter.android", + "ios" : "com.twitter.android", + "web" : "com.twitter.android" + } } } """.trimIndent() @@ -301,7 +305,7 @@ internal class MaestroCommandSerializationTest { val command = MaestroCommand( ApplyConfigurationCommand( MaestroConfig( - appId = "com.twitter.android", + appId = MaestroAppId("com.twitter.android"), name = "Twitter", ) ) @@ -317,7 +321,11 @@ internal class MaestroCommandSerializationTest { { "applyConfigurationCommand" : { "config" : { - "appId" : "com.twitter.android", + "appId" : { + "android" : "com.twitter.android", + "ios" : "com.twitter.android", + "web" : "com.twitter.android" + }, "name" : "Twitter", "tags" : [ ], "ext" : { } diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt index dedaa7b6ec..040c55b321 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt @@ -56,8 +56,8 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.file.FileSystems import java.nio.file.Paths +import maestro.orchestra.MaestroAppId -@Suppress("JUnitMalformedDeclaration") @ExtendWith(YamlCommandsExtension::class, YamlExceptionExtension::class) internal class YamlCommandReaderTest { @@ -74,10 +74,10 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") )), LaunchAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ), ) } @@ -88,10 +88,10 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), )), LaunchAppCommand( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), clearState = true, ), ) @@ -124,7 +124,7 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), ext = mapOf( "extra" to true, "extraMap" to mapOf( @@ -134,7 +134,7 @@ internal class YamlCommandReaderTest { ) )), LaunchAppCommand( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), ), ) } @@ -173,10 +173,10 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), )), LaunchAppCommand( - appId = "com.other.app" + appId = MaestroAppId("com.other.app") ), ) } @@ -187,7 +187,7 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), )), BackPressCommand(), ) @@ -199,7 +199,7 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), )), ScrollCommand(), ) @@ -211,11 +211,11 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), name = "Example Flow" )), LaunchAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ), ) } @@ -234,11 +234,11 @@ internal class YamlCommandReaderTest { assertThat(commands).isEqualTo(commands( ApplyConfigurationCommand( config = MaestroConfig( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ) ), LaunchAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ) )) } @@ -257,7 +257,7 @@ internal class YamlCommandReaderTest { assertThat(commands).containsExactly( ApplyConfigurationCommand( config = MaestroConfig( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), onFlowStart = MaestroOnFlowStart( commands = commands( BackPressCommand() @@ -271,7 +271,7 @@ internal class YamlCommandReaderTest { ) ), LaunchAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ) ) } @@ -282,8 +282,8 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand( - config=MaestroConfig( - appId="com.example.app" + config = MaestroConfig( + appId = MaestroAppId("com.example.app") ) ), @@ -374,7 +374,7 @@ internal class YamlCommandReaderTest { label = "Clear the keychain" ), ClearStateCommand( - appId = "com.example.app", + appId = MaestroAppId("com.example.app"), label = "Wipe the app state" ), CopyTextFromCommand( @@ -398,7 +398,7 @@ internal class YamlCommandReaderTest { label = "Hide the keyboard" ), LaunchAppCommand( - appId = "com.some.other", + appId = MaestroAppId("com.some.other"), clearState = true, label = "Launch some other app" ), @@ -448,7 +448,7 @@ internal class YamlCommandReaderTest { label = "Start recording a video" ), StopAppCommand( - appId = "com.some.other", + appId = MaestroAppId("com.some.other"), label = "Stop that other app from running" ), StopRecordingCommand( @@ -515,10 +515,10 @@ internal class YamlCommandReaderTest { } @Test - fun commands_with_string_non_string(@YamlFile("024_string_non_string_commands.yaml") commands: List,) { + fun commands_with_string_non_string(@YamlFile("024_string_non_string_commands.yaml") commands: List) { assertThat(commands).containsExactly( ApplyConfigurationCommand( - config= MaestroConfig(appId= "com.example.app") + config = MaestroConfig(appId = MaestroAppId("com.example.app")) ), InputTextCommand(text = "correct horse battery staple"), InputTextCommand(text = "correct horse battery staple"), @@ -594,10 +594,10 @@ internal class YamlCommandReaderTest { ) { assertThat(commands).containsExactly( ApplyConfigurationCommand(MaestroConfig( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") )), KillAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ), ) } diff --git a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt index ddc62748a6..ff85a915c7 100644 --- a/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt +++ b/maestro-studio/server/src/main/java/maestro/studio/DeviceService.kt @@ -22,6 +22,7 @@ import java.util.regex.Pattern import kotlin.io.path.Path import kotlin.io.path.createDirectories import kotlin.io.path.createTempDirectory +import maestro.orchestra.MaestroAppId import maestro.orchestra.MaestroCommand import maestro.orchestra.yaml.YamlCommandReader import maestro.orchestra.yaml.YamlFluentCommand @@ -55,7 +56,7 @@ object DeviceService { val request = call.parseBody() try { val commands = YamlCommandReader.MAPPER.readValue(request.yaml, YamlFluentCommand::class.java) - .toCommands(Paths.get(""), "") + .toCommands(Paths.get(""), MaestroAppId("")) if (request.dryRun != true) { executeCommands(maestro, commands) } @@ -67,7 +68,7 @@ object DeviceService { } routing.post("/api/format-flow") { val request = call.parseBody() - val commands = request.commands.map { YamlCommandReader.MAPPER.readValue(it, YamlFluentCommand::class.java).toCommands(Paths.get(""), "") } + val commands = request.commands.map { YamlCommandReader.MAPPER.readValue(it, YamlFluentCommand::class.java).toCommands(Paths.get(""), MaestroAppId("")) } val inferredAppId = commands.flatten().firstNotNullOfOrNull { it.launchAppCommand?.appId } val commandsString = YamlCommandReader.MAPPER.writeValueAsString(request.commands.map { YamlCommandReader.MAPPER.readTree(it) }) val formattedFlow = FormattedFlow("appId: $inferredAppId", commandsString) @@ -231,4 +232,4 @@ object DeviceService { screenshotDir.createDirectories() return screenshotDir } -} \ No newline at end of file +} From 24b204cbd97095d1b57880c4f809e9de43ad0456 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Mon, 26 Aug 2024 22:48:04 +0100 Subject: [PATCH 2/7] Add deserializer for backwards compatibility --- .../java/maestro/orchestra/MaestroConfig.kt | 21 +++++++++++++++++++ .../MaestroCommandSerializationTest.kt | 21 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt index 0384a05250..8790f43d1d 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt @@ -1,5 +1,9 @@ package maestro.orchestra +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode import maestro.Platform import maestro.js.JsEngine import maestro.orchestra.util.Env.evaluateScripts @@ -46,6 +50,23 @@ data class MaestroAppId( web = web?.evaluateScripts(jsEngine), ) } + + class Deserializer : JsonDeserializer() { + + override fun deserialize(parser: JsonParser, context: DeserializationContext): MaestroAppId { + val node: JsonNode = parser.codec.readTree(parser) + return when { + node.isTextual -> MaestroAppId(node.asText()) + node.isObject -> { + val android = node.get("android")?.asText() + val ios = node.get("ios")?.asText() + val web = node.get("web")?.asText() + MaestroAppId(android, ios, web) + } + else -> throw IllegalArgumentException("Unexpected JSON format for MaestroAppId: $node") + } + } + } } data class MaestroOnFlowComplete(val commands: List) { diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index 09a92910cf..ff318e21a2 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -310,10 +310,24 @@ internal class MaestroCommandSerializationTest { ) ) ) + @Language("json") + val legacyJson = """ + { + "applyConfigurationCommand" : { + "config" : { + "appId" : "com.twitter.android", + "name" : "Twitter", + "tags" : [ ], + "ext" : { } + } + } + } + """.trimIndent() // when val serializedCommandJson = command.toJson() val deserializedCommand = objectMapper.readValue(serializedCommandJson, MaestroCommand::class.java) + val deserializedLegacy = objectMapper.readValue(legacyJson, MaestroCommand::class.java) // then @Language("json") @@ -337,6 +351,8 @@ internal class MaestroCommandSerializationTest { .isEqualTo(expectedJson) assertThat(deserializedCommand) .isEqualTo(command) + assertThat(deserializedLegacy) + .isEqualTo(command) } @Test @@ -582,7 +598,10 @@ internal class MaestroCommandSerializationTest { .writerWithDefaultPrettyPrinter() .writeValueAsString(this) + private val module = KotlinModule.Builder().build() + .addDeserializer(MaestroAppId::class.java, MaestroAppId.Deserializer()) + private val objectMapper = ObjectMapper() .setSerializationInclusion(Include.NON_NULL) - .registerModule(KotlinModule.Builder().build()) + .registerModule(module) } From 3297c8ee3d736fdb7a3facb87db7409f844517cf Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Mon, 26 Aug 2024 22:48:48 +0100 Subject: [PATCH 3/7] Add Yaml model and parse it --- .../java/maestro/cli/command/TestCommand.kt | 2 +- .../java/maestro/orchestra/yaml/YamlAppId.kt | 39 +++++++++++++++++++ .../maestro/orchestra/yaml/YamlClearState.kt | 4 +- .../orchestra/yaml/YamlCommandReader.kt | 2 +- .../java/maestro/orchestra/yaml/YamlConfig.kt | 8 ++-- .../orchestra/yaml/YamlFluentCommand.kt | 18 ++++----- .../maestro/orchestra/yaml/YamlKillApp.kt | 10 ++--- .../maestro/orchestra/yaml/YamlLaunchApp.kt | 5 ++- .../maestro/orchestra/yaml/YamlStopApp.kt | 10 ++--- 9 files changed, 65 insertions(+), 33 deletions(-) create mode 100644 maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAppId.kt diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index c53a74b3ea..e205a5df03 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -144,7 +144,7 @@ class TestCommand : Callable { private fun isWebFlow(): Boolean { if (!flowFile.isDirectory) { val config = YamlCommandReader.readConfig(flowFile.toPath()) - return Regex("http(s?)://").containsMatchIn(config.appId) + return Regex("http(s?)://").containsMatchIn(config.appId.web ?: "") } return false diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAppId.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAppId.kt new file mode 100644 index 0000000000..f611aa984c --- /dev/null +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAppId.kt @@ -0,0 +1,39 @@ +package maestro.orchestra.yaml + +import com.fasterxml.jackson.annotation.JsonCreator +import java.lang.UnsupportedOperationException +import maestro.orchestra.MaestroAppId + +data class YamlAppId( + val android: String?, + val ios: String?, + val web: String?, +){ + fun asAppId() = MaestroAppId(android, ios, web) + + companion object { + + @JvmStatic + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + fun parse(appId: Any): YamlAppId { + val appIdString = when (appId) { + is String -> appId + is Map<*, *> -> { + val android = appId.getOrDefault("android", null) as String? + val ios = appId.getOrDefault("ios", null) as String? + val web = appId.getOrDefault("web", null) as String? + if ( + android == null && + ios == null && + web == null + ) { + throw UnsupportedOperationException("Cannot deserialize appId with no specified platform.") + } + return YamlAppId(android, ios, web) + } + else -> throw UnsupportedOperationException("Cannot deserialize appId with data type ${appId.javaClass}") + } + return YamlAppId(appIdString, appIdString, appIdString) + } + } +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt index 4d2a51acb1..5ab0fcba22 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt @@ -3,14 +3,14 @@ package maestro.orchestra.yaml import com.fasterxml.jackson.annotation.JsonCreator data class YamlClearState( - val appId: String? = null, + val appId: YamlAppId? = null, val label: String? = null, ) { companion object { @JvmStatic @JsonCreator(mode = JsonCreator.Mode.DELEGATING) fun parse(appId: String) = YamlClearState( - appId = appId, + appId = YamlAppId.parse(appId), ) } } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt index 5005c49b0a..39d5e2a2b4 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt @@ -50,7 +50,7 @@ object YamlCommandReader { fun readCommands(flowPath: Path): List = mapParsingErrors(flowPath) { val (config, commands) = readConfigAndCommands(flowPath) val maestroCommands = commands - .flatMap { it.toCommands(flowPath, config.appId) } + .flatMap { it.toCommands(flowPath, config.appId.asAppId()) } .withEnv(config.env) listOfNotNull(config.toCommand(flowPath), *maestroCommands.toTypedArray()) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlConfig.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlConfig.kt index 0606dda042..e0a8d5e819 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlConfig.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlConfig.kt @@ -10,7 +10,7 @@ import java.nio.file.Path data class YamlConfig( val name: String?, - val appId: String, + val appId: YamlAppId, val tags: List? = emptyList(), val env: Map = emptyMap(), val onFlowStart: YamlOnFlowStart?, @@ -26,7 +26,7 @@ data class YamlConfig( fun toCommand(flowPath: Path): MaestroCommand { val config = MaestroConfig( - appId = appId, + appId = appId.asAppId(), name = name, tags = tags, ext = ext.toMap(), @@ -39,12 +39,12 @@ data class YamlConfig( private fun onFlowComplete(flowPath: Path): MaestroOnFlowComplete? { if (onFlowComplete == null) return null - return MaestroOnFlowComplete(onFlowComplete.commands.flatMap { it.toCommands(flowPath, appId) }) + return MaestroOnFlowComplete(onFlowComplete.commands.flatMap { it.toCommands(flowPath, appId.asAppId()) }) } private fun onFlowStart(flowPath: Path): MaestroOnFlowStart? { if (onFlowStart == null) return null - return MaestroOnFlowStart(onFlowStart.commands.flatMap { it.toCommands(flowPath, appId) }) + return MaestroOnFlowStart(onFlowStart.commands.flatMap { it.toCommands(flowPath, appId.asAppId()) }) } } 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 5873602fea..ad549889cf 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -82,7 +82,7 @@ data class YamlFluentCommand( ) { @SuppressWarnings("ComplexMethod") - fun toCommands(flowPath: Path, appId: String): List { + fun toCommands(flowPath: Path, appId: MaestroAppId): List { return when { launchApp != null -> listOf(launchApp(launchApp, appId)) tapOn != null -> listOf(tapCommand(tapOn)) @@ -168,7 +168,7 @@ data class YamlFluentCommand( stopApp != null -> listOf( MaestroCommand( StopAppCommand( - appId = stopApp.appId ?: appId, + appId = stopApp.appId?.asAppId() ?: appId, label = stopApp.label ) ) @@ -176,15 +176,15 @@ data class YamlFluentCommand( killApp != null -> listOf( MaestroCommand( KillAppCommand( - appId = killApp.appId ?: appId, + appId = killApp.appId?.asAppId() ?: appId, label = killApp.label ) ) ) clearState != null -> listOf( MaestroCommand( - maestro.orchestra.ClearStateCommand( - appId = clearState.appId ?: appId, + ClearStateCommand( + appId = clearState.appId?.asAppId() ?: appId, label = clearState.label ) ) @@ -270,7 +270,7 @@ data class YamlFluentCommand( } private fun runFlowCommand( - appId: String, + appId: MaestroAppId, flowPath: Path, runFlow: YamlRunFlow ): MaestroCommand { @@ -329,7 +329,7 @@ data class YamlFluentCommand( ) } - private fun repeatCommand(repeat: YamlRepeatCommand, flowPath: Path, appId: String) = MaestroCommand( + private fun repeatCommand(repeat: YamlRepeatCommand, flowPath: Path, appId: MaestroAppId) = MaestroCommand( RepeatCommand( times = repeat.times, condition = repeat.`while`?.toCondition(), @@ -417,10 +417,10 @@ data class YamlFluentCommand( ) } - private fun launchApp(command: YamlLaunchApp, appId: String): MaestroCommand { + private fun launchApp(command: YamlLaunchApp, appId: MaestroAppId): MaestroCommand { return MaestroCommand( LaunchAppCommand( - appId = command.appId ?: appId, + appId = command.appId?.asAppId() ?: appId, clearState = command.clearState, clearKeychain = command.clearKeychain, stopApp = command.stopApp, diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt index 5f61522be4..e2b4500b22 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt @@ -3,18 +3,14 @@ package maestro.orchestra.yaml import com.fasterxml.jackson.annotation.JsonCreator data class YamlKillApp( - val appId: String? = null, + val appId: YamlAppId? = null, val label: String? = null, ) { - companion object { - @JvmStatic @JsonCreator(mode = JsonCreator.Mode.DELEGATING) fun parse(appId: String) = YamlKillApp( - appId = appId, + appId = YamlAppId.parse(appId), ) - } - -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt index 1b74cacdb7..577c5a9a98 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt @@ -22,7 +22,7 @@ package maestro.orchestra.yaml import com.fasterxml.jackson.annotation.JsonCreator data class YamlLaunchApp( - val appId: String?, + val appId: YamlAppId?, val clearState: Boolean?, val clearKeychain: Boolean?, val stopApp: Boolean?, @@ -37,12 +37,13 @@ data class YamlLaunchApp( @JsonCreator(mode = JsonCreator.Mode.DELEGATING) fun parse(appId: String): YamlLaunchApp { return YamlLaunchApp( - appId = appId, + appId = YamlAppId.parse(appId), clearState = null, clearKeychain = null, stopApp = null, permissions = null, arguments = null, + label = null ) } } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt index 63f7a9b031..b413cc090c 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt @@ -3,18 +3,14 @@ package maestro.orchestra.yaml import com.fasterxml.jackson.annotation.JsonCreator data class YamlStopApp( - val appId: String? = null, + val appId: YamlAppId? = null, val label: String? = null, ) { - companion object { - @JvmStatic @JsonCreator(mode = JsonCreator.Mode.DELEGATING) fun parse(appId: String) = YamlStopApp( - appId = appId, + appId = YamlAppId.parse(appId), ) - } - -} \ No newline at end of file +} From ffb271a536ff0c37918ecf5131fba207524cc713 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Mon, 26 Aug 2024 22:49:40 +0100 Subject: [PATCH 4/7] Update integration tests --- .../kotlin/maestro/test/IntegrationTest.kt | 23 +++++++++++++++---- .../src/test/resources/013_launch_app.yaml | 8 ++++++- .../src/test/resources/027_open_link.yaml | 4 +++- .../src/test/resources/043_stop_app.yaml | 6 +++++ .../src/test/resources/044_clear_state.yaml | 4 ++++ .../src/test/resources/115_kill_app.yaml | 5 ++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt index 58b115c305..0b9dad9265 100644 --- a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt +++ b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt @@ -27,8 +27,9 @@ import org.junit.jupiter.api.assertThrows import java.awt.Color import java.io.File import java.nio.file.Paths -import maestro.orchestra.error.SyntaxError import kotlin.system.measureTimeMillis +import maestro.Platform +import maestro.orchestra.MaestroAppId class IntegrationTest { @@ -351,6 +352,8 @@ class IntegrationTest { val driver = driver { } + driver.addInstalledApp("ios.app") + driver.addInstalledApp("another.app") driver.addInstalledApp("com.example.app") // When @@ -362,8 +365,12 @@ class IntegrationTest { // No test failure driver.assertEvents( listOf( + Event.StopApp("ios.app"), + Event.LaunchApp("ios.app"), + Event.StopApp("another.app"), + Event.LaunchApp("another.app"), Event.StopApp("com.example.app"), - Event.LaunchApp("com.example.app") + Event.LaunchApp("com.example.app"), ) ) } @@ -554,13 +561,13 @@ class IntegrationTest { MaestroCommand( ApplyConfigurationCommand( config = MaestroConfig( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ) ) ), MaestroCommand( LaunchAppCommand( - appId = "com.example.app" + appId = MaestroAppId("com.example.app") ) ) ) @@ -1169,6 +1176,7 @@ class IntegrationTest { // No test failure driver.assertHasEvent(Event.StopApp("com.example.app")) driver.assertHasEvent(Event.StopApp("another.app")) + driver.assertHasEvent(Event.StopApp("ios.app")) } @Test @@ -1181,6 +1189,7 @@ class IntegrationTest { driver.addInstalledApp("com.example.app") driver.addInstalledApp("another.app") + driver.addInstalledApp("ios.app") // When Maestro(driver).use { @@ -1191,6 +1200,7 @@ class IntegrationTest { // No test failure driver.assertHasEvent(Event.ClearState("com.example.app")) driver.assertHasEvent(Event.ClearState("another.app")) + driver.assertHasEvent(Event.ClearState("ios.app")) } @Test @@ -1233,6 +1243,8 @@ class IntegrationTest { driver.addInstalledApp("com.example.app") driver.addInstalledApp("com.other.app") + driver.addInstalledApp("another.app") + driver.addInstalledApp("ios.app") // When Maestro(driver).use { @@ -1267,6 +1279,8 @@ class IntegrationTest { driver.addInstalledApp("com.example.app") driver.addInstalledApp("com.other.app") + driver.addInstalledApp("another.app") + driver.addInstalledApp("ios.app") // When Maestro(driver).use { @@ -3087,6 +3101,7 @@ class IntegrationTest { // No test failure driver.assertHasEvent(Event.KillApp("com.example.app")) driver.assertHasEvent(Event.KillApp("another.app")) + driver.assertHasEvent(Event.KillApp("ios.app")) } private fun orchestra( diff --git a/maestro-test/src/test/resources/013_launch_app.yaml b/maestro-test/src/test/resources/013_launch_app.yaml index e98c0c42bf..1458cea327 100644 --- a/maestro-test/src/test/resources/013_launch_app.yaml +++ b/maestro-test/src/test/resources/013_launch_app.yaml @@ -1,3 +1,9 @@ appId: com.example.app --- -- launchApp \ No newline at end of file +- launchApp: + appId: + android: android.app + ios: ios.app + web: https://localhost.com +- launchApp: another.app +- launchApp diff --git a/maestro-test/src/test/resources/027_open_link.yaml b/maestro-test/src/test/resources/027_open_link.yaml index b5c8d1b6b8..109c13e7d1 100644 --- a/maestro-test/src/test/resources/027_open_link.yaml +++ b/maestro-test/src/test/resources/027_open_link.yaml @@ -1,3 +1,5 @@ appId: com.example.app --- -- openLink: https://example.com \ No newline at end of file +- openLink: https://example.com +- openLink: + link: https://example.com diff --git a/maestro-test/src/test/resources/043_stop_app.yaml b/maestro-test/src/test/resources/043_stop_app.yaml index e40576acfc..f754b863ee 100644 --- a/maestro-test/src/test/resources/043_stop_app.yaml +++ b/maestro-test/src/test/resources/043_stop_app.yaml @@ -2,3 +2,9 @@ appId: com.example.app --- - stopApp - stopApp: another.app +- stopApp: + appId: another.app +- stopApp: + appId: + android: android.app + ios: ios.app diff --git a/maestro-test/src/test/resources/044_clear_state.yaml b/maestro-test/src/test/resources/044_clear_state.yaml index 537bbb879e..84032470d7 100644 --- a/maestro-test/src/test/resources/044_clear_state.yaml +++ b/maestro-test/src/test/resources/044_clear_state.yaml @@ -2,3 +2,7 @@ appId: com.example.app --- - clearState - clearState: another.app +- clearState: + appId: + android: android.app + ios: ios.app diff --git a/maestro-test/src/test/resources/115_kill_app.yaml b/maestro-test/src/test/resources/115_kill_app.yaml index 52d9f89c9a..a93107bcba 100644 --- a/maestro-test/src/test/resources/115_kill_app.yaml +++ b/maestro-test/src/test/resources/115_kill_app.yaml @@ -2,3 +2,8 @@ appId: com.example.app --- - killApp - killApp: another.app +- killApp: + appId: + android: android.app + ios: ios.app + web: https://localhost.com From 80e66bc5756552ed96ca133bf64e2e530b800d3c Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Mon, 26 Aug 2024 22:49:58 +0100 Subject: [PATCH 5/7] Add appId platform integration test --- .../kotlin/maestro/test/drivers/FakeDriver.kt | 5 ++- .../kotlin/maestro/test/IntegrationTest.kt | 33 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt b/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt index 8bb50e17e3..02d514cc69 100644 --- a/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt +++ b/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt @@ -24,14 +24,13 @@ import com.google.common.truth.Truth.assertThat import maestro.* import maestro.utils.ScreenshotUtils import okio.Sink -import okio.Source import okio.buffer import java.awt.image.BufferedImage import java.io.File import java.util.UUID import javax.imageio.ImageIO -class FakeDriver : Driver { +class FakeDriver(private val platform: Platform = Platform.IOS) : Driver { private var state: State = State.NOT_INITIALIZED private var layout: FakeLayoutElement = FakeLayoutElement() @@ -73,7 +72,7 @@ class FakeDriver : Driver { ensureOpen() return DeviceInfo( - platform = Platform.IOS, + platform = platform, widthPixels = 1080, heightPixels = 1920, widthGrid = 540, diff --git a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt index 0b9dad9265..e194f05502 100644 --- a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt +++ b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt @@ -375,6 +375,35 @@ class IntegrationTest { ) } + @Test + fun `Case 013 - Launch app with platform`() { + // Given + val commands = readCommands("013_launch_app") + + val driver = driver(platform = Platform.ANDROID) {} + driver.addInstalledApp("android.app") + driver.addInstalledApp("another.app") + driver.addInstalledApp("com.example.app") + + // When + Maestro(driver).use { + orchestra(it).runFlow(commands) + } + + // Then + // No test failure + driver.assertEvents( + listOf( + Event.StopApp("android.app"), + Event.LaunchApp("android.app"), + Event.StopApp("another.app"), + Event.LaunchApp("another.app"), + Event.StopApp("com.example.app"), + Event.LaunchApp("com.example.app"), + ) + ) + } + @Test fun `Case 014 - Tap on point`() { // Given @@ -3132,8 +3161,8 @@ class IntegrationTest { onCommandFailed = onCommandFailed, ) - private fun driver(builder: FakeLayoutElement.() -> Unit): FakeDriver { - val driver = FakeDriver() + private fun driver(platform: Platform = Platform.IOS, builder: FakeLayoutElement.() -> Unit): FakeDriver { + val driver = FakeDriver(platform) driver.setLayout(FakeLayoutElement().apply { builder() }) driver.open() return driver From 38136eefc76c2078c3522a49c424d3dbf602b811 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Tue, 27 Aug 2024 00:31:07 +0100 Subject: [PATCH 6/7] Use annotation for deserializer --- .../src/main/java/maestro/orchestra/MaestroConfig.kt | 2 ++ .../maestro/orchestra/MaestroCommandSerializationTest.kt | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt index 8790f43d1d..735de14e58 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import maestro.Platform import maestro.js.JsEngine import maestro.orchestra.util.Env.evaluateScripts @@ -30,6 +31,7 @@ data class MaestroConfig( } +@JsonDeserialize(using = MaestroAppId.Deserializer::class) data class MaestroAppId( val android: String?, val ios: String?, diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index ff318e21a2..6ed8c8c7fa 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -598,10 +598,7 @@ internal class MaestroCommandSerializationTest { .writerWithDefaultPrettyPrinter() .writeValueAsString(this) - private val module = KotlinModule.Builder().build() - .addDeserializer(MaestroAppId::class.java, MaestroAppId.Deserializer()) - private val objectMapper = ObjectMapper() .setSerializationInclusion(Include.NON_NULL) - .registerModule(module) + .registerModule(KotlinModule.Builder().build()) } From 3228232b3a039c00ccfc5ea741627fb4ccfb8ba3 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Tue, 27 Aug 2024 00:33:04 +0100 Subject: [PATCH 7/7] Fix description for appId --- .../java/maestro/cli/runner/TestRunner.kt | 8 +++++-- .../src/main/java/maestro/Platform.kt | 10 +++++++-- .../main/java/maestro/orchestra/Commands.kt | 4 ++-- .../java/maestro/orchestra/MaestroCommand.kt | 15 +++++++++++++ .../java/maestro/orchestra/MaestroConfig.kt | 21 ++++++++++++------- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt b/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt index 3a42fc7084..bc20224184 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt @@ -7,6 +7,7 @@ import com.github.michaelbull.result.get import com.github.michaelbull.result.getOr import com.github.michaelbull.result.onFailure import maestro.Maestro +import maestro.Platform import maestro.cli.device.Device import maestro.cli.report.FlowAIOutput import maestro.cli.report.FlowDebugOutput @@ -23,6 +24,7 @@ import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path import kotlin.concurrent.thread +import maestro.orchestra.withPlatformInAppId /** * Knows how to run a single Maestro flow (either one-shot or continuously). @@ -51,13 +53,13 @@ object TestRunner { ) val result = runCatching(resultView, maestro) { + val platform = device?.platform?.name?.let { Platform.fromString(it) } val commands = YamlCommandReader.readCommands(flowFile.toPath()) .withEnv(env) - + .withPlatformInAppId(platform) YamlCommandReader.getConfig(commands)?.name?.let { aiOutput = aiOutput.copy(flowName = it) } - MaestroCommandRunner.runCommands( maestro = maestro, device = device, @@ -106,9 +108,11 @@ object TestRunner { join() } + val platform = device?.platform?.name?.let { Platform.fromString(it) } val commands = YamlCommandReader .readCommands(flowFile.toPath()) .withEnv(env) + .withPlatformInAppId(platform) // Restart the flow if anything has changed if (commands != previousCommands) { diff --git a/maestro-client/src/main/java/maestro/Platform.kt b/maestro-client/src/main/java/maestro/Platform.kt index 73bed1848e..8137080902 100644 --- a/maestro-client/src/main/java/maestro/Platform.kt +++ b/maestro-client/src/main/java/maestro/Platform.kt @@ -1,5 +1,11 @@ package maestro enum class Platform { - ANDROID, IOS, WEB -} \ No newline at end of file + ANDROID, IOS, WEB; + + companion object { + fun fromString(p: String): Platform? { + return values().find { it.name.lowercase() == p.lowercase() } + } + } +} 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 d8b6ae8b7e..d752b6ef5a 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -427,9 +427,9 @@ data class LaunchAppCommand( } var result = if (clearState != true) { - "Launch app \"$appId\"" + "Launch app \"${appId.description()}\"" } else { - "Launch app \"$appId\" with clear state" + "Launch app \"${appId.description()}\" with clear state" } if (clearKeychain == true) { 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 41715213a3..999788d132 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt @@ -19,6 +19,7 @@ package maestro.orchestra +import maestro.Platform import maestro.js.JsEngine /** @@ -164,3 +165,17 @@ data class MaestroCommand( return asCommand()?.description() ?: "No op" } } + +fun List.withPlatformInAppId(platform: Platform?) = if (platform == null) this else map { + when (val c = it.asCommand()) { + is LaunchAppCommand -> MaestroCommand(c.copy(appId = c.appId.copy(platform = platform))) + is KillAppCommand -> MaestroCommand(c.copy(appId = c.appId.copy(platform = platform))) + is StopAppCommand -> MaestroCommand(c.copy(appId = c.appId.copy(platform = platform))) + is ClearStateCommand -> MaestroCommand(c.copy(appId = c.appId.copy(platform = platform))) + is RunFlowCommand -> + MaestroCommand(c.copy(config = c.config?.copy(appId = c.config.appId?.copy(platform = platform)))) + is ApplyConfigurationCommand -> + MaestroCommand(c.copy(config = c.config.copy(appId = c.config.appId?.copy(platform = platform)))) + else -> it + } +} diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt index 735de14e58..cf133329d2 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroConfig.kt @@ -36,6 +36,7 @@ data class MaestroAppId( val android: String?, val ios: String?, val web: String?, + private val platform: Platform? = null, ) { constructor(appId: String) : this(appId, appId, appId) @@ -45,13 +46,19 @@ data class MaestroAppId( Platform.WEB -> web ?: error("No appId specified for web.") } - fun evaluateScripts(jsEngine: JsEngine): MaestroAppId { - return copy( - android = android?.evaluateScripts(jsEngine), - ios = ios?.evaluateScripts(jsEngine), - web = web?.evaluateScripts(jsEngine), - ) - } + fun evaluateScripts(jsEngine: JsEngine) = copy( + android = android?.evaluateScripts(jsEngine), + ios = ios?.evaluateScripts(jsEngine), + web = web?.evaluateScripts(jsEngine), + ) + + fun description() = + if (listOfNotNull(android, ios, web).toSet().size == 1) listOfNotNull(android, ios, web).first() + else if (platform != null) forPlatform(platform) + else listOf("android", "ios", "web") + .zip(listOf(android, ios, web)) + .filter { it.second != null } + .joinToString(", ") { "${it.first}: ${it.second}" } class Deserializer : JsonDeserializer() {