From bc94eaf94f82cafb588af197090d32189f5aa32d Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Sun, 15 Mar 2026 07:22:32 -0700 Subject: [PATCH] fix: add appium: vendor prefix to iOS capabilities for Appium v3 (#810) * fix: add appium: vendor prefix to iOS capabilities for Appium v3 Appium v3 enforces the W3C WebDriver spec strictly, requiring non-standard capabilities to use vendor prefixes. The iOS provider was sending capabilities like `automationName`, `noReset`, `deviceName`, `platformVersion`, and `udid` without the required `appium:` prefix, causing session creation to fail with InvalidArgumentError. This change prefixes all non-standard capabilities with `appium:` while leaving standard W3C capabilities (`platformName`, `browserName`) unprefixed. Backwards-compatible with Appium v2, which accepts both formats. Fixes #629 * fix: extract build_ios_capabilities for testable production code path Addresses review feedback: removes unused `mut manager` warning and validates the actual capability-building logic instead of reconstructing JSON inline. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- cli/src/native/webdriver/appium.rs | 81 ++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/cli/src/native/webdriver/appium.rs b/cli/src/native/webdriver/appium.rs index a1e866e..3d85a5e 100644 --- a/cli/src/native/webdriver/appium.rs +++ b/cli/src/native/webdriver/appium.rs @@ -40,32 +40,45 @@ impl AppiumManager { }) } + pub fn build_ios_capabilities( + device_udid: Option<&str>, + device_name: Option<&str>, + platform_version: Option<&str>, + ) -> Value { + let mut caps = json!({ + "platformName": "iOS", + "appium:automationName": "XCUITest", + "browserName": "Safari", + "appium:noReset": true, + }); + + if let Some(name) = device_name { + caps["appium:deviceName"] = json!(name); + } else { + caps["appium:deviceName"] = json!("iPhone"); + } + + if let Some(ver) = platform_version { + caps["appium:platformVersion"] = json!(ver); + } + + if let Some(udid) = device_udid { + caps["appium:udid"] = json!(udid); + } + + caps + } + pub async fn create_ios_session( &mut self, device_name: Option<&str>, platform_version: Option<&str>, ) -> Result { - let mut caps = json!({ - "platformName": "iOS", - "automationName": "XCUITest", - "browserName": "Safari", - "noReset": true, - }); - - if let Some(name) = device_name { - caps["deviceName"] = json!(name); - } else { - caps["deviceName"] = json!("iPhone"); - } - - if let Some(ver) = platform_version { - caps["platformVersion"] = json!(ver); - } - - if let Some(ref udid) = self.device_udid { - caps["udid"] = json!(udid); - } - + let caps = Self::build_ios_capabilities( + self.device_udid.as_deref(), + device_name, + platform_version, + ); self.client.create_session(caps).await } @@ -198,4 +211,30 @@ mod tests { assert_eq!(APPIUM_DEFAULT_PORT, 4723); assert_eq!(APPIUM_STARTUP_TIMEOUT_SECS, 30); } + + #[test] + fn test_ios_capabilities_use_vendor_prefix() { + let caps = AppiumManager::build_ios_capabilities( + Some("TEST-UDID-123"), + Some("iPhone 16 Pro"), + Some("18.5"), + ); + + // W3C standard capabilities must NOT have vendor prefix + assert!(caps.get("platformName").is_some()); + assert!(caps.get("browserName").is_some()); + + // Non-standard capabilities MUST have appium: vendor prefix + assert!(caps.get("appium:automationName").is_some()); + assert!(caps.get("appium:noReset").is_some()); + assert!(caps.get("appium:deviceName").is_some()); + assert!(caps.get("appium:platformVersion").is_some()); + assert!(caps.get("appium:udid").is_some()); + + // Must NOT have unprefixed non-standard capabilities + assert!(caps.get("automationName").is_none()); + assert!(caps.get("noReset").is_none()); + assert!(caps.get("deviceName").is_none()); + assert!(caps.get("udid").is_none()); + } }