feat: Add WatchOS support - #1209
Conversation
|
@Dan-Maor @KazuCocoa Could you please try it with a real device? I haven't wired watch into the node package fully yet, this is just a POC to gather the feedback and to test if it works with real devices (I did my testing on Simulator) |
|
Wow, incredible work! 🎉
|
|
I have an unused Apple watch lying around somewhere, will try to test it on Sunday. |
|
I don't have Apple Watch, although will review the code tomorrow or the next |
|
I've experimented on my Apple Watch and managed to get it to run, but there are two blocking issues:
Restarting the watch didn't help either, seems to me like the network connection handler doesn't fire. Like mentioned in the description - Apple doesn't really like the idea of using low level networking on Apple Watch for anything other than some specific scenarios listed here: https://developer.apple.com/documentation/technotes/tn3135-low-level-networking-on-watchos and emphasize the difference between a simulator and a real Apple Watch: Given that the code is functional on simulators it may be still worth merging it while emphasizing that only simulators are supported for Apple Watch testing. |
…ks, not Resources WebDriverAgentRunner_watchOS's copy-files build phase used dstSubfolderSpec = 7 (Resources) instead of 10 (Frameworks), so the framework ended up in the wrong location inside the .xctest bundle. On a real device this made the runner crash on startup unable to locate the framework (works on Simulator, which is more lenient about bundle layout). Found by @Dan-Maor testing on real hardware, see #1209 (comment) Verified locally: framework now lands under PlugIns/WebDriverAgentRunner_watchOS.xctest/Frameworks/ instead of Resources/, matching the existing iOS/tvOS "Copy frameworks" phases.
Thanks for checking it @Dan-Maor |
## Summary Adds a working watchOS port of WebDriverAgent (#4829): `WebDriverAgentLib_watchOS` and `WebDriverAgentRunner_watchOS` build, run, and serve the real WebDriver HTTP API against a watchOS Simulator target — verified end to end, not just compiled. - `find`, `click`, `screenshot`, `source`, session lifecycle, app lifecycle, device info/location, and all locator strategies (accessibility id, class name, class chain, xpath, predicate string, link text) work correctly over HTTP against the real server, the same way Appium would drive it. - `type` has a known, documented limitation: the on-screen keyboard opens and the field gets focus, but neither low-level key synthesis nor the public `-typeText:` API delivers keystrokes into it in this environment. Covered by a test wrapped in `XCTExpectFailure` so it starts failing loudly (as a signal to revisit) the moment it starts working, instead of silently staying stale. - CI builds and statically analyzes the new watchOS targets, mirroring the existing tvOS matrix. ## Why watchOS needs its own port, not just new `#if` branches - **No BSD sockets.** watchOS forbids them entirely, so `RoutingHTTPServer`/`CocoaHTTPServer`/`CocoaAsyncSocket` (all GCDAsyncSocket-based) can't be built there. `FBTCPSocket` gained a full `TARGET_OS_WATCH` implementation on `Network.framework`'s C API (`nw_listener_t`/`nw_connection_t`) instead. - **No RoutingHTTPServer.** A new minimal HTTP/1.1 server (`FBWatchHTTPServer`, plus `RouteRequest`/`RouteResponse` stand-ins) sits on top of the new socket layer, reproducing just enough of RoutingHTTPServer's surface that `FBWebServer.m` only needs a single `#if TARGET_OS_WATCH` swap of which server class it drives — no changes to route registration, dispatch, or exception handling. - **A much smaller UI-automation surface.** Gesture synthesis (swipe/pinch/rotate/tap-coordinates/etc.), orientation/rotation, pasteboard, battery info, and MJPEG streaming are either not meaningfully applicable on watchOS or not verified to work there, so those routes are excluded from the watchOS build rather than left to fail unpredictably at runtime. ## Test coverage `IntegrationTests_watchOS` was rewritten from a single leftover spike file into 10 split test files (32 test methods), organized by feature area like `IntegrationTests_1/2/3`/`IntegrationTests_tvOS` — but unlike those, which call WDA's `fb_*` categories in-process, this suite runs as its own XCTest process and drives the real `WebDriverAgentRunner_watchOS` server purely over HTTP, matching how a real client actually exercises WDA. Deliberately out of scope for this suite (with rationale in code comments): gesture synthesis, orientation, pasteboard, battery, keyboard input, picker wheel (none registered on watchOS at all); video recording, VoiceOver toggling, Touch ID, Siri, homescreen/lock/pressButton (registered, but either unverified on the Simulator or too disruptive for a repeatable, hermetic suite). ## CI Added `Generic_watchOS_Build`, `watchOS_Build`, `watchOS_Lib_Analyze`, and `watchOS_Runner_Analyze` to the build/analyze matrix in `wda-tests.yml`, at both MIN and MAX Xcode — the same shape as the existing tvOS entries. `Scripts/build.sh` gained matching `watch_lib`/`watch_runner`/`watch`/`watch_generic`/`watch_sim`/`watch_device` cases. No unit/integration test job was added yet — that's future work, see below. ## Real device status Per feedback from @Dan-Maor testing on real hardware (see [comment](#1209 (comment))): - A framework-embed bug (`WebDriverAgentLib_watchOS.framework` was copied into `Resources/` instead of `Frameworks/` inside the `.xctest` bundle) has been fixed — this was causing the runner to crash on launch on a real device. - With that fixed, the HTTP server still doesn't work on a real Apple Watch: connections never complete (`nw_connection_get_flow_id_on_nw_queue failed` repeating in the log). Per Apple's own guidance ([TN3135](https://developer.apple.com/documentation/technotes/tn3135-low-level-networking-on-watchos)), low-level networking via `Network.framework` is permitted on the Simulator but restricted to a narrow set of approved use cases on real watchOS hardware — general-purpose TCP servers aren't one of them. This is a platform restriction, not a WDA bug, and there is currently no known workaround. - **watchOS Simulator is the only supported target for now.** Real-device support is blocked on Apple's networking restrictions and is tracked as a follow-up, not solved in this PR. ## Known limitations / follow-ups (not in scope for this PR) - **Typing doesn't work** (see above) — needs further investigation, possibly lower-level HID key injection. - **Real hardware doesn't work** for the reasons above — Simulator only for now. - **No CI test job yet** for `IntegrationTests_watchOS` (build + static analysis only for now). - **No `appium-xcuitest-driver` changes** — this PR is WDA-side only; the driver still needs scheme selection, platform/device detection, and new capabilities to actually launch this from Appium (see the tvOS driver PR, appium/appium-xcuitest-driver#911, as a template).
KazuCocoa
left a comment
There was a problem hiding this comment.
lg. The title can address "simulator"
Adds a working watchOS port of WebDriverAgent (#4829): `WebDriverAgentLib_watchOS` and `WebDriverAgentRunner_watchOS` build, run, and serve the real WebDriver HTTP API against a watchOS Simulator target — verified end to end, not just compiled. - `find`, `click`, `screenshot`, `source`, session lifecycle, app lifecycle, device info/location, and all locator strategies (accessibility id, class name, class chain, xpath, predicate string, link text) work correctly over HTTP against the real server, the same way Appium would drive it. - `type` has a known, documented limitation: the on-screen keyboard opens and the field gets focus, but neither low-level key synthesis nor the public `-typeText:` API delivers keystrokes into it in this environment. Covered by a test wrapped in `XCTExpectFailure` so it starts failing loudly (as a signal to revisit) the moment it starts working, instead of silently staying stale. - CI builds and statically analyzes the new watchOS targets, mirroring the existing tvOS matrix. - **No BSD sockets.** watchOS forbids them entirely, so `RoutingHTTPServer`/`CocoaHTTPServer`/`CocoaAsyncSocket` (all GCDAsyncSocket-based) can't be built there. `FBTCPSocket` gained a full `TARGET_OS_WATCH` implementation on `Network.framework`'s C API (`nw_listener_t`/`nw_connection_t`) instead. - **No RoutingHTTPServer.** A new minimal HTTP/1.1 server (`FBWatchHTTPServer`, plus `RouteRequest`/`RouteResponse` stand-ins) sits on top of the new socket layer, reproducing just enough of RoutingHTTPServer's surface that `FBWebServer.m` only needs a single `#if TARGET_OS_WATCH` swap of which server class it drives — no changes to route registration, dispatch, or exception handling. - **A much smaller UI-automation surface.** Gesture synthesis (swipe/pinch/rotate/tap-coordinates/etc.), orientation/rotation, pasteboard, battery info, and MJPEG streaming are either not meaningfully applicable on watchOS or not verified to work there, so those routes are excluded from the watchOS build rather than left to fail unpredictably at runtime. `IntegrationTests_watchOS` was rewritten from a single leftover spike file into 10 split test files (32 test methods), organized by feature area like `IntegrationTests_1/2/3`/`IntegrationTests_tvOS` — but unlike those, which call WDA's `fb_*` categories in-process, this suite runs as its own XCTest process and drives the real `WebDriverAgentRunner_watchOS` server purely over HTTP, matching how a real client actually exercises WDA. Deliberately out of scope for this suite (with rationale in code comments): gesture synthesis, orientation, pasteboard, battery, keyboard input, picker wheel (none registered on watchOS at all); video recording, VoiceOver toggling, Touch ID, Siri, homescreen/lock/pressButton (registered, but either unverified on the Simulator or too disruptive for a repeatable, hermetic suite). Added `Generic_watchOS_Build`, `watchOS_Build`, `watchOS_Lib_Analyze`, and `watchOS_Runner_Analyze` to the build/analyze matrix in `wda-tests.yml`, at both MIN and MAX Xcode — the same shape as the existing tvOS entries. `Scripts/build.sh` gained matching `watch_lib`/`watch_runner`/`watch`/`watch_generic`/`watch_sim`/`watch_device` cases. No unit/integration test job was added yet — that's future work, see below. Per feedback from @Dan-Maor testing on real hardware (see [comment](#1209 (comment))): - A framework-embed bug (`WebDriverAgentLib_watchOS.framework` was copied into `Resources/` instead of `Frameworks/` inside the `.xctest` bundle) has been fixed — this was causing the runner to crash on launch on a real device. - With that fixed, the HTTP server still doesn't work on a real Apple Watch: connections never complete (`nw_connection_get_flow_id_on_nw_queue failed` repeating in the log). Per Apple's own guidance ([TN3135](https://developer.apple.com/documentation/technotes/tn3135-low-level-networking-on-watchos)), low-level networking via `Network.framework` is permitted on the Simulator but restricted to a narrow set of approved use cases on real watchOS hardware — general-purpose TCP servers aren't one of them. This is a platform restriction, not a WDA bug, and there is currently no known workaround. - **watchOS Simulator is the only supported target for now.** Real-device support is blocked on Apple's networking restrictions and is tracked as a follow-up, not solved in this PR. - **Typing doesn't work** (see above) — needs further investigation, possibly lower-level HID key injection. - **Real hardware doesn't work** for the reasons above — Simulator only for now. - **No CI test job yet** for `IntegrationTests_watchOS` (build + static analysis only for now). - **No `appium-xcuitest-driver` changes** — this PR is WDA-side only; the driver still needs scheme selection, platform/device detection, and new capabilities to actually launch this from Appium (see the tvOS driver PR, appium/appium-xcuitest-driver#911, as a template).
- FBTCPSocket.m: nw_listener_set_state_changed_handler's failure path stored the underlying CFError/NSError itself under NSLocalizedDescriptionKey, which must be string-valued - calling startupError.localizedDescription would crash with -[NSError length]: unrecognized selector. Use the underlying error's .localizedDescription for the string and keep the error itself under NSUnderlyingErrorKey. (@KazuCocoa) - WDAWatchHTTPClient.swift: doc comment referenced the now-deleted Scripts/watchos-http-integration-test.sh; point at the actual way to start the server instead. (@KazuCocoa)
## [16.3.0](v16.2.2...v16.3.0) (2026-08-18) ### Features * Add WatchOS support ([#1209](#1209)) ([90b443c](90b443c))
|
🎉 This PR is included in version 16.3.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
Adds a working watchOS port of WebDriverAgent (#4829):
WebDriverAgentLib_watchOSandWebDriverAgentRunner_watchOSbuild, run, and serve the real WebDriver HTTP API against a watchOS Simulator target — verified end to end, not just compiled.find,click,screenshot,source, session lifecycle, app lifecycle, device info/location, and all locator strategies (accessibility id, class name, class chain, xpath, predicate string, link text) work correctly over HTTP against the real server, the same way Appium would drive it.typehas a known, documented limitation: the on-screen keyboard opens and the field gets focus, but neither low-level key synthesis nor the public-typeText:API delivers keystrokes into it in this environment. Covered by a test wrapped inXCTExpectFailureso it starts failing loudly (as a signal to revisit) the moment it starts working, instead of silently staying stale.Why watchOS needs its own port, not just new
#ifbranchesRoutingHTTPServer/CocoaHTTPServer/CocoaAsyncSocket(all GCDAsyncSocket-based) can't be built there.FBTCPSocketgained a fullTARGET_OS_WATCHimplementation onNetwork.framework's C API (nw_listener_t/nw_connection_t) instead.FBWatchHTTPServer, plusRouteRequest/RouteResponsestand-ins) sits on top of the new socket layer, reproducing just enough of RoutingHTTPServer's surface thatFBWebServer.monly needs a single#if TARGET_OS_WATCHswap of which server class it drives — no changes to route registration, dispatch, or exception handling.Test coverage
IntegrationTests_watchOSwas rewritten from a single leftover spike file into 10 split test files (32 test methods), organized by feature area likeIntegrationTests_1/2/3/IntegrationTests_tvOS— but unlike those, which call WDA'sfb_*categories in-process, this suite runs as its own XCTest process and drives the realWebDriverAgentRunner_watchOSserver purely over HTTP, matching how a real client actually exercises WDA.Deliberately out of scope for this suite (with rationale in code comments): gesture synthesis, orientation, pasteboard, battery, keyboard input, picker wheel (none registered on watchOS at all); video recording, VoiceOver toggling, Touch ID, Siri, homescreen/lock/pressButton (registered, but either unverified on the Simulator or too disruptive for a repeatable, hermetic suite).
CI
Added
Generic_watchOS_Build,watchOS_Build,watchOS_Lib_Analyze, andwatchOS_Runner_Analyzeto the build/analyze matrix inwda-tests.yml, at both MIN and MAX Xcode — the same shape as the existing tvOS entries.Scripts/build.shgained matchingwatch_lib/watch_runner/watch/watch_generic/watch_sim/watch_devicecases. No unit/integration test job was added yet — that's future work, see below.Real device status
Per feedback from @Dan-Maor testing on real hardware (see comment):
WebDriverAgentLib_watchOS.frameworkwas copied intoResources/instead ofFrameworks/inside the.xctestbundle) has been fixed — this was causing the runner to crash on launch on a real device.nw_connection_get_flow_id_on_nw_queue failedrepeating in the log). Per Apple's own guidance (TN3135), low-level networking viaNetwork.frameworkis permitted on the Simulator but restricted to a narrow set of approved use cases on real watchOS hardware — general-purpose TCP servers aren't one of them. This is a platform restriction, not a WDA bug, and there is currently no known workaround.Known limitations / follow-ups (not in scope for this PR)
IntegrationTests_watchOS(build + static analysis only for now).appium-xcuitest-driverchanges — this PR is WDA-side only; the driver still needs scheme selection, platform/device detection, and new capabilities to actually launch this from Appium (see the tvOS driver PR, tvos support appium-xcuitest-driver#911, as a template).