diff --git a/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt b/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt index 05bfba937d..75f09403ad 100644 --- a/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt +++ b/maestro-cli/src/main/java/maestro/cli/session/MaestroSessionManager.kt @@ -298,7 +298,8 @@ object MaestroSessionManager { logger = IOSDriverLogger(LocalXCTestInstaller::class.java), deviceId = deviceId, host = defaultXctestHost, - defaultPort = driverHostPort ?: defaultXcTestPort + defaultPort = driverHostPort ?: defaultXcTestPort, + enableXCTestOutputFileLogging = true, ) val xcTestDriverClient = XCTestDriverClient( diff --git a/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt b/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt index 9722c7c478..0bb14c32b9 100644 --- a/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt +++ b/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt @@ -22,8 +22,8 @@ class LocalXCTestInstaller( private val logger: Logger, private val deviceId: String, private val host: String = "[::1]", - private val enableXCTestOutputFileLogging: Boolean = false, - defaultPort: Int? = null + private val enableXCTestOutputFileLogging: Boolean, + defaultPort: Int, ) : XCTestInstaller { // Set this flag to allow using a test runner started from Xcode // When this flag is set, maestro will not install, run, stop or remove the xctest runner. @@ -107,9 +107,16 @@ class LocalXCTestInstaller( private fun ensureOpen(): Boolean { return MaestroTimer.retryUntilTrue(10_000, 200) { try { - XCRunnerCLIUtils.isAppAlive(UI_TEST_RUNNER_APP_BUNDLE_ID, deviceId) && - xcTestDriverStatusCheck().use { it.isSuccessful } + val appAlive = XCRunnerCLIUtils.isAppAlive(UI_TEST_RUNNER_APP_BUNDLE_ID, deviceId) + logger.info("[Start] Perform XCUITest runner status check on $deviceId, appAlive: $appAlive") + appAlive && + xcTestDriverStatusCheck().use { + logger.info("[Done] Perform XCUITest runner status check on $deviceId") + + it.isSuccessful + } } catch (ignore: IOException) { + logger.info("[Failed] Perform XCUITest runner status check on $deviceId, error: $ignore") false } } @@ -168,10 +175,10 @@ class LocalXCTestInstaller( logger.info("[Start] Running XcUITest with xcode build command") xcTestProcess = XCRunnerCLIUtils.runXcTestWithoutBuild( - deviceId, - xctestRunFile.absolutePath, - port, - enableXCTestOutputFileLogging, + deviceId = deviceId, + xcTestRunFilePath = xctestRunFile.absolutePath, + port = port, + enableXCTestOutputFileLogging = enableXCTestOutputFileLogging, ) logger.info("[Done] Running XcUITest with xcode build command") } 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 31ac2e88bf..b8d2c2221b 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 6a3bb6ed07..379a30e6ba 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-ios.xcodeproj/project.pbxproj b/maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.pbxproj index 6058e2415a..8096a1a12d 100644 --- a/maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.pbxproj +++ b/maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 9494CBD12982F719009C987C /* ScreenshotHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9494CBD02982F719009C987C /* ScreenshotHandler.swift */; }; 949535FC299FD67E00FD0159 /* XCTestHTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949535FB299FD67E00FD0159 /* XCTestHTTPServer.swift */; }; 94A90DDE298AE72A006EB769 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A90DDD298AE72A006EB769 /* XCUIElement+Extensions.swift */; }; + 9811C9092C49751D00DDACA0 /* ScreenSizeHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9811C9082C49751D00DDACA0 /* ScreenSizeHelper.swift */; }; F328D3E62A2A98E7000546D3 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F328D3E52A2A98E7000546D3 /* StringExtensions.swift */; }; /* End PBXBuildFile section */ @@ -129,6 +130,7 @@ 9494CBD02982F719009C987C /* ScreenshotHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotHandler.swift; sourceTree = ""; }; 949535FB299FD67E00FD0159 /* XCTestHTTPServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestHTTPServer.swift; sourceTree = ""; }; 94A90DDD298AE72A006EB769 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = ""; }; + 9811C9082C49751D00DDACA0 /* ScreenSizeHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSizeHelper.swift; sourceTree = ""; }; F328D3E52A2A98E7000546D3 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -280,6 +282,7 @@ 522785802A54410D008DBC0A /* AppError.swift */, 941AE8EE2A77D4B20097C02A /* TimeoutHelper.swift */, 9468FA7C2AA741F100254AA3 /* TextInputHelper.swift */, + 9811C9082C49751D00DDACA0 /* ScreenSizeHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -438,6 +441,7 @@ 61C0AFF129C8C01F005D1FC5 /* DeviceInfoHandler.swift in Sources */, 943A9088293F5EAA00C85136 /* RunningAppRouteHandler.swift in Sources */, 613E87D7299A64BD00FF8551 /* PointerEventPath.swift in Sources */, + 9811C9092C49751D00DDACA0 /* ScreenSizeHelper.swift in Sources */, 9468FA7D2AA741F100254AA3 /* TextInputHelper.swift in Sources */, 61C0AFF729D34FEA005D1FC5 /* EventTarget.swift in Sources */, 612DE414298426EF003C2BE0 /* EventRecord.swift in Sources */, diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/DeviceInfoHandler.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/DeviceInfoHandler.swift index b51a3dda5b..7bd5cc8f40 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/DeviceInfoHandler.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/DeviceInfoHandler.swift @@ -17,11 +17,12 @@ struct DeviceInfoHandler: HTTPHandler { let springboardApp = XCUIApplication(bundleIdentifier: springboardBundleId) let screenSize = springboardApp.frame.size + let (width, height) = ScreenSizeHelper.actualScreenSize() let deviceInfo = DeviceInfoResponse( - widthPoints: Int(screenSize.width), - heightPoints: Int(screenSize.height), - widthPixels: Int(screenSize.width * UIScreen.main.scale), - heightPixels: Int(screenSize.height * UIScreen.main.scale) + widthPoints: Int(width), + heightPoints: Int(height), + widthPixels: Int(CGFloat(width) * UIScreen.main.scale), + heightPixels: Int(CGFloat(height) * UIScreen.main.scale) ) let responseBody = try JSONEncoder().encode(deviceInfo) diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandler.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandler.swift index 19a426aab9..8aa37ade05 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandler.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/SwipeRouteHandler.swift @@ -27,8 +27,7 @@ struct SwipeRouteHandler: HTTPHandler { } func swipePrivateAPI(start: CGPoint, end: CGPoint, duration: Double) async throws { - - logger.info("Swiping from \(start.debugDescription) to \(end.debugDescription) with \(duration) duration") + logger.info("Swipe (v1) from \(start.debugDescription) to \(end.debugDescription) with \(duration) duration") let eventRecord = EventRecord(orientation: .portrait) _ = eventRecord.addSwipeEvent(start: start, end: end, duration: duration) 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 57cbc1f403..464746ad2c 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 @@ -23,7 +23,19 @@ struct SwipeRouteHandlerV2: HTTPHandler { } func swipePrivateAPI(_ request: SwipeRequest) async throws { - let description = "Swipe from \(request.start) to \(request.end) with \(request.duration) duration" + let (width, height) = ScreenSizeHelper.physicalScreenSize() + let startPoint = ScreenSizeHelper.orientationAwarePoint( + width: width, + height: height, + point: request.start + ) + let endPoint = ScreenSizeHelper.orientationAwarePoint( + width: width, + height: height, + point: request.end + ) + + let description = "Swipe (v2) from \(request.start) to \(request.end) with \(request.duration) duration" logger.info("\(description)") let runningAppId = RunningApp.getForegroundAppId(request.appIds ?? []) @@ -31,9 +43,10 @@ struct SwipeRouteHandlerV2: HTTPHandler { try await eventTarget.dispatchEvent(description: description) { EventRecord(orientation: .portrait) .addSwipeEvent( - start: request.start, - end: request.end, - duration: request.duration) + start: startPoint, + end: endPoint, + duration: request.duration + ) } } } diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/TouchRouteHandler.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/TouchRouteHandler.swift index c5d002837a..eba40eb25c 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/TouchRouteHandler.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/TouchRouteHandler.swift @@ -11,21 +11,29 @@ struct TouchRouteHandler: HTTPHandler { func handleRequest(_ request: FlyingFox.HTTPRequest) async throws -> FlyingFox.HTTPResponse { let decoder = JSONDecoder() - + guard let requestBody = try? decoder.decode(TouchRequest.self, from: request.body) else { return AppError(type: .precondition, message: "incorrect request body provided for tap route").httpResponse } + let (width, height) = ScreenSizeHelper.physicalScreenSize() + let point = ScreenSizeHelper.orientationAwarePoint( + width: width, + height: height, + point: CGPoint(x: CGFloat(requestBody.x), y: CGFloat(requestBody.y)) + ) + let (x, y) = (point.x, point.y) + if requestBody.duration != nil { - logger.info("Long pressing \(requestBody.x), \(requestBody.y) for \(requestBody.duration!)s") + logger.info("Long pressing \(x), \(y) for \(requestBody.duration!)s") } else { - logger.info("Tapping \(requestBody.x), \(requestBody.y)") + logger.info("Tapping \(x), \(y)") } do { let eventRecord = EventRecord(orientation: .portrait) _ = eventRecord.addPointerTouchEvent( - at: CGPoint(x: CGFloat(requestBody.x), y: CGFloat(requestBody.y)), + at: CGPoint(x: CGFloat(x), y: CGFloat(y)), touchUpAfter: requestBody.duration ) let start = Date() diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/ScreenSizeHelper.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/ScreenSizeHelper.swift new file mode 100644 index 0000000000..569acfc48d --- /dev/null +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/ScreenSizeHelper.swift @@ -0,0 +1,36 @@ +import XCTest + +struct ScreenSizeHelper { + static func physicalScreenSize() -> (Float, Float) { + let springboardBundleId = "com.apple.springboard" + let springboardApp = XCUIApplication(bundleIdentifier: springboardBundleId) + let screenSize = springboardApp.frame.size + return (Float(screenSize.width), Float(screenSize.height)) + } + + /// Takes device orientation into account. + static func actualScreenSize() -> (Float, Float) { + let orientation = XCUIDevice.shared.orientation + + let (width, height) = physicalScreenSize() + let (actualWidth, actualHeight) = switch (orientation) { + case .portrait, .portraitUpsideDown: (width, height) + case .landscapeLeft, .landscapeRight: (height, width) + case .faceDown, .faceUp, .unknown: fatalError("Unsupported orientation: \(orientation)") + @unknown default: fatalError("Unsupported orientation: \(orientation)") + } + + return (actualWidth, actualHeight) + } + + static func orientationAwarePoint(width: Float, height: Float, point: CGPoint) -> CGPoint { + let orientation = XCUIDevice.shared.orientation + + return switch (orientation) { + case .portrait: point + case .landscapeLeft: CGPoint(x: CGFloat(width) - point.y, y: CGFloat(point.x)) + case .landscapeRight: CGPoint(x: CGFloat(point.y), y: CGFloat(height) - point.x) + default: fatalError("Not implemented yet") + } + } +} diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/maestro_driver_iosUITests.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/maestro_driver_iosUITests.swift index 095a768354..be19887994 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/maestro_driver_iosUITests.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/maestro_driver_iosUITests.swift @@ -1,5 +1,6 @@ import XCTest import FlyingFox +import os class maestro_driver_iosUITests: XCTestCase { private static var swizzledOutIdle = false