feat(sidecar): EraseText backend support on android and ios

This commit is contained in:
pj committed 2026-06-05 17:40:09 +05:30
1 parent 89e9601e43
commit 5ede139945
5 files changed
+45

No files matched your search

@@ -6,6 +6,7 @@ interface DriverBackend {
fun tap(x: Int, y: Int)
fun tapSelector(selector: String)
fun inputText(text: String)
fun eraseText(characterCount: Int)
fun swipe(fromX: Int, fromY: Int, toX: Int, toY: Int, durationMillis: Long)
fun pressKey(key: String)
fun longPress(x: Int, y: Int)
@@ -402,6 +403,16 @@ class StubDriverBackend(
runAdb(listOf("shell", "input", "text", escapeForAdbInputText(text)))
}
@Volatile var lastEraseCharacterCount: Int? = null
private set
override fun eraseText(characterCount: Int) {
lastEraseCharacterCount = characterCount
repeat(characterCount) {
runAdb(listOf("shell", "input", "keyevent", "KEYCODE_DEL"))
}
}
@Volatile var lastSwipe: SwipeRecord? = null
private set
@Volatile var lastKey: String? = null
@@ -531,6 +542,8 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend {
override fun inputText(text: String) = driver.inputText(text)
override fun eraseText(characterCount: Int) = driver.eraseText(characterCount)
override fun swipe(fromX: Int, fromY: Int, toX: Int, toY: Int, durationMillis: Long) =
driver.swipe(maestro.Point(fromX, fromY), maestro.Point(toX, toY), maxOf(durationMillis, 250L))
@@ -732,6 +745,8 @@ class IosDriverBackend(private val udid: String) : DriverBackend {
override fun inputText(text: String) = withReconnect { driver.inputText(text) }
override fun eraseText(characterCount: Int) = withReconnect { driver.eraseText(characterCount) }
override fun swipe(fromX: Int, fromY: Int, toX: Int, toY: Int, durationMillis: Long) = withReconnect {
driver.swipe(maestro.Point(fromX, fromY), maestro.Point(toX, toY), maxOf(durationMillis, 250L))
}
@@ -4,6 +4,7 @@ import com.google.protobuf.ByteString
import dev.sanderling.driver.v1.DriverGrpc
import dev.sanderling.driver.v1.Duration
import dev.sanderling.driver.v1.Empty
import dev.sanderling.driver.v1.EraseTextRequest
import dev.sanderling.driver.v1.HealthStatus
import dev.sanderling.driver.v1.HierarchyJSON
import dev.sanderling.driver.v1.Image
@@ -74,6 +75,13 @@ class DriverService(
}
}
override fun eraseText(request: EraseTextRequest, responseObserver: StreamObserver<Empty>) {
runRpc(responseObserver) {
backend.eraseText(request.characterCount)
Empty.getDefaultInstance()
}
}
override fun swipe(request: SwipeRequest, responseObserver: StreamObserver<Empty>) {
runRpc(responseObserver) {
val from = request.from
@@ -3,6 +3,7 @@ package dev.sanderling.sidecar
import dev.sanderling.driver.v1.DriverGrpc
import dev.sanderling.driver.v1.Duration
import dev.sanderling.driver.v1.Empty
import dev.sanderling.driver.v1.EraseTextRequest
import dev.sanderling.driver.v1.LaunchRequest
import dev.sanderling.driver.v1.Point
import dev.sanderling.driver.v1.PressKeyRequest
@@ -82,6 +83,14 @@ class DriverServiceTest {
assertEquals("hello world", backend.lastInputText)
}
@Test fun eraseTextForwardsCharacterCount() {
val backend = StubDriverBackend("android")
val client = newClient(backend)
client.eraseText(EraseTextRequest.newBuilder().setCharacterCount(11).build())
assertEquals(11, backend.lastEraseCharacterCount)
}
@Test fun screenshotReturnsBackendBytes() {
val backend = object : DriverBackend by StubDriverBackend("android") {
override fun screenshot(): Triple<ByteArray, Int, Int> = Triple(byteArrayOf(1, 2, 3), 1080, 2340)
@@ -15,6 +15,18 @@ class InputTextTest {
assertEquals(listOf(listOf("shell", "input", "text", "Emergency%sFund")), commands)
}
@Test fun eraseTextSendsOneDeleteKeyPerCharacter() {
val commands = mutableListOf<List<String>>()
val backend = StubDriverBackend("android") { commands.add(it) }
backend.eraseText(3)
assertEquals(
List(3) { listOf("shell", "input", "keyevent", "KEYCODE_DEL") },
commands,
)
}
@Test fun escapeForAdbInputTextSubstitutesSpaces() {
assertEquals("hello%sworld", StubDriverBackend.escapeForAdbInputText("hello world"))
}
@@ -60,6 +60,7 @@ class SnapshotHandlerTest {
override fun tap(x: Int, y: Int) {}
override fun tapSelector(selector: String) {}
override fun inputText(text: String) {}
override fun eraseText(characterCount: Int) {}
override fun swipe(fromX: Int, fromY: Int, toX: Int, toY: Int, durationMillis: Long) {}
override fun pressKey(key: String) {}
override fun longPress(x: Int, y: Int) {}