diff --git a/maestro-client/src/main/java/maestro/Driver.kt b/maestro-client/src/main/java/maestro/Driver.kt index ae52d76668..687ddadddc 100644 --- a/maestro-client/src/main/java/maestro/Driver.kt +++ b/maestro-client/src/main/java/maestro/Driver.kt @@ -64,6 +64,8 @@ interface Driver { fun hideKeyboard() + fun clipboardPaste() + fun takeScreenshot(out: Sink) } diff --git a/maestro-client/src/main/java/maestro/Maestro.kt b/maestro-client/src/main/java/maestro/Maestro.kt index 6b743295bf..addd9683b5 100644 --- a/maestro-client/src/main/java/maestro/Maestro.kt +++ b/maestro-client/src/main/java/maestro/Maestro.kt @@ -101,6 +101,13 @@ class Maestro(private val driver: Driver) : AutoCloseable { waitForAppToSettle() } + fun clipboardPaste() { + LOGGER.info("Paste from Clipboard") + + driver.clipboardPaste() + waitForAppToSettle() + } + fun swipe(start: Point, end: Point) { LOGGER.info("Swiping from (${start.x},${start.y}) to (${end.x},${end.y})") diff --git a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt index c97b6e2baa..bd2a8e1d6b 100644 --- a/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt @@ -278,6 +278,10 @@ class AndroidDriver( dadb.shell("input keyevent 111") } + override fun clipboardPaste() { + dadb.shell("input keyevent 279") + } + override fun takeScreenshot(out: Sink) { val deviceScreenshotPath = "/sdcard/maestro-screenshot.png" diff --git a/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt b/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt index bf5f06c76e..088863a5d2 100644 --- a/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt @@ -255,6 +255,10 @@ class IOSDriver( iosDevice.pressKey(40).expect {} } + override fun clipboardPaste() { + iosDevice.pressKey(40).expect {} + } + override fun takeScreenshot(out: Sink) { iosDevice.takeScreenshot(out).expect {} } 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 ab86b145df..e29cff3910 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -128,6 +128,31 @@ class HideKeyboardCommand : Command { } } +class ClipboardPasteCommand : Command { + + override fun equals(other: Any?): Boolean { + if (this == other) return true + if (javaClass != other?.javaClass) return false + return true + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + override fun toString(): String { + return "ClipboardPasteCommand()" + } + + override fun description(): String { + return "Paste from Clipboard" + } + + override fun injectEnv(env: Map): ClipboardPasteCommand { + return this + } +} + data class TapOnElementCommand( val selector: ElementSelector, val retryIfNoChange: Boolean? = null, diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt index cab2cda7c5..2ba328f343 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt @@ -39,6 +39,7 @@ data class MaestroCommand( val pressKeyCommand: PressKeyCommand? = null, val eraseTextCommand: EraseTextCommand? = null, val hideKeyboardCommand: HideKeyboardCommand? = null, + val clipboardPasteCommand: ClipboardPasteCommand? = null, val takeScreenshotCommand: TakeScreenshotCommand? = null, val stopAppCommand: StopAppCommand? = null, val clearStateCommand: ClearStateCommand? = null, @@ -60,6 +61,7 @@ data class MaestroCommand( pressKeyCommand = command as? PressKeyCommand, eraseTextCommand = command as? EraseTextCommand, hideKeyboardCommand = command as? HideKeyboardCommand, + clipboardPasteCommand = command as? ClipboardPasteCommand, takeScreenshotCommand = command as? TakeScreenshotCommand, stopAppCommand = command as? StopAppCommand, clearStateCommand = command as? ClearStateCommand, @@ -81,6 +83,7 @@ data class MaestroCommand( pressKeyCommand != null -> pressKeyCommand eraseTextCommand != null -> eraseTextCommand hideKeyboardCommand != null -> hideKeyboardCommand + clipboardPasteCommand != null -> clipboardPasteCommand takeScreenshotCommand != null -> takeScreenshotCommand stopAppCommand != null -> stopAppCommand clearStateCommand != null -> clearStateCommand diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index 0a9a6c386c..6502b96e53 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -127,6 +127,7 @@ class Orchestra( is TapOnPointCommand -> tapOnPoint(command, command.retryIfNoChange ?: true) is BackPressCommand -> maestro.backPress() is HideKeyboardCommand -> maestro.hideKeyboard() + is ClipboardPasteCommand -> maestro.clipboardPaste() is ScrollCommand -> maestro.scrollVertical() is SwipeCommand -> swipeCommand(command) is AssertCommand -> assertCommand(command) 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 233ebc234f..580afb32f0 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -30,6 +30,7 @@ import maestro.orchestra.ElementSelector import maestro.orchestra.ElementTrait import maestro.orchestra.EraseTextCommand import maestro.orchestra.HideKeyboardCommand +import maestro.orchestra.ClipboardPasteCommand import maestro.orchestra.InputTextCommand import maestro.orchestra.LaunchAppCommand import maestro.orchestra.MaestroCommand @@ -84,6 +85,7 @@ data class YamlFluentCommand( when (action) { "back" -> MaestroCommand(BackPressCommand()) "hide keyboard" -> MaestroCommand(HideKeyboardCommand()) + "clipboard paste" -> MaestroCommand(ClipboardPasteCommand()) "scroll" -> MaestroCommand(ScrollCommand()) "clearKeychain" -> MaestroCommand(ClearKeychainCommand()) else -> error("Unknown navigation target: $action") @@ -360,6 +362,10 @@ data class YamlFluentCommand( action = "hide keyboard" ) + "clipboard paste", "clipboardPaste" -> YamlFluentCommand( + action = "clipboard paste" + ) + "scroll" -> YamlFluentCommand( action = "scroll" ) 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 5ced65baa6..7f3cb40d3e 100644 --- a/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt +++ b/maestro-test/src/main/kotlin/maestro/test/drivers/FakeDriver.kt @@ -180,6 +180,12 @@ class FakeDriver : Driver { events += Event.HideKeyboard } + override fun clipboardPaste() { + ensureOpen() + + events += Event.ClipboardPaste + } + override fun takeScreenshot(out: Sink) { ensureOpen() @@ -280,6 +286,8 @@ class FakeDriver : Driver { object BackPress : Event(), UserInteraction object HideKeyboard : Event(), UserInteraction + + object ClipboardPaste : Event(), UserInteraction data class InputText( val text: String diff --git a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt index 4bda492f51..a3327d4765 100644 --- a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt +++ b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt @@ -1408,6 +1408,29 @@ class IntegrationTest { driver.assertEventCount(Event.Tap(Point(50, 50)), 1) } + @Test + fun `Case 050 - Paste from Clipboard`() { + // Given + val commands = readCommands("050_clipboard_paste") + + val driver = driver { + } + + // When + Maestro(driver).use { + orchestra(it).runFlow(commands) + } + + // Then + // No test failure + driver.assertEvents( + listOf( + Event.ClipboardPaste, + Event.ClipboardPaste, + ) + ) + } + private fun orchestra(it: Maestro) = Orchestra(it, lookupTimeoutMs = 0L, optionalLookupTimeoutMs = 0L) private fun driver(builder: FakeLayoutElement.() -> Unit): FakeDriver { diff --git a/maestro-test/src/test/resources/050_clipboard_paste.yaml b/maestro-test/src/test/resources/050_clipboard_paste.yaml new file mode 100644 index 0000000000..7393b24930 --- /dev/null +++ b/maestro-test/src/test/resources/050_clipboard_paste.yaml @@ -0,0 +1,4 @@ +appId: com.example.app +--- +- action: clipboard paste +- clipboardPaste \ No newline at end of file