mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 20:17:09 +00:00
fix(sidecar): land the second double-tap sequentially on gesture collision
The overlapped second tap can hit the XCTest runner while the first
gesture is still executing ('only one gesture can be performed at a
time'), failing the step. The second tap now waits the first out and
retries once, keeping the tight gap on the happy path.
This commit is contained in:
1 parent
4f99af7d90
commit
6b1188c5a2
2 files changed
+60
-6
No files matched your search
@@ -179,6 +179,24 @@ private fun walkForStructuralHash(node: com.fasterxml.jackson.databind.JsonNode,
|
|||||||
out.append(')')
|
out.append(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overlappedDoubleTap fires the second tap while the first is still in
|
||||||
|
// flight, so the on-device gap stays tight on transports with high per-tap
|
||||||
|
// latency. The overlap can collide with the first tap still executing ("only
|
||||||
|
// one gesture can be performed at a time"); the second tap then waits the
|
||||||
|
// first out and lands sequentially instead of failing the step.
|
||||||
|
internal fun overlappedDoubleTap(tapAction: () -> Unit) {
|
||||||
|
val firstTap = java.util.concurrent.CompletableFuture.runAsync { tapAction() }
|
||||||
|
Thread.sleep(40)
|
||||||
|
try {
|
||||||
|
tapAction()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
runCatching { firstTap.join() }
|
||||||
|
tapAction()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
firstTap.join()
|
||||||
|
}
|
||||||
|
|
||||||
data class MetricsSample(
|
data class MetricsSample(
|
||||||
val cpuPercent: Double,
|
val cpuPercent: Double,
|
||||||
val heapBytes: Long,
|
val heapBytes: Long,
|
||||||
@@ -760,12 +778,7 @@ class IosDriverBackend(private val udid: String) : DriverBackend {
|
|||||||
// turnaround instead of a full transport round trip. Sequential requests
|
// turnaround instead of a full transport round trip. Sequential requests
|
||||||
// leave a gap wide enough for the app to navigate between the taps.
|
// leave a gap wide enough for the app to navigate between the taps.
|
||||||
override fun doubleTap(x: Int, y: Int): Unit = withReconnect(replay = false) {
|
override fun doubleTap(x: Int, y: Int): Unit = withReconnect(replay = false) {
|
||||||
val point = maestro.Point(x, y)
|
overlappedDoubleTap { driver.tap(maestro.Point(x, y)) }
|
||||||
val firstTap = java.util.concurrent.CompletableFuture.runAsync { driver.tap(point) }
|
|
||||||
Thread.sleep(40)
|
|
||||||
driver.tap(point)
|
|
||||||
firstTap.join()
|
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun longPress(x: Int, y: Int) = withReconnect(replay = false) { driver.longPress(maestro.Point(x, y)) }
|
override fun longPress(x: Int, y: Int) = withReconnect(replay = false) { driver.longPress(maestro.Point(x, y)) }
|
||||||
|
|||||||
@@ -102,6 +102,47 @@ class DriverServiceTest {
|
|||||||
assertEquals(io.grpc.Status.Code.UNAVAILABLE, thrown.status.code)
|
assertEquals(io.grpc.Status.Code.UNAVAILABLE, thrown.status.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The vendored iOS client throws failures that do not extend Exception;
|
||||||
|
// they must still map to a status error instead of killing the RPC as a
|
||||||
|
// channel-level Unknown the runner cannot classify.
|
||||||
|
@Test fun nonExceptionThrowableMapsToInternal() {
|
||||||
|
val backend = object : DriverBackend by StubDriverBackend("android") {
|
||||||
|
override fun inputText(text: String) {
|
||||||
|
throw Throwable("only one gesture can be performed at a time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val client = newClient(backend)
|
||||||
|
|
||||||
|
val thrown = kotlin.test.assertFailsWith<io.grpc.StatusRuntimeException> {
|
||||||
|
client.inputText(Text.newBuilder().setValue("hello").build())
|
||||||
|
}
|
||||||
|
assertEquals(io.grpc.Status.Code.INTERNAL, thrown.status.code)
|
||||||
|
assertTrue(thrown.status.description.orEmpty().contains("only one gesture"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun overlappedDoubleTapLandsTwoTaps() {
|
||||||
|
val invocations = java.util.concurrent.atomic.AtomicInteger(0)
|
||||||
|
overlappedDoubleTap { invocations.incrementAndGet() }
|
||||||
|
assertEquals(2, invocations.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun overlappedDoubleTapRetriesSequentiallyOnGestureCollision() {
|
||||||
|
val invocations = java.util.concurrent.atomic.AtomicInteger(0)
|
||||||
|
val inFlight = java.util.concurrent.atomic.AtomicBoolean(false)
|
||||||
|
// Mimic the XCTest runner: a tap issued while another gesture is
|
||||||
|
// still executing fails instead of queuing.
|
||||||
|
val tapAction = {
|
||||||
|
if (!inFlight.compareAndSet(false, true)) {
|
||||||
|
throw IllegalStateException("only one gesture can be performed at a time")
|
||||||
|
}
|
||||||
|
invocations.incrementAndGet()
|
||||||
|
Thread.sleep(150)
|
||||||
|
inFlight.set(false)
|
||||||
|
}
|
||||||
|
overlappedDoubleTap(tapAction)
|
||||||
|
assertEquals(2, invocations.get())
|
||||||
|
}
|
||||||
|
|
||||||
@Test fun doubleTapDefaultComposesTwoTaps() {
|
@Test fun doubleTapDefaultComposesTwoTaps() {
|
||||||
// Interface delegation would bind the default doubleTap to the
|
// Interface delegation would bind the default doubleTap to the
|
||||||
// delegate, bypassing the tap override, so implement the interface
|
// delegate, bypassing the tap override, so implement the interface
|
||||||
|
|||||||
Reference in new issue
Block a user