diff --git a/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt b/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt index 1a369c4ab2..e9cc9dede3 100644 --- a/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt +++ b/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt @@ -93,6 +93,7 @@ object TestDebugReporter { val status = when (it.status) { CommandStatus.COMPLETED -> "✅" CommandStatus.FAILED -> "❌" + CommandStatus.WARNED -> "⚠️" else -> "﹖" } val filename = "screenshot-$status-${it.timestamp}-(${flowName}).png" diff --git a/maestro-cli/src/main/java/maestro/cli/runner/CommandStatus.kt b/maestro-cli/src/main/java/maestro/cli/runner/CommandStatus.kt index 484d53bdab..e68eae3e13 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/CommandStatus.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/CommandStatus.kt @@ -24,5 +24,6 @@ enum class CommandStatus { RUNNING, COMPLETED, FAILED, + WARNED, SKIPPED, } diff --git a/maestro-cli/src/main/java/maestro/cli/runner/MaestroCommandRunner.kt b/maestro-cli/src/main/java/maestro/cli/runner/MaestroCommandRunner.kt index 12c620da81..bcdfaadc8f 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/MaestroCommandRunner.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/MaestroCommandRunner.kt @@ -172,6 +172,17 @@ object MaestroCommandRunner { } refreshUi() }, + onCommandWarned = { _, command -> + logger.info("${command.description()} WARNED") + commandStatuses[command] = CommandStatus.WARNED + debugOutput.commands[command]?.apply { + status = CommandStatus.WARNED + } + + takeDebugScreenshot(CommandStatus.WARNED) + + refreshUi() + }, onCommandReset = { command -> logger.info("${command.description()} PENDING") commandStatuses[command] = CommandStatus.PENDING diff --git a/maestro-cli/src/main/java/maestro/cli/runner/TestSuiteInteractor.kt b/maestro-cli/src/main/java/maestro/cli/runner/TestSuiteInteractor.kt index a4d31c819b..7d6f5f1197 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/TestSuiteInteractor.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/TestSuiteInteractor.kt @@ -231,6 +231,12 @@ class TestSuiteInteractor( it.status = CommandStatus.SKIPPED } }, + onCommandWarned = { _, command -> + logger.info("${command.description()} WARNED") + debugOutput.commands[command]?.apply { + status = CommandStatus.WARNED + } + }, onCommandReset = { command -> logger.info("${command.description()} PENDING") debugOutput.commands[command]?.let { diff --git a/maestro-cli/src/main/java/maestro/cli/runner/resultview/AnsiResultView.kt b/maestro-cli/src/main/java/maestro/cli/runner/resultview/AnsiResultView.kt index 3b7fb574ff..160b029748 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/resultview/AnsiResultView.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/resultview/AnsiResultView.kt @@ -119,6 +119,8 @@ class AnsiResultView( if (commandState.status == CommandStatus.SKIPPED) { render(" (skipped)") + } else if (commandState.status == CommandStatus.WARNED) { + render(" (warned)") } else if (commandState.numberOfRuns != null) { val timesWord = if (commandState.numberOfRuns == 1) "time" else "times" render(" (completed ${commandState.numberOfRuns} $timesWord)") @@ -204,6 +206,7 @@ class AnsiResultView( CommandStatus.FAILED -> "❌" CommandStatus.RUNNING -> "⏳" CommandStatus.PENDING -> "\uD83D\uDD32 " // 🔲 + CommandStatus.WARNED -> "⚠️" CommandStatus.SKIPPED -> "⚪️" } } diff --git a/maestro-cli/src/main/java/maestro/cli/runner/resultview/PlainTextResultView.kt b/maestro-cli/src/main/java/maestro/cli/runner/resultview/PlainTextResultView.kt index 8ba8ff7c1d..fe46347a6d 100644 --- a/maestro-cli/src/main/java/maestro/cli/runner/resultview/PlainTextResultView.kt +++ b/maestro-cli/src/main/java/maestro/cli/runner/resultview/PlainTextResultView.kt @@ -117,7 +117,7 @@ class PlainTextResultView: ResultView { } } - CommandStatus.COMPLETED, CommandStatus.FAILED, CommandStatus.SKIPPED -> { + CommandStatus.COMPLETED, CommandStatus.FAILED, CommandStatus.SKIPPED, CommandStatus.WARNED -> { registerStep() if (shouldPrintStep()) { println(" " + status(command.status)) @@ -148,6 +148,7 @@ class PlainTextResultView: ResultView { CommandStatus.RUNNING -> "RUNNING" CommandStatus.PENDING -> "PENDING" CommandStatus.SKIPPED -> "SKIPPED" + CommandStatus.WARNED -> "WARNED" } } } 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 8444979435..0d721b82f6 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -36,6 +36,9 @@ sealed interface Command { fun visible(): Boolean = true + val label: String? + + val optional: Boolean } sealed interface CompositeCommand : Command { @@ -52,7 +55,8 @@ data class SwipeCommand( val startRelative: String? = null, val endRelative: String? = null, val duration: Long = DEFAULT_DURATION_IN_MILLIS, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -99,7 +103,8 @@ data class ScrollUntilVisibleCommand( val visibilityPercentage: Int, val timeout: Long = DEFAULT_TIMEOUT_IN_MILLIS, val centerElement: Boolean, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { val visibilityPercentageNormalized = (visibilityPercentage / 100).toDouble() @@ -128,7 +133,8 @@ data class ScrollUntilVisibleCommand( } class ScrollCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun equals(other: Any?): Boolean { @@ -155,7 +161,8 @@ class ScrollCommand( } class BackPressCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun equals(other: Any?): Boolean { @@ -182,7 +189,8 @@ class BackPressCommand( } class HideKeyboardCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun equals(other: Any?): Boolean { @@ -210,7 +218,8 @@ class HideKeyboardCommand( data class CopyTextFromCommand( val selector: ElementSelector, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -225,7 +234,8 @@ data class CopyTextFromCommand( } data class PasteTextCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -244,7 +254,8 @@ data class TapOnElementCommand( val longPress: Boolean? = null, val repeat: TapRepeat? = null, val waitToSettleTimeoutMs: Int? = null, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -271,7 +282,8 @@ data class TapOnPointCommand( val waitUntilVisible: Boolean? = null, val longPress: Boolean? = null, val repeat: TapRepeat? = null, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -289,7 +301,8 @@ data class TapOnPointV2Command( val longPress: Boolean? = null, val repeat: TapRepeat? = null, val waitToSettleTimeoutMs: Int? = null, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -309,7 +322,8 @@ data class AssertCommand( val visible: ElementSelector? = null, val notVisible: ElementSelector? = null, val timeout: Long? = null, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -350,7 +364,8 @@ data class AssertCommand( data class AssertConditionCommand( val condition: Condition, private val timeout: String? = null, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { fun timeoutMs(): Long? { @@ -370,8 +385,8 @@ data class AssertConditionCommand( } data class AssertNoDefectsWithAICommand( - val optional: Boolean = true, - val label: String? = null, + override val optional: Boolean = true, + override val label: String? = null, ) : Command { override fun description(): String { if (label != null) return label @@ -384,8 +399,8 @@ data class AssertNoDefectsWithAICommand( data class AssertWithAICommand( val assertion: String, - val optional: Boolean = true, - val label: String? = null, + override val optional: Boolean = true, + override val label: String? = null, ) : Command { override fun description(): String { if (label != null) return label @@ -402,7 +417,8 @@ data class AssertWithAICommand( data class InputTextCommand( val text: String, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -423,7 +439,8 @@ data class LaunchAppCommand( val stopApp: Boolean? = null, var permissions: Map? = null, val launchArguments: Map? = null, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -465,7 +482,8 @@ data class LaunchAppCommand( data class ApplyConfigurationCommand( val config: MaestroConfig, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -485,7 +503,8 @@ data class OpenLinkCommand( val link: String, val autoVerify: Boolean? = null, val browser: Boolean? = null, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -507,7 +526,8 @@ data class OpenLinkCommand( data class PressKeyCommand( val code: KeyCode, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -522,7 +542,8 @@ data class PressKeyCommand( data class EraseTextCommand( val charactersToErase: Int?, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -543,7 +564,8 @@ data class EraseTextCommand( data class TakeScreenshotCommand( val path: String, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -559,7 +581,8 @@ data class TakeScreenshotCommand( data class StopAppCommand( val appId: String, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -575,7 +598,8 @@ data class StopAppCommand( data class KillAppCommand( val appId: String, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -591,7 +615,8 @@ data class KillAppCommand( data class ClearStateCommand( val appId: String, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -606,7 +631,8 @@ data class ClearStateCommand( } class ClearKeychainCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -636,7 +662,8 @@ enum class InputRandomType { data class InputRandomCommand( val inputType: InputRandomType? = InputRandomType.TEXT, val length: Int? = 8, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { fun genRandomString(): String { @@ -666,7 +693,8 @@ data class RunFlowCommand( val condition: Condition? = null, val sourceDescription: String? = null, val config: MaestroConfig?, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : CompositeCommand { override fun subCommands(): List { @@ -705,7 +733,8 @@ data class RunFlowCommand( data class SetLocationCommand( val latitude: String, val longitude: String, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -724,7 +753,8 @@ data class RepeatCommand( val times: String? = null, val condition: Condition? = null, val commands: List, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : CompositeCommand { override fun subCommands(): List { @@ -763,7 +793,8 @@ data class RepeatCommand( data class DefineVariablesCommand( val env: Map, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -787,7 +818,8 @@ data class RunScriptCommand( val env: Map = emptyMap(), val sourceDescription: String, val condition: Condition?, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -813,7 +845,8 @@ data class RunScriptCommand( data class WaitForAnimationToEndCommand( val timeout: Long?, - val label: String? = null + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -827,7 +860,8 @@ data class WaitForAnimationToEndCommand( data class EvalScriptCommand( val scriptString: String, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -843,7 +877,8 @@ data class EvalScriptCommand( data class TravelCommand( val points: List, val speedMPS: Double? = null, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { data class GeoPoint( @@ -888,7 +923,8 @@ data class TravelCommand( data class StartRecordingCommand( val path: String, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -904,7 +940,8 @@ data class StartRecordingCommand( data class AddMediaCommand( val mediaPaths: List, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ): Command { override fun description(): String { @@ -920,7 +957,8 @@ data class AddMediaCommand( data class StopRecordingCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { @@ -939,7 +977,8 @@ enum class AirplaneValue { data class SetAirplaneModeCommand( val value: AirplaneValue, - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { return label @@ -955,7 +994,8 @@ data class SetAirplaneModeCommand( } data class ToggleAirplaneModeCommand( - val label: String? = null, + override val label: String? = null, + override val optional: Boolean = false, ) : Command { override fun description(): String { diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index f1b4ab538b..266ff2014d 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -80,6 +80,7 @@ class Orchestra( private val onCommandStart: (Int, MaestroCommand) -> Unit = { _, _ -> }, private val onCommandComplete: (Int, MaestroCommand) -> Unit = { _, _ -> }, private val onCommandFailed: (Int, MaestroCommand, Throwable) -> ErrorResolution = { _, _, e -> throw e }, + private val onCommandWarned: (Int, MaestroCommand) -> Unit = { _, _ -> }, private val onCommandSkipped: (Int, MaestroCommand) -> Unit = { _, _ -> }, private val onCommandReset: (MaestroCommand) -> Unit = {}, private val onCommandMetadataUpdate: (MaestroCommand, CommandMetadata) -> Unit = { _, _ -> }, @@ -188,13 +189,21 @@ class Orchestra( } Insights.onInsightsUpdated(callback) try { - executeCommand(evaluatedCommand, config) - onCommandComplete(index, command) + try { + executeCommand(evaluatedCommand, config) + onCommandComplete(index, command) + } catch (e: MaestroException) { + val isOptional = command.asCommand()?.optional == true + if (isOptional) throw CommandWarned + else throw e + } + } catch (ignored: CommandWarned) { + // Swallow exception + onCommandWarned(index, command) } catch (ignored: CommandSkipped) { // Swallow exception onCommandSkipped(index, command) } catch (e: Throwable) { - when (onCommandFailed(index, command, e)) { ErrorResolution.FAIL -> return false ErrorResolution.CONTINUE -> { @@ -357,7 +366,7 @@ class Orchestra( if (defects.isNotEmpty()) { onCommandGeneratedOutput(command, defects, imageData) - if (command.optional) throw CommandSkipped + if (command.optional) throw CommandWarned val word = if (defects.size == 1) "defect" else "defects" throw MaestroException.AssertionFailure( @@ -388,7 +397,7 @@ class Orchestra( if (defect != null) { onCommandGeneratedOutput(command, listOf(defect), imageData) - if (command.optional) throw CommandSkipped + if (command.optional) throw CommandWarned throw MaestroException.AssertionFailure( message = """ @@ -1201,6 +1210,8 @@ class Orchestra( private object CommandSkipped : Exception() + private object CommandWarned : Exception() + data class CommandMetadata( val numberOfRuns: Int? = null, val evaluatedCommand: MaestroCommand? = null, diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAction.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAction.kt index fa5a0c2f94..44f7676882 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAction.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAction.kt @@ -2,20 +2,25 @@ package maestro.orchestra.yaml data class YamlActionBack( val label: String? = null, + val optional: Boolean = false, ) data class YamlActionClearKeychain( val label: String? = null, + val optional: Boolean = false, ) data class YamlActionHideKeyboard( val label: String? = null, + val optional: Boolean = false, ) data class YamlActionPasteText( val label: String? = null, + val optional: Boolean = false, ) data class YamlActionScroll( val label: String? = null, -) \ No newline at end of file + val optional: Boolean = false, +) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAddMedia.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAddMedia.kt index 4aa5c51f7c..9d19832588 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAddMedia.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAddMedia.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlAddMedia( val files: List? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -14,4 +15,4 @@ data class YamlAddMedia( files = files, ) } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAssertTrue.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAssertTrue.kt index 001cd082a5..48fe70fabd 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAssertTrue.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlAssertTrue.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlAssertTrue( val condition: String? = null, val label: String? = null, + val optional: Boolean = false, ){ companion object { @@ -16,8 +17,9 @@ data class YamlAssertTrue( is Int, is Long, is Char, is Boolean, is Float, is Double -> condition.toString() is Map<*, *> -> { val evaluatedCondition = condition.getOrDefault("condition", "") as String - val label = condition.getOrDefault("label", "") as String - return YamlAssertTrue(evaluatedCondition, label) + val label = condition.getOrDefault("label", null) as String? + val optional = condition.getOrDefault("optional", false) as Boolean + return YamlAssertTrue(evaluatedCondition, label, optional) } else -> throw UnsupportedOperationException("Cannot deserialize assert true with data type ${condition.javaClass}") } @@ -27,4 +29,3 @@ data class YamlAssertTrue( } } } - 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..efc3c35273 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlClearState.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlClearState( val appId: String? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { @JvmStatic diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCondition.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCondition.kt index 7f68f4fec3..6a1ee24c30 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCondition.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCondition.kt @@ -10,4 +10,5 @@ data class YamlCondition( val notVisible: YamlElementSelectorUnion? = null, val `true`: String? = null, val label: String? = null, + val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEraseTextUnion.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEraseTextUnion.kt index cb488b8d51..b919f839fe 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEraseTextUnion.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEraseTextUnion.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlEraseText( val charactersToErase: Int? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -17,4 +18,4 @@ data class YamlEraseText( ) } } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEvalScript.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEvalScript.kt index 7a7ab55b76..b4936524ba 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEvalScript.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlEvalScript.kt @@ -6,6 +6,7 @@ import java.lang.UnsupportedOperationException data class YamlEvalScript( val script: String, val label: String? = null, + val optional: Boolean = false, ){ companion object { @@ -16,7 +17,7 @@ data class YamlEvalScript( is String -> script is Map<*, *> -> { val evaluatedScript = script.getOrDefault("script", "") as String - val label = script.getOrDefault("label", "") as String + val label = script.getOrDefault("label", null) as String? return YamlEvalScript(evaluatedScript, label) } is Int, is Long, is Char, is Boolean, is Float, is Double -> script.toString() @@ -28,4 +29,3 @@ data class YamlEvalScript( } } } - diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlExtendedWaitUntil.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlExtendedWaitUntil.kt index a07bcc8795..07e3376765 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlExtendedWaitUntil.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlExtendedWaitUntil.kt @@ -4,5 +4,6 @@ data class YamlExtendedWaitUntil( val visible: YamlElementSelectorUnion? = null, val notVisible: YamlElementSelectorUnion? = null, val timeout: String? = null, - val label: String? = null + val label: String? = null, + val optional: Boolean = false, ) 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 83a714e8b4..952481649d 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -93,7 +93,8 @@ data class YamlFluentCommand( condition = Condition( visible = toElementSelector(assertVisible), ), - label = (assertVisible as? YamlElementSelector)?.label + label = (assertVisible as? YamlElementSelector)?.label, + optional = (assertVisible as? YamlElementSelector)?.optional ?: false, ) ) ) @@ -103,7 +104,8 @@ data class YamlFluentCommand( condition = Condition( notVisible = toElementSelector(assertNotVisible), ), - label = (assertNotVisible as? YamlElementSelector)?.label + label = (assertNotVisible as? YamlElementSelector)?.label, + optional = (assertNotVisible as? YamlElementSelector)?.optional ?: false, ) ) ) @@ -113,7 +115,8 @@ data class YamlFluentCommand( Condition( scriptCondition = assertTrue.condition, ), - label = assertTrue.label + label = assertTrue.label, + optional = assertTrue.optional, ) ) ) @@ -139,14 +142,14 @@ data class YamlFluentCommand( addMediaCommand = addMediaCommand(addMedia, flowPath) ) ) - inputText != null -> listOf(MaestroCommand(InputTextCommand(text = inputText.text, label = inputText.label))) - inputRandomText != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT, length = inputRandomText.length, label = inputRandomText.label))) - inputRandomNumber != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.NUMBER, length = inputRandomNumber.length, label = inputRandomNumber.label))) - inputRandomEmail != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT_EMAIL_ADDRESS, label = inputRandomEmail.label))) - inputRandomPersonName != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT_PERSON_NAME, label = inputRandomPersonName.label))) + inputText != null -> listOf(MaestroCommand(InputTextCommand(text = inputText.text, label = inputText.label, optional = inputText.optional))) + inputRandomText != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT, length = inputRandomText.length, label = inputRandomText.label, optional = inputRandomText.optional))) + inputRandomNumber != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.NUMBER, length = inputRandomNumber.length, label = inputRandomNumber.label, optional = inputRandomNumber.optional))) + inputRandomEmail != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT_EMAIL_ADDRESS, label = inputRandomEmail.label, optional = inputRandomEmail.optional))) + inputRandomPersonName != null -> listOf(MaestroCommand(InputRandomCommand(inputType = InputRandomType.TEXT_PERSON_NAME, label = inputRandomPersonName.label, optional = inputRandomPersonName.optional))) swipe != null -> listOf(swipeCommand(swipe)) - openLink != null -> listOf(MaestroCommand(OpenLinkCommand(link = openLink.link, autoVerify = openLink.autoVerify, browser = openLink.browser, label = openLink.label))) - pressKey != null -> listOf(MaestroCommand(PressKeyCommand(code = KeyCode.getByName(pressKey.key) ?: throw SyntaxError("Unknown key name: $pressKey"), label = pressKey.label))) + openLink != null -> listOf(MaestroCommand(OpenLinkCommand(link = openLink.link, autoVerify = openLink.autoVerify, browser = openLink.browser, label = openLink.label, optional = openLink.optional))) + pressKey != null -> listOf(MaestroCommand(PressKeyCommand(code = KeyCode.getByName(pressKey.key) ?: throw SyntaxError("Unknown key name: $pressKey"), label = pressKey.label, optional = pressKey.optional))) eraseText != null -> listOf(eraseCommand(eraseText)) action != null -> listOf( when (action) { @@ -158,18 +161,19 @@ data class YamlFluentCommand( else -> error("Unknown navigation target: $action") } ) - back != null -> listOf(MaestroCommand(BackPressCommand(label = back.label))) - clearKeychain != null -> listOf(MaestroCommand(ClearKeychainCommand(label = clearKeychain.label))) - hideKeyboard != null -> listOf(MaestroCommand(HideKeyboardCommand(label = hideKeyboard.label))) - pasteText != null -> listOf(MaestroCommand(PasteTextCommand(label = pasteText.label))) - scroll != null -> listOf(MaestroCommand(ScrollCommand(label = scroll.label))) - takeScreenshot != null -> listOf(MaestroCommand(TakeScreenshotCommand(path = takeScreenshot.path, label = takeScreenshot.label))) + back != null -> listOf(MaestroCommand(BackPressCommand(label = back.label, optional = back.optional))) + clearKeychain != null -> listOf(MaestroCommand(ClearKeychainCommand(label = clearKeychain.label, optional = clearKeychain.optional))) + hideKeyboard != null -> listOf(MaestroCommand(HideKeyboardCommand(label = hideKeyboard.label, optional = hideKeyboard.optional))) + pasteText != null -> listOf(MaestroCommand(PasteTextCommand(label = pasteText.label, optional = pasteText.optional))) + scroll != null -> listOf(MaestroCommand(ScrollCommand(label = scroll.label, optional = scroll.optional))) + takeScreenshot != null -> listOf(MaestroCommand(TakeScreenshotCommand(path = takeScreenshot.path, label = takeScreenshot.label, optional = takeScreenshot.optional))) extendedWaitUntil != null -> listOf(extendedWait(extendedWaitUntil)) stopApp != null -> listOf( MaestroCommand( StopAppCommand( appId = stopApp.appId ?: appId, - label = stopApp.label + label = stopApp.label, + optional = stopApp.optional, ) ) ) @@ -177,15 +181,17 @@ data class YamlFluentCommand( MaestroCommand( KillAppCommand( appId = killApp.appId ?: appId, - label = killApp.label + label = killApp.label, + optional = killApp.optional, ) ) ) clearState != null -> listOf( MaestroCommand( - maestro.orchestra.ClearStateCommand( + ClearStateCommand( appId = clearState.appId ?: appId, - label = clearState.label + label = clearState.label, + optional = clearState.optional, ) ) ) @@ -195,7 +201,8 @@ data class YamlFluentCommand( SetLocationCommand( latitude = setLocation.latitude, longitude = setLocation.longitude, - label = setLocation.label + label = setLocation.label, + optional = setLocation.optional, ) ) ) @@ -211,7 +218,8 @@ data class YamlFluentCommand( env = runScript.env, sourceDescription = runScript.file, condition = runScript.`when`?.toCondition(), - label = runScript.label + label = runScript.label, + optional = runScript.optional, ) ) ) @@ -219,7 +227,8 @@ data class YamlFluentCommand( MaestroCommand( WaitForAnimationToEndCommand( timeout = waitForAnimationToEnd.timeout, - label = waitForAnimationToEnd.label + label = waitForAnimationToEnd.label, + optional = waitForAnimationToEnd.optional, ) ) ) @@ -227,22 +236,23 @@ data class YamlFluentCommand( MaestroCommand( EvalScriptCommand( scriptString = evalScript.script, - label = evalScript.label + label = evalScript.label, + optional = evalScript.optional, ) ) ) scrollUntilVisible != null -> listOf(scrollUntilVisibleCommand(scrollUntilVisible)) travel != null -> listOf(travelCommand(travel)) - startRecording != null -> listOf(MaestroCommand(StartRecordingCommand(startRecording.path, startRecording.label))) - stopRecording != null -> listOf(MaestroCommand(StopRecordingCommand(stopRecording.label))) + startRecording != null -> listOf(MaestroCommand(StartRecordingCommand(startRecording.path, startRecording.label, startRecording.optional))) + stopRecording != null -> listOf(MaestroCommand(StopRecordingCommand(stopRecording.label, stopRecording.optional))) doubleTapOn != null -> { val yamlDelay = (doubleTapOn as? YamlElementSelector)?.delay?.toLong() val delay = if (yamlDelay != null && yamlDelay >= 0) yamlDelay else TapOnElementCommand.DEFAULT_REPEAT_DELAY 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))) else -> throw SyntaxError("Invalid command: No mapping provided for $this") } } @@ -266,7 +276,7 @@ data class YamlFluentCommand( resolvedPath } val mediaAbsolutePathStrings = mediaPaths.mapNotNull { it.absolutePathString() } - return AddMediaCommand(mediaAbsolutePathStrings, addMedia.label) + return AddMediaCommand(mediaAbsolutePathStrings, addMedia.label, addMedia.optional) } private fun runFlowCommand( @@ -299,7 +309,8 @@ data class YamlFluentCommand( condition = runFlow.`when`?.toCondition(), sourceDescription = runFlow.file, config = config, - label = runFlow.label + label = runFlow.label, + optional = runFlow.optional, ) ) } @@ -325,6 +336,7 @@ data class YamlFluentCommand( }, speedMPS = command.speed, label = command.label, + optional = command.optional, ) ) } @@ -336,14 +348,15 @@ data class YamlFluentCommand( commands = repeat.commands .flatMap { it.toCommands(flowPath, appId) }, label = repeat.label, + optional = repeat.optional, ) ) private fun eraseCommand(eraseText: YamlEraseText): MaestroCommand { return if (eraseText.charactersToErase != null) { - MaestroCommand(EraseTextCommand(charactersToErase = eraseText.charactersToErase, label = eraseText.label)) + MaestroCommand(EraseTextCommand(charactersToErase = eraseText.charactersToErase, label = eraseText.label, optional = eraseText.optional)) } else { - MaestroCommand(EraseTextCommand(charactersToErase = null, label = eraseText.label)) + MaestroCommand(EraseTextCommand(charactersToErase = null, label = eraseText.label, optional = eraseText.optional)) } } @@ -413,6 +426,7 @@ data class YamlFluentCommand( condition = condition, timeout = command.timeout, label = command.label, + optional = command.optional, ) ) } @@ -427,6 +441,7 @@ data class YamlFluentCommand( permissions = command.permissions, launchArguments = command.arguments, label = command.label, + optional = command.optional, ) ) } @@ -440,6 +455,7 @@ data class YamlFluentCommand( val waitUntilVisible = (tapOn as? YamlElementSelector)?.waitUntilVisible ?: false val point = (tapOn as? YamlElementSelector)?.point val label = (tapOn as? YamlElementSelector)?.label + val optional = (tapOn as? YamlElementSelector)?.optional ?: false val delay = (tapOn as? YamlElementSelector)?.delay?.toLong() val repeat = tapRepeat ?: (tapOn as? YamlElementSelector)?.repeat?.let { @@ -461,7 +477,8 @@ data class YamlFluentCommand( longPress = longPress, repeat = repeat, waitToSettleTimeoutMs = waitToSettleTimeoutMs, - label = label + label = label, + optional = optional, ) ) } else { @@ -473,7 +490,8 @@ data class YamlFluentCommand( longPress = longPress, repeat = repeat, waitToSettleTimeoutMs = waitToSettleTimeoutMs, - label = label + label = label, + optional = optional, ) ) } @@ -481,7 +499,7 @@ data class YamlFluentCommand( private fun swipeCommand(swipe: YamlSwipe): MaestroCommand { when (swipe) { - is YamlSwipeDirection -> return MaestroCommand(SwipeCommand(direction = swipe.direction, duration = swipe.duration, label = swipe.label)) + is YamlSwipeDirection -> return MaestroCommand(SwipeCommand(direction = swipe.direction, duration = swipe.duration, label = swipe.label, optional = swipe.optional)) is YamlCoordinateSwipe -> { val start = swipe.start val end = swipe.end @@ -500,11 +518,11 @@ data class YamlFluentCommand( } endPoint = Point(endPoints[0], endPoints[1]) - return MaestroCommand(SwipeCommand(startPoint = startPoint, endPoint = endPoint, duration = swipe.duration, label = swipe.label)) + return MaestroCommand(SwipeCommand(startPoint = startPoint, endPoint = endPoint, duration = swipe.duration, label = swipe.label, optional = swipe.optional)) } is YamlRelativeCoordinateSwipe -> { return MaestroCommand( - SwipeCommand(startRelative = swipe.start, endRelative = swipe.end, duration = swipe.duration, label = swipe.label) + SwipeCommand(startRelative = swipe.start, endRelative = swipe.end, duration = swipe.duration, label = swipe.label, optional = swipe.optional) ) } is YamlSwipeElement -> return swipeElementCommand(swipe) @@ -524,6 +542,7 @@ data class YamlFluentCommand( elementSelector = toElementSelector(swipeElement.from), duration = swipeElement.duration, label = swipeElement.label, + optional = swipeElement.optional, ) ) } @@ -587,7 +606,8 @@ data class YamlFluentCommand( MaestroCommand( CopyTextFromCommand( selector = toElementSelector(copyText), - label = (copyText as? YamlElementSelector)?.label + label = (copyText as? YamlElementSelector)?.label, + optional = (copyText as? YamlElementSelector)?.optional ?: false ) ) } @@ -608,7 +628,8 @@ data class YamlFluentCommand( scrollDuration = yaml.speed, visibilityPercentage = visibility, centerElement = yaml.centerElement, - label = yaml.label + label = yaml.label, + optional = yaml.optional, ) ) } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputRandomText.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputRandomText.kt index 73743a8746..8e1b632181 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputRandomText.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputRandomText.kt @@ -22,17 +22,21 @@ package maestro.orchestra.yaml data class YamlInputRandomText( val length: Int?, val label: String? = null, + val optional: Boolean = false, ) data class YamlInputRandomNumber( val length: Int?, val label: String? = null, + val optional: Boolean = false, ) data class YamlInputRandomEmail( val label: String? = null, + val optional: Boolean = false, ) data class YamlInputRandomPersonName( val label: String? = null, + val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputText.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputText.kt index 5fc1eaf1d9..fc22ed8bf9 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputText.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlInputText.kt @@ -6,6 +6,7 @@ import java.lang.UnsupportedOperationException data class YamlInputText( val text: String, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -17,7 +18,7 @@ data class YamlInputText( is String -> text is Map<*, *> -> { val input = text.getOrDefault("text", "") as String - val label = text.getOrDefault("label", "") as String + val label = text.getOrDefault("label", null) as String? return YamlInputText(input, label) } is Int, is Long, is Char, is Boolean, is Float, is Double -> text.toString() 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..b0f3f27f4b 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlKillApp.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlKillApp( val appId: String? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -17,4 +18,4 @@ data class YamlKillApp( } -} \ 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..5f9a3ec837 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlLaunchApp.kt @@ -28,7 +28,8 @@ data class YamlLaunchApp( val stopApp: Boolean?, val permissions: Map?, val arguments: Map?, - val label: String? = null + val label: String? = null, + val optional: Boolean = false, ) { companion object { diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOpenLink.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOpenLink.kt index 4745082bec..fa0eb54a42 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOpenLink.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlOpenLink.kt @@ -7,6 +7,7 @@ data class YamlOpenLink( val browser: Boolean = false, val autoVerify: Boolean = false, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -18,4 +19,4 @@ data class YamlOpenLink( ) } } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlPressKey.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlPressKey.kt index 0661180e08..94b5ece716 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlPressKey.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlPressKey.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlPressKey ( val key: String, val label: String? = null, + val optional: Boolean = false, ){ companion object { @JvmStatic @@ -13,4 +14,4 @@ data class YamlPressKey ( key = key, ) } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRepeatCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRepeatCommand.kt index a8a9afe619..6861541130 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRepeatCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRepeatCommand.kt @@ -5,4 +5,5 @@ data class YamlRepeatCommand( val `while`: YamlCondition? = null, val commands: List, val label: String? = null, + val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunFlow.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunFlow.kt index b38e0b600a..6ee63bf5c9 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunFlow.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunFlow.kt @@ -8,6 +8,7 @@ data class YamlRunFlow( val env: Map = emptyMap(), val commands: List? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunScript.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunScript.kt index f2d4f5e132..e98226ab5a 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunScript.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlRunScript.kt @@ -7,6 +7,7 @@ data class YamlRunScript( val env: Map = emptyMap(), val `when`: YamlCondition? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt index 33c374ff0e..8ed384709e 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt @@ -12,5 +12,6 @@ data class YamlScrollUntilVisible( val speed: String = ScrollUntilVisibleCommand.DEFAULT_SCROLL_DURATION, val visibilityPercentage: Int = ScrollUntilVisibleCommand.DEFAULT_ELEMENT_VISIBILITY_PERCENTAGE, val centerElement: Boolean = ScrollUntilVisibleCommand.DEFAULT_CENTER_ELEMENT, - val label: String? = null -) \ No newline at end of file + val label: String? = null, + val optional: Boolean = false, +) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetAirplaneMode.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetAirplaneMode.kt index 241de94b3c..b67a900b5c 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetAirplaneMode.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetAirplaneMode.kt @@ -13,6 +13,7 @@ import maestro.orchestra.AirplaneValue data class YamlSetAirplaneMode( val value: AirplaneValue, val label: String? = null, + val optional: Boolean = false, ) { companion object { @JvmStatic @@ -68,4 +69,4 @@ class YamlSetAirplaneModeDeserializer : JsonDeserializer() } } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetLocation.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetLocation.kt index b35b4413b7..ac8b999a62 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetLocation.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSetLocation.kt @@ -6,4 +6,5 @@ data class YamlSetLocation @JsonCreator constructor( val latitude: String, val longitude: String, val label: String? = null, + val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStartRecording.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStartRecording.kt index e2dfd09c3d..502f152702 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStartRecording.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStartRecording.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlStartRecording( val path: String, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -21,4 +22,5 @@ data class YamlStartRecording( data class YamlStopRecording( val label: String? = null, + val optional: Boolean = false, ) 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..5f60ed0797 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlStopApp.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlStopApp( val appId: String? = null, val label: String? = null, + val optional: Boolean = false, ) { companion object { @@ -17,4 +18,4 @@ data class YamlStopApp( } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt index bd5ac16ad5..b827be7778 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -5,10 +5,8 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.TreeNode import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.json.JsonMapper import maestro.SwipeDirection import maestro.directionValueOfOrNull @@ -16,15 +14,31 @@ import maestro.directionValueOfOrNull interface YamlSwipe { val duration: Long val label: String? + val optional: Boolean } data class YamlSwipeDirection( - val direction: SwipeDirection, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, - override val label: String? = null + val direction: SwipeDirection, + override val duration: Long = DEFAULT_DURATION_IN_MILLIS, + override val label: String? = null, + override val optional: Boolean, +) : YamlSwipe + +data class YamlCoordinateSwipe( + val start: String, + val end: String, + override val duration: Long = DEFAULT_DURATION_IN_MILLIS, + override val label: String? = null, + override val optional: Boolean, ) : YamlSwipe -data class YamlCoordinateSwipe(val start: String, val end: String, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null) : YamlSwipe -data class YamlRelativeCoordinateSwipe(val start: String, val end: String, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null) : YamlSwipe +data class YamlRelativeCoordinateSwipe( + val start: String, + val end: String, + override val duration: Long = DEFAULT_DURATION_IN_MILLIS, + override val label: String? = null, + override val optional: Boolean, +) : YamlSwipe @JsonDeserialize(`as` = YamlSwipeElement::class) data class YamlSwipeElement( @@ -32,7 +46,8 @@ data class YamlSwipeElement( val direction: SwipeDirection, val from: YamlElementSelectorUnion, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, - override val label: String? = null + override val label: String? = null, + override val optional: Boolean, ) : YamlSwipe private const val DEFAULT_DURATION_IN_MILLIS = 400L @@ -45,13 +60,14 @@ class YamlSwipeDeserializer : JsonDeserializer() { val input = root.fieldNames().asSequence().toList() val duration = getDuration(root) val label = getLabel(root) + val optional = getOptional(root) when { input.contains("start") || input.contains("end") -> { check(root.get("direction") == null) { "You cannot provide direction with start/end swipe." } check(root.get("start") != null && root.get("end") != null) { "You need to provide both start and end coordinates, to swipe with coordinates" } - return resolveCoordinateSwipe(root, duration, label) + return resolveCoordinateSwipe(root, duration, label, optional) } input.contains("direction") -> { check(root.get("start") == null && root.get("end") == null) { @@ -67,7 +83,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { } val isDirectionalSwipe = isDirectionalSwipe(input) return if (isDirectionalSwipe) { - YamlSwipeDirection(SwipeDirection.valueOf(direction.uppercase()), duration, label) + YamlSwipeDirection(SwipeDirection.valueOf(direction.uppercase()), duration, label, optional) } else { mapper.convertValue(root, YamlSwipeElement::class.java) } @@ -84,7 +100,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { } } - private fun resolveCoordinateSwipe(root: TreeNode, duration: Long, label: String?): YamlSwipe { + private fun resolveCoordinateSwipe(root: TreeNode, duration: Long, label: String?, optional: Boolean): YamlSwipe { when { isRelativeSwipe(root) -> { val start = root.path("start").toString().replace("\"", "") @@ -112,6 +128,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { end, duration, label, + optional, ) } else -> return YamlCoordinateSwipe( @@ -119,6 +136,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { root.path("end").toString().replace("\"", ""), duration, label, + optional, ) } } @@ -143,8 +161,16 @@ class YamlSwipeDeserializer : JsonDeserializer() { } } + private fun getOptional(root: TreeNode): Boolean { + return if (root.path("optional").isMissingNode) { + false + } else { + root.path("optional").toString().replace("\"", "").toBoolean() + } + } + private fun isDirectionalSwipe(input: List): Boolean { return input == listOf("direction", "duration") || input == listOf("direction") || input == listOf("direction", "label") || input == listOf("direction", "duration", "label") } -} \ No newline at end of file +} diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTakeScreenshot.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTakeScreenshot.kt index 47fc69380a..2c9767dd5a 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTakeScreenshot.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTakeScreenshot.kt @@ -4,7 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator data class YamlTakeScreenshot( val path: String, - val label: String? = null + val label: String? = null, + val optional: Boolean = false, ) { companion object { diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlToggleAirplaneMode.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlToggleAirplaneMode.kt index 8721d3c02e..2edc3650e0 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlToggleAirplaneMode.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlToggleAirplaneMode.kt @@ -2,4 +2,5 @@ package maestro.orchestra.yaml data class YamlToggleAirplaneMode( val label: String? = null, + val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTravelCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTravelCommand.kt index ec3b6843a3..1fba010eb9 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTravelCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlTravelCommand.kt @@ -4,4 +4,5 @@ data class YamlTravelCommand( val points: List, val speed: Double? = null, val label: String? = null, -) \ No newline at end of file + val optional: Boolean = false, +) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlWaitForAnimationToEndCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlWaitForAnimationToEndCommand.kt index 59a0bf2263..532dbc80d2 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlWaitForAnimationToEndCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlWaitForAnimationToEndCommand.kt @@ -3,4 +3,5 @@ package maestro.orchestra.yaml data class YamlWaitForAnimationToEndCommand( val timeout: Long?, val label: String? = null, -) \ No newline at end of file + val optional: Boolean = false, +) diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt index 1a1b052fc8..3339c9a316 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt @@ -39,7 +39,8 @@ internal class MaestroCommandSerializationTest { "retryIfNoChange" : false, "waitUntilVisible" : true, "longPress" : false, - "label" : "My Tap" + "label" : "My Tap", + "optional" : false } } """.trimIndent() @@ -78,7 +79,8 @@ internal class MaestroCommandSerializationTest { "retryIfNoChange" : false, "waitUntilVisible" : true, "longPress" : false, - "label" : "My TapOnPoint" + "label" : "My TapOnPoint", + "optional" : false } } """.trimIndent() @@ -112,7 +114,8 @@ internal class MaestroCommandSerializationTest { "point" : "20,30", "retryIfNoChange" : false, "longPress" : false, - "label" : "My TapOnPointV2" + "label" : "My TapOnPointV2", + "optional" : false } } """.trimIndent() @@ -137,7 +140,9 @@ internal class MaestroCommandSerializationTest { @Language("json") val expectedJson = """ { - "scrollCommand" : { } + "scrollCommand" : { + "optional" : false + } } """.trimIndent() assertThat(serializedCommandJson) @@ -173,7 +178,8 @@ internal class MaestroCommandSerializationTest { "x" : 100, "y" : 100 }, - "duration" : 400 + "duration" : 400, + "optional" : false } } """.trimIndent() @@ -198,7 +204,9 @@ internal class MaestroCommandSerializationTest { @Language("json") val expectedJson = """ { - "backPressCommand" : { } + "backPressCommand" : { + "optional" : false + } } """.trimIndent() assertThat(serializedCommandJson) @@ -233,7 +241,8 @@ internal class MaestroCommandSerializationTest { "notVisible" : { "textRegex" : "\\s", "optional" : false - } + }, + "optional" : false } } """.trimIndent() @@ -259,7 +268,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "inputTextCommand" : { - "text" : "Hello, world!" + "text" : "Hello, world!", + "optional" : false } } """.trimIndent() @@ -285,7 +295,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "launchAppCommand" : { - "appId" : "com.twitter.android" + "appId" : "com.twitter.android", + "optional" : false } } """.trimIndent() @@ -321,7 +332,8 @@ internal class MaestroCommandSerializationTest { "name" : "Twitter", "tags" : [ ], "ext" : { } - } + }, + "optional" : false } } """.trimIndent() @@ -347,7 +359,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "openLinkCommand" : { - "link" : "https://mobile.dev" + "link" : "https://mobile.dev", + "optional" : false } } """.trimIndent() @@ -373,7 +386,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "pressKeyCommand" : { - "code" : "ENTER" + "code" : "ENTER", + "optional" : false } } """.trimIndent() @@ -399,7 +413,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "eraseTextCommand" : { - "charactersToErase" : 128 + "charactersToErase" : 128, + "optional" : false } } """.trimIndent() @@ -425,7 +440,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "takeScreenshotCommand" : { - "path" : "screenshot.png" + "path" : "screenshot.png", + "optional" : false } } """.trimIndent() @@ -452,7 +468,8 @@ internal class MaestroCommandSerializationTest { { "inputRandomTextCommand" : { "inputType" : "TEXT", - "length" : 2 + "length" : 2, + "optional" : false } } """.trimIndent() @@ -479,7 +496,8 @@ internal class MaestroCommandSerializationTest { { "inputRandomTextCommand" : { "inputType" : "NUMBER", - "length" : 3 + "length" : 3, + "optional" : false } } """.trimIndent() @@ -506,7 +524,8 @@ internal class MaestroCommandSerializationTest { { "inputRandomTextCommand" : { "inputType" : "TEXT_EMAIL_ADDRESS", - "length" : 8 + "length" : 8, + "optional" : false } } """.trimIndent() @@ -520,7 +539,7 @@ internal class MaestroCommandSerializationTest { fun `serialize InputRandomCommand with person name`() { // given val command = MaestroCommand( - InputRandomCommand(InputRandomType.TEXT_PERSON_NAME) + InputRandomCommand(InputRandomType.TEXT_PERSON_NAME, optional = true) ) // when @@ -533,7 +552,8 @@ internal class MaestroCommandSerializationTest { { "inputRandomTextCommand" : { "inputType" : "TEXT_PERSON_NAME", - "length" : 8 + "length" : 8, + "optional" : true } } """.trimIndent() @@ -559,7 +579,8 @@ internal class MaestroCommandSerializationTest { val expectedJson = """ { "waitForAnimationToEndCommand" : { - "timeout" : 9 + "timeout" : 9, + "optional" : false } } """.trimIndent() diff --git a/maestro-test/src/test/resources/076_optional_assertion.yaml b/maestro-test/src/test/resources/076_optional_assertion.yaml index 2a24c1c1b4..50329a9223 100644 --- a/maestro-test/src/test/resources/076_optional_assertion.yaml +++ b/maestro-test/src/test/resources/076_optional_assertion.yaml @@ -1,5 +1,18 @@ appId: com.example.app --- +- scrollUntilVisible: + timeout: 1 + element: + id: "not_found" + optional: true +- assertTrue: + condition: "false" + optional: true +- extendedWaitUntil: + visible: + id: "not_found" + timeout: 1 + optional: true - assertVisible: text: "Button" optional: true