-
Notifications
You must be signed in to change notification settings - Fork 392
Launch app through xctest request instead of simctl #2374 #2421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Launch app through xctest request instead of simctl #2374 #2421
Conversation
* main: bump default version to 16 + include ios 18 feat: make statusBar visible from springboard (mobile-dev-inc#2419) use device-model and device-os fields for ios (mobile-dev-inc#2418) Reuse xctest (mobile-dev-inc#2413) added default analytics (mobile-dev-inc#2417) Android WebView devtoools fixes (mobile-dev-inc#2414) Updating flying fox on the iOS driver (mobile-dev-inc#2415) use os-provided temp dir instead of bash variable (mobile-dev-inc#2412) noninteractive ask mode for chatbot (mobile-dev-inc#2409) # Conflicts: # maestro-ios-driver/src/main/resources/maestro-driver-ios.zip # maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip
@@ -0,0 +1,5 @@ | |||
struct LaunchAppRequest: Codable { | |||
let bundleId: String | |||
let arguments: [String]? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not passed as map here:
data class LaunchAppRequest(val appId: String, val launchArguments: Map<String, Any>, val maestroSessionId: UUID?)
How is this converted to array? Also not sure why we need environment here.
We just need support launch arguments with all type of values mentioned in docs here:
https://docs.maestro.dev/api-reference/commands/launchapp#sending-launch-arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d recommend rebasing this branch onto main, or cherry-picking your changes on top of the latest main that way, you’ll be working with the most up-to-date code and can avoid running into major merge conflict.
maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/LaunchAppHandler.swift
Show resolved
Hide resolved
@@ -90,15 +90,7 @@ class SimctlIOSDevice( | |||
launchArguments: Map<String, Any>, | |||
maestroSessionId: UUID?, | |||
): Result<Unit, Throwable> { | |||
return runCatching { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be removed. We could still be using this somewhere. Some usecases I see is during testing and on cloud.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you plan to test these changes? It would be helpful to include an E2E test that we can run locally.
You can create a new directory under e2e for this. The tests should cover the following scenarios:
- App launch works on real iOS devices
- App launch works on simulators
- App launch arguments work correctly for all of these types: String, Boolean, Double, and Integer
* main: fix: deserialization breaking for devicectl response (mobile-dev-inc#2458) fix: don't use prebuilt for cli on iOS (mobile-dev-inc#2457) [MA 3235] --reinstall-driver flag getting ignored (mobile-dev-inc#2455) fix: infinite loop on maestro studio (mobile-dev-inc#2450) fix: studio mode should not foreground running app (mobile-dev-inc#2449) fix: transport type can be null (mobile-dev-inc#2447) [MA 3212] Building iOS driver on host's machines for cli distribution (mobile-dev-inc#2440) [MA-3186] Setting up iOS drivers for both real iOS device and simulators (mobile-dev-inc#2429) Prepare for release 1.40.0 (mobile-dev-inc#2430) adding metadata logs to AI commands (mobile-dev-inc#2428) print compact hierarchy as a csv (mobile-dev-inc#2427) maestro: interfacing AIEnginePrediction - making AI commands work on copilot(mobile-dev-inc#2425) # Conflicts: # maestro-ios-driver/src/main/kotlin/device/SimctlIOSDevice.kt # maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt # maestro-ios-driver/src/main/kotlin/xcuitest/api/LaunchAppRequest.kt # maestro-ios-driver/src/main/resources/maestro-driver-ios.zip # maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip # maestro-ios-xctest-runner/maestro-driver-ios.xcodeproj/project.pbxproj # maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/LaunchAppHandler.swift # maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Models/LaunchAppRequest.swift # maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/RouteHandlerFactory.swift # maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTestHTTPServer.swift # maestro-ios/src/main/java/ios/LocalIOSDevice.kt # maestro-ios/src/main/java/ios/xctest/XCTestIOSDevice.kt
* main: fix: sharding on iOS (mobile-dev-inc#2467) fix: only connected devices for iOS devices (mobile-dev-inc#2463) Prepare release 1.40.1 (mobile-dev-inc#2461) Fix: Handle empty descriptions in chrome dev tools response (mobile-dev-inc#2452) flag to skip interactive device selection by picking a device-index (mobile-dev-inc#2459)
* main: use config on e2e e2e demoapp config modes fix e2e prod test fix: hierarchy endpoint causing illegal argument in deep hierarchy screens (mobile-dev-inc#2481) [MA 3181] iOS device info frame creating incorrect hierarchy. (mobile-dev-inc#2473) Prepare for release 1.40.3 (mobile-dev-inc#2476) fix: hardware udid can be missing (mobile-dev-inc#2475) [MAE 167] breaking deserialization (mobile-dev-inc#2474) Preparing for release 1.40.2 (mobile-dev-inc#2470)
|
||
// Verify app is running and arguments are displayed | ||
let app = XCUIApplication(bundleIdentifier: launchRequest.appId) | ||
app.launch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unclear on how this test works — do you actually have the app (com.example.example) running locally?
The test is supposed to verify that the app launches, but it looks like we're launching it ourselves in the test, which feels off.
It also seems to test the request payload rather than verifying that the app was launched with the correct arguments.
What I was expecting from this test:
- The app is actually launched
- It’s launched with the correct arguments from the request
Right now, it feels more like we’re testing the request structure, not the actual implementation behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the test to assert that the app was successfully launched by sending a request to the local HTTP server and then checking if the app is in the foreground using:
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 5), "App did not launch")
This ensures that the app is actually running after the /launchApp handler is triggered.
Question:
Is there a way in the demo app (or any other app) to verify what launch arguments were passed in, so that we can assert on those as well? Currently, since the app doesn’t expose them, we can only validate the structure of the payload and that the app launched — but not whether the correct arguments were actually used inside the app.
) { | ||
deviceController.launch(id, launchArguments) | ||
launchArguments: Map<String, Any> | ||
): Result<Unit, Throwable> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this return type added here? This doesn't exist on the main
@@ -88,7 +88,7 @@ interface IOSDevice : AutoCloseable { | |||
fun launch( | |||
id: String, | |||
launchArguments: Map<String, Any>, | |||
) | |||
): Result<Unit, Throwable> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be changed. Not sure I understand intent behind this?
case let bool as Bool: | ||
return bool ? ["--\(key)"] : [] | ||
default: | ||
return ["--\(key)", "\(value)"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I got this flatmap logic?
Why do we want to check the bool arguments separately here. Can't we just pass arguments as is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we're using flatMap with a special case for Bool is because of how launchArguments work in XCUIApplication. It's not a dictionary but an array of strings, similar to command-line arguments.
For boolean flags:
If the value is true, we include it as a standalone flag like --featureFlagA.
If the value is false, we omit it entirely (which is the typical convention for CLI-like flags).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ummm, not sure I still got fully. For example, if we omit it and if someone is expecting false value on their app, will it give them nil or false?
I think right now your test also doesn't seem to cover this case. If you can ensure we have all the types covered in the test that would help here.
} | ||
} | ||
], | ||
"version" : 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test plan got added while i was testing, will remove this commit
@@ -1111,6 +1139,14 @@ | |||
version = 0.22.0; | |||
}; | |||
}; | |||
F4FD416D2DCA33440063BDB3 /* XCRemoteSwiftPackageReference "AnyCodable" */ = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What’s the intent behind this new library? Do we already have an internal API or utility that could be reused instead of adding a new dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent behind using AnyCodable is to support the flexible structure of the launchArguments payload that Maestro sends during the launchApp request. Specifically, Maestro sends this field as a Map<String, Any>, where the values can be of varying types (String, Bool, Int, etc.).
Since Swift’s Codable doesn’t natively support [String: Any], AnyCodable was introduced to cleanly decode this structure without writing extensive boilerplate or custom decoding logic.
I'm happy to switch to an internal utility if we already have something that supports decoding heterogeneous dictionaries like this — let me know if there’s a recommended alternative!
"--retryCount", "3"] | ||
|
||
let actual = flattenLaunchArguments(launchRequest.launchArguments) | ||
XCTAssertEqual(actual, expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still asserting from request right? How is this asserting what is passed on app com.example.example?
case let bool as Bool: | ||
return bool ? ["--\(key)"] : [] | ||
default: | ||
return ["--\(key)", "\(value)"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ummm, not sure I still got fully. For example, if we omit it and if someone is expecting false value on their app, will it give them nil or false?
I think right now your test also doesn't seem to cover this case. If you can ensure we have all the types covered in the test that would help here.
…tro into simctl_to_xctest * 'simctl_to_xctest' of github.com:Sanskargautam/FKiOSMaestro: Delete maestro-ios-xctest-runner/driver-verification-tests.xctestplan
…tro into simctl_to_xctest * 'simctl_to_xctest' of github.com:Sanskargautam/FKiOSMaestro: Update driver-verification-tests.xcscheme Update driver-verification-tests.xcscheme
* main: [MA 3281] Setting location by mocking all the possible providers (mobile-dev-inc#2498) feat (maestro): add originalDescription property to commands Use MaestroFlowParser to format error messages properly for studio (mobile-dev-inc#2399) Do not treat custom config file as a flow file (mobile-dev-inc#2495) Do no retry taps by default (mobile-dev-inc#2489) revert e2e changes [MA 3258] Introducing debug info on element not found exceptions (mobile-dev-inc#2487)
Proposed changes
This pull request transitions the app launch mechanism from simctl to using XCTest APIs. Leveraging XCTest provides a more robust and consistent way to launch apps, with improved compatibility across both simulators and real devices. This change ensures better integration within the XCTest testing lifecycle and enhances support for automation use cases on physical iOS devices.
copilot:summary
Testing
Issues fixed