diff --git a/maestro-ios-driver/src/main/resources/maestro-driver-ios-config.xctestrun b/maestro-ios-driver/src/main/resources/maestro-driver-ios-config.xctestrun
index 73724610a4..c5e7c04356 100644
--- a/maestro-ios-driver/src/main/resources/maestro-driver-ios-config.xctestrun
+++ b/maestro-ios-driver/src/main/resources/maestro-driver-ios-config.xctestrun
@@ -20,6 +20,8 @@
maestro-driver-iosUITests
BlueprintProviderName
maestro-driver-ios
+ BlueprintProviderRelativePath
+ maestro-driver-ios.xcodeproj
BundleIdentifiersForCrashReportEmphasis
dev.mobile.maestro-driver-ios
@@ -39,6 +41,8 @@
1
EnvironmentVariables
+ APP_DISTRIBUTOR_ID_OVERRIDE
+ com.apple.AppStore
OS_ACTIVITY_DT_MODE
YES
SQLITE_ENABLE_THREAD_ASSERTIONS
@@ -48,6 +52,8 @@
IsXCTRunnerHostedTestBundle
+ PreferredScreenCaptureFormat
+ screenRecording
ProductModuleName
maestro_driver_iosUITests
RunOrder
@@ -85,6 +91,8 @@
UITargetAppEnvironmentVariables
+ APP_DISTRIBUTOR_ID_OVERRIDE
+ com.apple.AppStore
DYLD_FRAMEWORK_PATH
__TESTROOT__/Debug-iphonesimulator:__TESTROOT__/Debug-iphonesimulator/PackageFrameworks
DYLD_LIBRARY_PATH
diff --git a/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip b/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip
index 57efc0ff27..9e27dc6702 100644
Binary files a/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip and b/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip differ
diff --git a/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip b/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip
index 95d3f6f470..913a55f771 100644
Binary files a/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip and b/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip differ
diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandlerV2.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandlerV2.swift
index 464746ad2c..a952615bc5 100644
--- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandlerV2.swift
+++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandlerV2.swift
@@ -14,6 +14,10 @@ struct SwipeRouteHandlerV2: HTTPHandler {
return AppError(type: .precondition, message: "incorrect request body provided for swipe request v2").httpResponse
}
+ if (requestBody.duration < 0) {
+ return AppError(type: .precondition, message: "swipe duration can not be negative").httpResponse
+ }
+
do {
try await swipePrivateAPI(requestBody)
return HTTPResponse(statusCode: .ok)
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 1cc4b1f244..d0098b4fde 100644
--- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt
+++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt
@@ -110,7 +110,8 @@ data class ScrollUntilVisibleCommand(
val visibilityPercentageNormalized = (visibilityPercentage / 100).toDouble()
private fun String.speedToDuration(): String {
- return ((1000 * (100 - this.toLong()).toDouble() / 100).toLong() + 1).toString()
+ val duration = ((1000 * (100 - this.toLong()).toDouble() / 100).toLong() + 1)
+ return if (duration < 0) { DEFAULT_SCROLL_DURATION } else duration.toString()
}
private fun String.timeoutToMillis(): String {
diff --git a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt
index 9c95ca4d68..972f1c2f81 100644
--- a/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt
+++ b/maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt
@@ -3135,6 +3135,42 @@ class IntegrationTest {
)
}
+ @Test
+ fun `Case 118 - Scroll until view is visible - no negative values allowed`() {
+ // Given
+ val commands = readCommands("118_scroll_until_visible_negative")
+ val expectedDuration = "40"
+ val expectedTimeout = "20000"
+ val info = driver { }.deviceInfo()
+
+ val elementBounds = Bounds(0, 0 + info.heightGrid, 100, 100 + info.heightGrid)
+ val driver = driver {
+ element {
+ id = "maestro"
+ bounds = elementBounds
+ }
+ }
+
+ // When
+ var scrollDuration = "0"
+ var timeout = "0"
+ Maestro(driver).use {
+ orchestra(it, onCommandMetadataUpdate = { _, metaData ->
+ scrollDuration = metaData.evaluatedCommand?.scrollUntilVisible?.scrollDuration.toString()
+ timeout = metaData.evaluatedCommand?.scrollUntilVisible?.timeout.toString()
+ }).runFlow(commands)
+ }
+
+ // Then
+ assertThat(scrollDuration).isEqualTo(expectedDuration)
+ assertThat(timeout).isEqualTo(expectedTimeout)
+ driver.assertEvents(
+ listOf(
+ Event.SwipeElementWithDirection(Point(270, 480), SwipeDirection.UP, expectedDuration.toLong()),
+ )
+ )
+ }
+
private fun orchestra(
maestro: Maestro,
) = Orchestra(
diff --git a/maestro-test/src/test/resources/118_scroll_until_visible_negative.yaml b/maestro-test/src/test/resources/118_scroll_until_visible_negative.yaml
new file mode 100644
index 0000000000..44ee4468e9
--- /dev/null
+++ b/maestro-test/src/test/resources/118_scroll_until_visible_negative.yaml
@@ -0,0 +1,8 @@
+appId: com.example.app
+---
+- scrollUntilVisible:
+ element:
+ id: "maestro"
+ direction: DOWN
+ speed: 110
+ timeout: -200
\ No newline at end of file