-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Problem Statement
The launchApp: arguments feature works correctly on iOS Simulators but silently fails on real iOS devices. This blocks critical E2E testing patterns that require passing data (particularly authentication tokens) to the app at launch time.
Current behavior:
- On iOS Simulators: Arguments passed via
launchApp: argumentsare accessible in the app viaProcessInfo.processInfo.arguments✅ - On real iOS devices: Arguments are silently ignored - the app does not receive them via
ProcessInfo.processInfo.arguments❌
Root cause: iOS Simulators support arguments via xcrun simctl launch, but real devices use WebDriverAgent (WDA) with XCUITest's launch mechanism, which doesn't propagate arguments to the target app's process in the same way.
This limitation forces tests requiring authenticated users to navigate full sign-in UI flows on real devices, making tests slower, flakier, and dependent on external auth providers.
Proposed Solution
Option A (Preferred): Make launchApp: arguments work consistently across simulators and real devices. Arguments should be accessible via ProcessInfo.processInfo.arguments on both platforms.
Option B: If Option A is not technically feasible due to iOS/WDA limitations, provide an alternative mechanism:
- Support setting
UserDefaultsvalues before app launch - Add a
launchApp: environmentoption for environment variables - Provide clear documentation of the limitation with recommended workarounds
Option C (Minimum): Emit a warning when launchApp: arguments is used on real iOS devices (non-simulator), alerting users that arguments may not work as expected.
Use Case
We uses maestro-runner for E2E UI testing with the following pattern:
- Create test user: Use
runScriptto call an E2E auth API and generate a test user with an auth token - Launch app with token: Pass the auth token via
launchApp: argumentsso the app can auto-authenticate - Run test flows: Execute UI tests (onboarding, features, etc.) without manually navigating sign-in screens
This pattern is essential for:
- Speed: Bypassing login UI saves 10-30 seconds per test
- Reliability: Avoids dependency on external auth providers (Google OAuth, email delivery)
- Isolation: Each test gets a fresh, authenticated user without state pollution
- Real device testing: Required for catching device-specific bugs (performance, networking, push notifications, biometrics)
App-side implementation (Swift):
// In didFinishLaunchingWithOptions:
if isE2EMode {
var token: String?
// Check process arguments (Appium/XCUITest style)
let arguments = ProcessInfo.processInfo.arguments
if let tokenIndex = arguments.firstIndex(of: "-E2E_CUSTOM_AUTH_TOKEN"),
arguments.count > tokenIndex + 1 {
token = arguments[tokenIndex + 1]
}
// Fallback to UserDefaults (Maestro style)
if token == nil {
token = UserDefaults.standard.string(forKey: "E2E_CUSTOM_AUTH_TOKEN")
}
if let token = token, !token.isEmpty {
appSetup.signInWithE2EToken(token: token)
}
}Example
appId: ai.stormlabs.treework.dev
env:
E2E_API_URL: https://our-e2e-auth-service.run.app/api/v1/e2e-auth
E2E_API_KEY: <api-key>
---
# Step 1: Create test user via API and get auth token
- runScript: ../../scripts/create-test-user.js
# Step 2: Launch app with auth token passed as argument
- launchApp:
appId: ai.stormlabs.treework.dev
clearState: true
arguments:
E2E_CUSTOM_AUTH_TOKEN: ${output.testUser.token}
# Step 3: App auto-authenticates and shows onboarding (authenticated state)
- assertVisible: "Your privacy is our priority."
# Step 4: Continue with test flow
- tapOn: "Continue"
# ... rest of testExpected behavior: The app receives E2E_CUSTOM_AUTH_TOKEN via ProcessInfo.processInfo.arguments and auto-authenticates the user, skipping the login screen.
Actual behavior on real device: Arguments are silently ignored, app shows login screen, test fails at assertVisible step.
Alternatives Considered
-
Deep links: Could pass token via URL scheme (e.g.,
myapp://e2e-auth?token=xyz)- Pros: Works on real devices
- Cons: Requires app code changes, less elegant, URL length limits, security concerns with tokens in URLs
-
Manual login flow in tests: Navigate sign-in UI in every test
- Pros: No workarounds needed
- Cons: Slow (adds 10-30s per test), flaky (depends on external services), harder to maintain
-
Pre-install app with baked-in credentials: Build test-specific IPA with hardcoded tokens
- Pros: No runtime argument passing needed
- Cons: Security risk, requires separate builds, tokens expire, not scalable
-
Use only simulators: Skip real device testing entirely
- Pros:
launchApp: argumentsworks perfectly - Cons: Misses device-specific bugs, not acceptable for production testing
- Pros:
None of these alternatives are as clean or reliable as fixing launchApp: arguments on real devices.
Additional Context
Environment:
- maestro-runner: v1.0.4 (Go binary, WDA driver)
- Real device: iPhone (iOS 18.2.1, UDID: 00008030-0001353934D3402E)
- WDA: v11.1.4 (auto-downloaded by maestro-runner)
- macOS: Apple Silicon
- App: Kotlin Multiplatform (Compose Multiplatform) with SwiftUI shell
Command used:
maestro-runner \
--platform ios \
--device 00008030-0001353934D3402E \
--team-id Z5Q3WUX123 \
--app-file "path/to/app.ipa" \
--verbose \
test flows/onboarding/onboarding-user-profile-validation.yamlEvidence from test run:
✓ runScript (993ms) ← Test user created, token generated
launchApp (clearState) (20.5s) ← App launched but arguments NOT passed
✗ assertVisible (17.3s) ← Failed: user not authenticated
╰─ Element not visible: text='Your privacy is our priority.'