@fluentui-react-native/desktop-driver 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.cache/tsconfig.tsbuildinfo +1 -0
  2. package/AGENTS.md +68 -0
  3. package/CHANGELOG.md +16 -0
  4. package/PLAN.md +1394 -0
  5. package/README.md +223 -0
  6. package/SPEC.md +105 -0
  7. package/config/cli.cjs +8 -0
  8. package/jest.config.cjs +9 -0
  9. package/lib/agent/DesktopAgent.d.ts +56 -0
  10. package/lib/agent/DesktopAgent.d.ts.map +1 -0
  11. package/lib/agent/DesktopAgent.js +118 -0
  12. package/lib/agent/DesktopAgent.js.map +1 -0
  13. package/lib/agent/DesktopAgent.test.d.ts +2 -0
  14. package/lib/agent/DesktopAgent.test.d.ts.map +1 -0
  15. package/lib/agent/DesktopAgent.test.js +97 -0
  16. package/lib/agent/DesktopAgent.test.js.map +1 -0
  17. package/lib/agent/agent.contract.cjs +66 -0
  18. package/lib/agent/agent.contract.cjs.map +1 -0
  19. package/lib/agent/agent.contract.d.cts +2 -0
  20. package/lib/agent/agent.contract.d.cts.map +1 -0
  21. package/lib/agent/index.d.ts +3 -0
  22. package/lib/agent/index.d.ts.map +1 -0
  23. package/lib/agent/index.js +2 -0
  24. package/lib/agent/index.js.map +1 -0
  25. package/lib/artifacts/ArtifactManager.d.ts +13 -0
  26. package/lib/artifacts/ArtifactManager.d.ts.map +1 -0
  27. package/lib/artifacts/ArtifactManager.js +88 -0
  28. package/lib/artifacts/ArtifactManager.js.map +1 -0
  29. package/lib/artifacts/ArtifactManager.test.d.ts +2 -0
  30. package/lib/artifacts/ArtifactManager.test.d.ts.map +1 -0
  31. package/lib/artifacts/ArtifactManager.test.js +19 -0
  32. package/lib/artifacts/ArtifactManager.test.js.map +1 -0
  33. package/lib/artifacts/index.d.ts +2 -0
  34. package/lib/artifacts/index.d.ts.map +1 -0
  35. package/lib/artifacts/index.js +2 -0
  36. package/lib/artifacts/index.js.map +1 -0
  37. package/lib/authoring/index.d.ts +4 -0
  38. package/lib/authoring/index.d.ts.map +1 -0
  39. package/lib/authoring/index.js +2 -0
  40. package/lib/authoring/index.js.map +1 -0
  41. package/lib/authoring/results.d.ts +43 -0
  42. package/lib/authoring/results.d.ts.map +1 -0
  43. package/lib/authoring/results.js +2 -0
  44. package/lib/authoring/results.js.map +1 -0
  45. package/lib/authoring/storyTests.d.ts +81 -0
  46. package/lib/authoring/storyTests.d.ts.map +1 -0
  47. package/lib/authoring/storyTests.js +272 -0
  48. package/lib/authoring/storyTests.js.map +1 -0
  49. package/lib/authoring/storyTests.test.d.ts +2 -0
  50. package/lib/authoring/storyTests.test.d.ts.map +1 -0
  51. package/lib/authoring/storyTests.test.js +46 -0
  52. package/lib/authoring/storyTests.test.js.map +1 -0
  53. package/lib/cli/DesktopDriverCli.test.d.ts +2 -0
  54. package/lib/cli/DesktopDriverCli.test.d.ts.map +1 -0
  55. package/lib/cli/DesktopDriverCli.test.js +141 -0
  56. package/lib/cli/DesktopDriverCli.test.js.map +1 -0
  57. package/lib/cli/createDesktopDriverCommand.d.ts +8 -0
  58. package/lib/cli/createDesktopDriverCommand.d.ts.map +1 -0
  59. package/lib/cli/createDesktopDriverCommand.js +242 -0
  60. package/lib/cli/createDesktopDriverCommand.js.map +1 -0
  61. package/lib/cli/index.d.ts +3 -0
  62. package/lib/cli/index.d.ts.map +1 -0
  63. package/lib/cli/index.js +2 -0
  64. package/lib/cli/index.js.map +1 -0
  65. package/lib/client/DesktopDriverClient.d.ts +60 -0
  66. package/lib/client/DesktopDriverClient.d.ts.map +1 -0
  67. package/lib/client/DesktopDriverClient.js +155 -0
  68. package/lib/client/DesktopDriverClient.js.map +1 -0
  69. package/lib/client/index.d.ts +3 -0
  70. package/lib/client/index.d.ts.map +1 -0
  71. package/lib/client/index.js +2 -0
  72. package/lib/client/index.js.map +1 -0
  73. package/lib/host/types.d.ts +153 -0
  74. package/lib/host/types.d.ts.map +1 -0
  75. package/lib/host/types.js +2 -0
  76. package/lib/host/types.js.map +1 -0
  77. package/lib/hosts/fake/FakeDesktopHost.d.ts +72 -0
  78. package/lib/hosts/fake/FakeDesktopHost.d.ts.map +1 -0
  79. package/lib/hosts/fake/FakeDesktopHost.js +452 -0
  80. package/lib/hosts/fake/FakeDesktopHost.js.map +1 -0
  81. package/lib/index.d.ts +23 -0
  82. package/lib/index.d.ts.map +1 -0
  83. package/lib/index.js +11 -0
  84. package/lib/index.js.map +1 -0
  85. package/lib/protocol/actions.d.ts +11 -0
  86. package/lib/protocol/actions.d.ts.map +1 -0
  87. package/lib/protocol/actions.js +137 -0
  88. package/lib/protocol/actions.js.map +1 -0
  89. package/lib/protocol/capabilities.d.ts +11 -0
  90. package/lib/protocol/capabilities.d.ts.map +1 -0
  91. package/lib/protocol/capabilities.js +103 -0
  92. package/lib/protocol/capabilities.js.map +1 -0
  93. package/lib/protocol/constants.d.ts +2 -0
  94. package/lib/protocol/constants.d.ts.map +1 -0
  95. package/lib/protocol/constants.js +2 -0
  96. package/lib/protocol/constants.js.map +1 -0
  97. package/lib/protocol/errors.d.ts +37 -0
  98. package/lib/protocol/errors.d.ts.map +1 -0
  99. package/lib/protocol/errors.js +66 -0
  100. package/lib/protocol/errors.js.map +1 -0
  101. package/lib/protocol/timeouts.d.ts +2 -0
  102. package/lib/protocol/timeouts.d.ts.map +1 -0
  103. package/lib/protocol/timeouts.js +23 -0
  104. package/lib/protocol/timeouts.js.map +1 -0
  105. package/lib/protocol/types.d.ts +46 -0
  106. package/lib/protocol/types.d.ts.map +1 -0
  107. package/lib/protocol/types.js +2 -0
  108. package/lib/protocol/types.js.map +1 -0
  109. package/lib/runner/StoryTestRunner.d.ts +38 -0
  110. package/lib/runner/StoryTestRunner.d.ts.map +1 -0
  111. package/lib/runner/StoryTestRunner.js +497 -0
  112. package/lib/runner/StoryTestRunner.js.map +1 -0
  113. package/lib/runner/StoryTestRunner.test.d.ts +2 -0
  114. package/lib/runner/StoryTestRunner.test.d.ts.map +1 -0
  115. package/lib/runner/StoryTestRunner.test.js +349 -0
  116. package/lib/runner/StoryTestRunner.test.js.map +1 -0
  117. package/lib/runner/index.d.ts +3 -0
  118. package/lib/runner/index.d.ts.map +1 -0
  119. package/lib/runner/index.js +2 -0
  120. package/lib/runner/index.js.map +1 -0
  121. package/lib/server/SessionManager.d.ts +52 -0
  122. package/lib/server/SessionManager.d.ts.map +1 -0
  123. package/lib/server/SessionManager.js +208 -0
  124. package/lib/server/SessionManager.js.map +1 -0
  125. package/lib/server/TargetRegistry.d.ts +9 -0
  126. package/lib/server/TargetRegistry.d.ts.map +1 -0
  127. package/lib/server/TargetRegistry.js +24 -0
  128. package/lib/server/TargetRegistry.js.map +1 -0
  129. package/lib/server/createDesktopDriverServer.d.ts +19 -0
  130. package/lib/server/createDesktopDriverServer.d.ts.map +1 -0
  131. package/lib/server/createDesktopDriverServer.js +609 -0
  132. package/lib/server/createDesktopDriverServer.js.map +1 -0
  133. package/lib/server/desktopDriver.test.d.ts +2 -0
  134. package/lib/server/desktopDriver.test.d.ts.map +1 -0
  135. package/lib/server/desktopDriver.test.js +389 -0
  136. package/lib/server/desktopDriver.test.js.map +1 -0
  137. package/lib/server/index.d.ts +6 -0
  138. package/lib/server/index.d.ts.map +1 -0
  139. package/lib/server/index.js +4 -0
  140. package/lib/server/index.js.map +1 -0
  141. package/lib/server/webdriverio.contract.cjs +35 -0
  142. package/lib/server/webdriverio.contract.cjs.map +1 -0
  143. package/lib/server/webdriverio.contract.d.cts +2 -0
  144. package/lib/server/webdriverio.contract.d.cts.map +1 -0
  145. package/lib/server/webdriverio.test.d.ts +2 -0
  146. package/lib/server/webdriverio.test.d.ts.map +1 -0
  147. package/lib/server/webdriverio.test.js +40 -0
  148. package/lib/server/webdriverio.test.js.map +1 -0
  149. package/lib/storybook.d.ts +36 -0
  150. package/lib/storybook.d.ts.map +1 -0
  151. package/lib/storybook.js +2 -0
  152. package/lib/storybook.js.map +1 -0
  153. package/lib/testing/FakeStoryOrchestrator.d.ts +21 -0
  154. package/lib/testing/FakeStoryOrchestrator.d.ts.map +1 -0
  155. package/lib/testing/FakeStoryOrchestrator.js +44 -0
  156. package/lib/testing/FakeStoryOrchestrator.js.map +1 -0
  157. package/lib/testing/fakeStoryElements.d.ts +4 -0
  158. package/lib/testing/fakeStoryElements.d.ts.map +1 -0
  159. package/lib/testing/fakeStoryElements.js +143 -0
  160. package/lib/testing/fakeStoryElements.js.map +1 -0
  161. package/lib/testing/fakeStoryElements.test.d.ts +2 -0
  162. package/lib/testing/fakeStoryElements.test.d.ts.map +1 -0
  163. package/lib/testing/fakeStoryElements.test.js +38 -0
  164. package/lib/testing/fakeStoryElements.test.js.map +1 -0
  165. package/lib/testing/index.d.ts +7 -0
  166. package/lib/testing/index.d.ts.map +1 -0
  167. package/lib/testing/index.js +5 -0
  168. package/lib/testing/index.js.map +1 -0
  169. package/lib/testing/protocolHarness.d.ts +17 -0
  170. package/lib/testing/protocolHarness.d.ts.map +1 -0
  171. package/lib/testing/protocolHarness.js +46 -0
  172. package/lib/testing/protocolHarness.js.map +1 -0
  173. package/lib/wdio/DesktopWebdriver.d.ts +47 -0
  174. package/lib/wdio/DesktopWebdriver.d.ts.map +1 -0
  175. package/lib/wdio/DesktopWebdriver.js +76 -0
  176. package/lib/wdio/DesktopWebdriver.js.map +1 -0
  177. package/lib/wdio/DesktopWebdriver.test.d.ts +2 -0
  178. package/lib/wdio/DesktopWebdriver.test.d.ts.map +1 -0
  179. package/lib/wdio/DesktopWebdriver.test.js +74 -0
  180. package/lib/wdio/DesktopWebdriver.test.js.map +1 -0
  181. package/lib/wdio/index.d.ts +3 -0
  182. package/lib/wdio/index.d.ts.map +1 -0
  183. package/lib/wdio/index.js +2 -0
  184. package/lib/wdio/index.js.map +1 -0
  185. package/lib/wdio/wdioRunner.contract.cjs +64 -0
  186. package/lib/wdio/wdioRunner.contract.cjs.map +1 -0
  187. package/lib/wdio/wdioRunner.contract.d.cts +2 -0
  188. package/lib/wdio/wdioRunner.contract.d.cts.map +1 -0
  189. package/package.json +96 -0
  190. package/src/agent/DesktopAgent.test.ts +102 -0
  191. package/src/agent/DesktopAgent.ts +170 -0
  192. package/src/agent/agent.contract.cjs +32 -0
  193. package/src/agent/index.ts +8 -0
  194. package/src/artifacts/ArtifactManager.test.ts +20 -0
  195. package/src/artifacts/ArtifactManager.ts +105 -0
  196. package/src/artifacts/index.ts +1 -0
  197. package/src/authoring/index.ts +26 -0
  198. package/src/authoring/results.ts +47 -0
  199. package/src/authoring/storyTests.test.ts +58 -0
  200. package/src/authoring/storyTests.ts +345 -0
  201. package/src/cli/DesktopDriverCli.test.ts +150 -0
  202. package/src/cli/createDesktopDriverCommand.ts +290 -0
  203. package/src/cli/index.ts +2 -0
  204. package/src/client/DesktopDriverClient.ts +218 -0
  205. package/src/client/index.ts +2 -0
  206. package/src/host/types.ts +158 -0
  207. package/src/hosts/fake/FakeDesktopHost.ts +558 -0
  208. package/src/index.ts +101 -0
  209. package/src/protocol/actions.ts +163 -0
  210. package/src/protocol/capabilities.ts +129 -0
  211. package/src/protocol/constants.ts +1 -0
  212. package/src/protocol/errors.ts +76 -0
  213. package/src/protocol/timeouts.ts +26 -0
  214. package/src/protocol/types.ts +55 -0
  215. package/src/runner/StoryTestRunner.test.ts +362 -0
  216. package/src/runner/StoryTestRunner.ts +624 -0
  217. package/src/runner/index.ts +9 -0
  218. package/src/server/SessionManager.ts +302 -0
  219. package/src/server/TargetRegistry.ts +29 -0
  220. package/src/server/createDesktopDriverServer.ts +775 -0
  221. package/src/server/desktopDriver.test.ts +421 -0
  222. package/src/server/index.ts +5 -0
  223. package/src/server/webdriverio.contract.cjs +38 -0
  224. package/src/server/webdriverio.test.ts +41 -0
  225. package/src/storybook.ts +40 -0
  226. package/src/testing/FakeStoryOrchestrator.ts +55 -0
  227. package/src/testing/fakeStoryElements.test.ts +40 -0
  228. package/src/testing/fakeStoryElements.ts +167 -0
  229. package/src/testing/index.ts +6 -0
  230. package/src/testing/protocolHarness.ts +66 -0
  231. package/src/wdio/DesktopWebdriver.test.ts +76 -0
  232. package/src/wdio/DesktopWebdriver.ts +130 -0
  233. package/src/wdio/index.ts +2 -0
  234. package/src/wdio/wdioRunner.contract.cjs +30 -0
  235. package/tsconfig.json +15 -0
package/PLAN.md ADDED
@@ -0,0 +1,1394 @@
1
+ # Desktop Driver Plan
2
+
3
+ ## Status
4
+
5
+ Active architecture and implementation plan. This document starts from the
6
+ current checked-out tree and public platform/protocol documentation. It does
7
+ not depend on work from other branches.
8
+
9
+ The effort starts with the platform-neutral protocol, fake host, Storybook
10
+ orchestration, WebdriverIO authoring, and agent contracts. Windows and macOS
11
+ native code is an explicit later stage, so native transport, signing, and
12
+ distribution choices do not block the initial implementation.
13
+
14
+ ## Implementation status
15
+
16
+ Updated 2026-08-28.
17
+
18
+ ### Stage 1 Phase 1: Complete
19
+
20
+ - Added the public `@fluentui-react-native/desktop-driver` package and repository
21
+ project references.
22
+ - Implemented W3C response/error routing, capability negotiation,
23
+ server-registered targets, one-session-per-target reservation, sessions,
24
+ timeouts, windows, elements, actions, screenshots, source, and unsupported
25
+ browser-command handling.
26
+ - Implemented stable WebDriver element references, native liveness checks,
27
+ preview-scoped staleness, configurable click modes, input-state tracking,
28
+ action validation, element-origin resolution, per-session command queues,
29
+ a global input mutex, abortable host deadlines, drained runner cancellation,
30
+ and ownership-safe shutdown.
31
+ - Added the deterministic fake host, typed low-level client, raw HTTP contract
32
+ coverage, and a WebdriverIO remote-session contract with no Appium service.
33
+ - Portable fake-host coverage exercises element lookup, click, text entry,
34
+ actions, waits, screenshots, stale references, concurrent session rejection,
35
+ and shutdown during session creation.
36
+
37
+ ### Stage 1 Phase 2: Complete
38
+
39
+ - Added exact-platform Story Manifest generation with statically extracted,
40
+ validated `parameters.desktopDriver` plans, relocatable source paths,
41
+ platform digests, and portable-plan digests.
42
+ - Added a per-enlistment driver port and generated driver manifest containing
43
+ target identity, test-ID prefix, nonce, catalog digests, and service ports.
44
+ - Added authenticated, bridge-only runtime hello/readiness/error events with
45
+ request/run correlation, explicit hello challenges, same-story reset, and
46
+ preview generation.
47
+ - Added stable native app and story-root markers, native marker verification,
48
+ preview-only element invalidation, and a keyed per-run remount/error boundary.
49
+ - Added Storybook selection, reset, manifest, current-story, and args extension
50
+ commands to the WebDriver session.
51
+ - Added `storybook-desktop manifest`, `instance`, and `driver` flows. The
52
+ `driver` supervisor runs Metro plus separate Storybook and WebDriver listeners
53
+ while keeping both server protocols in one Node process.
54
+ - Added an embedded-server integration test and verified the live Windows
55
+ Stage 1 supervisor exposes equivalent 136-story channel and driver manifests.
56
+ - All macOS, Windows, and Win32 JavaScript bundles include the runtime bridge.
57
+
58
+ ### Stage 1 Phase 3: Complete
59
+
60
+ - Finalized strict static plan, selector, action, assertion, capability,
61
+ platform, result, test, step, and artifact contracts under `/authoring`.
62
+ - Added deterministic filtering and sharding plus complete fake-host execution
63
+ for clicks, text entry, key/action sequences, scrolling, waits, Storybook
64
+ args, screenshots, source, and semantic assertions.
65
+ - Added the sanctioned `/wdio` API and typed browser commands for listing,
66
+ opening, resetting, asserting, and running story plans without Appium.
67
+ - Added confined atomic artifacts, host metadata, `run.json`, and automatic
68
+ screenshot, source, and compact-tree failure evidence.
69
+ - Added the bounded `/agent` API for listing, explaining, inspecting, finding,
70
+ acting, checking, capturing, and running the same story plans.
71
+ - Added the JSON `desktop-driver` CLI for fake serving, story list/explain/run,
72
+ sharding, evidence, and bounded agent describe/screenshot operations.
73
+ - Added manifest-derived fake elements and repeatable per-test state reset so
74
+ real component plans can run repeatedly in Stage 1.
75
+ - Added typed, statically extractable `desktop-e2e` plans to Button, Checkbox,
76
+ and Input. All three extract from real CSF and pass repeatedly through the
77
+ sanctioned WebdriverIO runner.
78
+ - Evaluated MCP integration and deferred a composed executable adapter until
79
+ Stage 2 proves the native command and security model; a schema-only claim is
80
+ explicitly insufficient.
81
+ - Updated package, Storybook, runtime, app, component, skill, and agent
82
+ documentation for the final Stage 1 responsibilities.
83
+
84
+ ### Remaining work
85
+
86
+ Stage 1 is complete; no Phase 1, Phase 2, or Phase 3 deliverables are left
87
+ incomplete.
88
+
89
+ - Stage 2 remains: implement Windows/Win32 and macOS native host providers and
90
+ replace the Stage 1 fake target in native runs.
91
+ - Stage 3 remains: release hardening, native artifact ownership, security
92
+ review, and CI promotion.
93
+ - On-device bridge execution is intentionally deferred to Stage 2; Phase 2 is
94
+ validated through the fake host, runtime/server contract tests, live
95
+ same-process services, and production bundles.
96
+
97
+ ## Outcome
98
+
99
+ Create a public `@fluentui-react-native/desktop-driver` package that:
100
+
101
+ - implements a useful, explicitly documented subset of the W3C WebDriver
102
+ Classic protocol without Appium;
103
+ - drives React Native macOS, React Native Windows Fabric, and React Native
104
+ Win32 Paper applications;
105
+ - exposes platform-neutral window, accessibility-tree, input, screenshot, and
106
+ diagnostics APIs;
107
+ - formalizes the device contracts required by desktop Storybook applications;
108
+ - lets component authors declare portable tests alongside stories;
109
+ - runs those tests as end-to-end automation or exposes the same operations to
110
+ validation agents;
111
+ - reuses the existing Storybook channel server for story discovery, selection,
112
+ and render events;
113
+ - avoids requiring another long-running Node server process for Storybook.
114
+
115
+ ## Non-goals
116
+
117
+ - Do not expose Appium client APIs, Appium capabilities, or the Appium CLI.
118
+ - Do not emulate browser-only behavior such as navigation, cookies, frames,
119
+ shadow roots, JavaScript execution, prompts, or printing.
120
+ - Do not make Storybook a dependency of the generic driver.
121
+ - Do not require Jest or a particular agent protocol for authored story tests.
122
+ WebdriverIO is the sanctioned high-level automation API.
123
+ - Do not use visible text, layout order, or native class names as the stable
124
+ selector contract.
125
+ - Do not include visual-regression baseline comparison or migration of the
126
+ legacy E2E harness in the initial effort; both are future considerations.
127
+
128
+ ## Architectural decisions
129
+
130
+ ### Package topology
131
+
132
+ Create one new public package:
133
+
134
+ ```text
135
+ @fluentui-react-native/desktop-driver
136
+ ```
137
+
138
+ Do not initially create either `desktop-driver-server` or
139
+ `storybook-desktop-server`.
140
+
141
+ `desktop-driver` owns both an embeddable W3C remote end and a standalone CLI.
142
+ The server is central to the package rather than an independently useful
143
+ product boundary. Keep internal `protocol`, `server`, `client`, and `host`
144
+ seams so a server package can be extracted later without changing public
145
+ contracts.
146
+
147
+ `storybook-desktop` remains the owner of Storybook configuration, the channel
148
+ server, Metro and native app lifecycle, platform selection, generated
149
+ manifests, and Storybook-specific orchestration. It depends on
150
+ `desktop-driver`, registers a Storybook target and orchestration adapter, and
151
+ starts the embedded driver listener.
152
+
153
+ `storybook-desktop-runtime` remains React Native-only. It exposes the native
154
+ story root and sends versioned readiness/error messages over the existing
155
+ Storybook channel. It does not host WebDriver or import Node APIs.
156
+
157
+ ```text
158
+ component story
159
+ -- type-only --> desktop-driver/authoring
160
+
161
+ desktop-driver
162
+ -- no dependency --> Storybook, React, React Native, or private apps
163
+
164
+ storybook-desktop
165
+ --> desktop-driver
166
+
167
+ storybook-desktop-runtime
168
+ --> Storybook channel only
169
+
170
+ apps/storybook
171
+ --> storybook-desktop
172
+ --> storybook-desktop-runtime
173
+ ```
174
+
175
+ ### Server and process model
176
+
177
+ The existing Storybook server remains the Storybook control plane:
178
+
179
+ - story index and documentation;
180
+ - WebSocket channel;
181
+ - story selection;
182
+ - Storybook events;
183
+ - existing MCP endpoint.
184
+
185
+ The W3C remote end uses a separate loopback port because the current upstream
186
+ channel server constructs and owns its HTTP server and handles unmatched
187
+ requests itself. Mounting WebDriver routes into that listener would couple the
188
+ driver to upstream internals and risk conflicting responses.
189
+
190
+ Both listeners should run in the same `storybook-desktop` Node process:
191
+
192
+ ```text
193
+ storybook-desktop supervisor process
194
+ |- Storybook HTTP/WebSocket/MCP listener
195
+ |- WebDriver HTTP listener
196
+ |- Metro child process, when needed
197
+ |- native host transport, when needed
198
+ `- owned application process or attached application lease
199
+ ```
200
+
201
+ This meets the goal of avoiding another long-running server process while
202
+ keeping the two protocols isolated. A non-Storybook application can run the
203
+ same WebDriver remote end through the standalone `desktop-driver` CLI.
204
+
205
+ Create a separate server package only if one of these triggers occurs:
206
+
207
+ 1. a consumer needs the server without the client, authoring, and testing APIs;
208
+ 2. remote host deployment requires a release cadence independent of the
209
+ package;
210
+ 3. native artifacts make the server install materially heavier than the
211
+ client;
212
+ 4. authentication, TLS, or fleet management becomes a separate product
213
+ concern.
214
+
215
+ ### Target registration
216
+
217
+ Clients select a server-registered target:
218
+
219
+ ```json
220
+ {
221
+ "capabilities": {
222
+ "alwaysMatch": {
223
+ "browserName": "furn-native-desktop",
224
+ "platformName": "windows",
225
+ "furn:target": "agentic-storybook-windows",
226
+ "furn:launchMode": "attach"
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ Capabilities must not accept arbitrary executable paths, command arguments,
233
+ environment variables, manifest paths, or artifact roots. Target definitions
234
+ are registered when the server starts and resolve to controlled launch/attach
235
+ providers and a confined artifact root.
236
+
237
+ An explicit local-development mode may allow ad hoc targets later, but it must
238
+ be disabled by default and unavailable to agent-facing APIs.
239
+
240
+ ### V1 support scope
241
+
242
+ V1 supports:
243
+
244
+ - Windows 11 x64;
245
+ - macOS 14 on Apple Silicon;
246
+ - Windows Fabric, Win32 Paper, and macOS Storybook endpoints;
247
+ - exactly one active session per physical target.
248
+
249
+ Broader operating-system, architecture, and concurrency support is deferred.
250
+
251
+ ## Package responsibilities
252
+
253
+ ### `desktop-driver`
254
+
255
+ Own:
256
+
257
+ - W3C routing, response envelopes, errors, and capability processing;
258
+ - session, timeout, window, input, and element state;
259
+ - server-side target registry;
260
+ - platform-neutral host contract;
261
+ - native host transport protocol;
262
+ - WebDriver element identity and staleness;
263
+ - typed low-level client;
264
+ - sanctioned WebdriverIO runner, configuration, matchers, and custom commands;
265
+ - generic serializable story-test schema and runner primitives;
266
+ - screenshots, artifacts, logs, and diagnostics;
267
+ - token-efficient agent operations;
268
+ - deterministic fake host and protocol conformance harness.
269
+
270
+ Do not depend at runtime on:
271
+
272
+ - Appium;
273
+ - Storybook;
274
+ - React or React Native;
275
+ - a private application package.
276
+
277
+ ### `storybook-desktop`
278
+
279
+ Own:
280
+
281
+ - a generated platform-specific Story Manifest;
282
+ - a Storybook implementation of the driver's `StoryOrchestrator` interface;
283
+ - authenticated/correlated channel messages;
284
+ - Storybook extension commands;
285
+ - one supervisor for channel, Metro, driver, app, and test lifecycle;
286
+ - driver port allocation in the existing per-enlistment instance identity;
287
+ - Storybook test-plan extraction and digest generation;
288
+ - machine-readable readiness output;
289
+ - `driver`, `test`, and `agent` CLI flows.
290
+
291
+ ### `storybook-desktop-runtime`
292
+
293
+ Own:
294
+
295
+ - a stable native application/root marker;
296
+ - a stable native story-canvas marker;
297
+ - native-observable current story and preview generation;
298
+ - runtime hello, story-ready, story-error, and reset acknowledgements;
299
+ - a per-test remount boundary keyed by run ID;
300
+ - render-error forwarding.
301
+
302
+ ### Consuming Storybook app
303
+
304
+ Own:
305
+
306
+ - target registration and native identity;
307
+ - story package discovery and platform exclusions;
308
+ - exceptional launch/run commands;
309
+ - artifact root;
310
+ - concrete `testID` prefix;
311
+ - pilot story tests;
312
+ - cross-package contract tests.
313
+
314
+ ## W3C remote-end contract
315
+
316
+ Describe the package as a **W3C WebDriver Classic-compatible native desktop
317
+ remote end**, not a conforming browser remote end. Unsupported browser commands
318
+ return `unsupported operation`; they never return fabricated success values.
319
+
320
+ ### Initial standard endpoints
321
+
322
+ Implement:
323
+
324
+ - `GET /status`;
325
+ - `POST /session` and `DELETE /session/{id}`;
326
+ - `GET|POST /session/{id}/timeouts`;
327
+ - current window, window handles, switch window, close window;
328
+ - get/set window rectangle where the host reports support;
329
+ - find element(s) from the window or an element;
330
+ - active element;
331
+ - element name/role, text, attributes, properties, rectangle, enabled, and
332
+ selected state where supported;
333
+ - click, clear, and send keys;
334
+ - perform and release actions;
335
+ - window screenshot and element screenshot;
336
+ - normalized accessibility source.
337
+
338
+ Return `unsupported operation` for:
339
+
340
+ - navigation and history;
341
+ - cookies;
342
+ - frames and shadow roots;
343
+ - arbitrary JavaScript execution;
344
+ - browser prompts;
345
+ - printing;
346
+ - CSS values;
347
+ - new-window creation until native semantics are specified.
348
+
349
+ Do not repurpose the standard `pageLoad` timeout for story readiness. Add
350
+ namespaced driver timeouts:
351
+
352
+ ```ts
353
+ type DesktopTimeouts = {
354
+ appLaunch: number;
355
+ nativeCommand: number;
356
+ storyRender: number;
357
+ stableLayout: number;
358
+ };
359
+ ```
360
+
361
+ ### Capability negotiation
362
+
363
+ Implement W3C `alwaysMatch` and ordered `firstMatch` processing, including:
364
+
365
+ - extension capability names containing `:`;
366
+ - rejection of duplicate keys during merge;
367
+ - ordered candidate evaluation;
368
+ - `session not created` when no target/provider can satisfy a candidate;
369
+ - truthful returned capabilities based on the selected host.
370
+
371
+ Use:
372
+
373
+ - `platformName: "macos"` or `"windows"` for the operating system;
374
+ - `furn:endpoint: "macos" | "windows" | "win32"` for the repository endpoint;
375
+ - `furn:renderer: "fabric" | "paper"` for renderer semantics;
376
+ - `furn:target` for the registered target;
377
+ - `furn:clickMode: "physical" | "accessibility" | "auto"` for environment-
378
+ appropriate element-click behavior;
379
+ - `furn:features` for negotiated input, tree, state, screenshot, and window
380
+ capabilities.
381
+
382
+ Return a standard capability only when its semantics are implemented.
383
+
384
+ ### Errors
385
+
386
+ Map native failures to specific WebDriver errors:
387
+
388
+ | Condition | WebDriver error |
389
+ | --------------------------------------------- | --------------------------- |
390
+ | target cannot launch or attach | `session not created` |
391
+ | missing/closed session | `invalid session id` |
392
+ | missing/closed window | `no such window` |
393
+ | lookup does not resolve | `no such element` |
394
+ | retained native node is detached/replaced | `stale element reference` |
395
+ | malformed locator | `invalid selector` |
396
+ | disabled, unfocusable, or empty-bounds target | `element not interactable` |
397
+ | another node owns the hit-tested point | `element click intercepted` |
398
+ | capture backend fails | `unable to capture screen` |
399
+ | deadline expires | `timeout` |
400
+ | capability/property/operation is unavailable | `unsupported operation` |
401
+
402
+ Error `data` may contain redacted native error codes, operation names, and
403
+ artifact IDs. It must not expose environment variables, arbitrary paths, or
404
+ private window content.
405
+
406
+ ### Element identity and staleness
407
+
408
+ Expose only session-generated UUIDs under the standard key:
409
+
410
+ ```text
411
+ element-6066-11e4-a52e-4f735466cecf
412
+ ```
413
+
414
+ Never expose UIA runtime IDs, AX references, React tags, HWNDs, or accessibility
415
+ paths as public element IDs.
416
+
417
+ Each stored element records:
418
+
419
+ - native handle;
420
+ - application and window;
421
+ - logical scope: `application`, `chrome`, `preview`, or `secondary-window`;
422
+ - preview generation, when applicable;
423
+ - diagnostic locator fingerprint.
424
+
425
+ Every element command performs a cheap liveness check. A story reset increments
426
+ the preview generation and invalidates preview elements only. Storybook chrome
427
+ and still-live secondary-window elements remain valid.
428
+
429
+ Do not reconstruct a missing native object from a role/index path. Re-resolving
430
+ to a different object must produce staleness rather than silently changing the
431
+ meaning of an existing WebDriver reference.
432
+
433
+ ### Selectors
434
+
435
+ The portable authoring API exposes:
436
+
437
+ ```ts
438
+ by.testId('button-primary');
439
+ by.role('button', { name: 'Save' });
440
+ by.accessibleName('Save');
441
+ by.text('Saved');
442
+ ```
443
+
444
+ Wire strategies in the first release:
445
+
446
+ - `accessibility id` as a documented extension mapping to `testID`;
447
+ - `tag name` mapping to normalized native role;
448
+ - `link text` and `partial link text` mapping to accessible name only where
449
+ those standard semantics are meaningful.
450
+
451
+ Defer CSS and XPath. Do not redefine CSS for a non-DOM tree, and do not add the
452
+ cost and brittleness of normalized XML/XPath until a concrete client need is
453
+ demonstrated.
454
+
455
+ Deterministic authored tests use `testID`. Role and accessible name are
456
+ important for accessibility validation and agent exploration, but are not a
457
+ replacement for stable IDs.
458
+
459
+ ### State
460
+
461
+ Native platforms expose different state sets. Absence must never become a
462
+ false-shaped passing assertion.
463
+
464
+ ```ts
465
+ type SupportedValue<T> = { supported: true; value: T } | { supported: false; reason: string };
466
+ ```
467
+
468
+ Normalize, when supported:
469
+
470
+ - automation ID;
471
+ - accessible name and help;
472
+ - role and native role;
473
+ - value/text;
474
+ - enabled;
475
+ - focused/focusable;
476
+ - selected, checked/mixed, and expanded;
477
+ - visible/offscreen;
478
+ - logical rectangle;
479
+ - supported accessibility actions/patterns.
480
+
481
+ The runner checks declared capabilities before a test. Unsupported required
482
+ state produces an explicit skip or unsupported result, not a passing
483
+ assertion.
484
+
485
+ ### Input
486
+
487
+ Standard `element.click()` uses the session's negotiated click mode:
488
+
489
+ ```ts
490
+ type ClickMode = 'physical' | 'accessibility' | 'auto';
491
+ ```
492
+
493
+ - `physical` performs real pointer input and is the default for local
494
+ component validation;
495
+ - `accessibility` invokes the native accessibility action and is intended for
496
+ environments such as CI where physical input is blocked;
497
+ - `auto` prefers physical input and falls back to accessibility activation
498
+ only when the host reports that physical input is unavailable.
499
+
500
+ The selected mode is returned in `furn:features`. Session creation fails when
501
+ the requested mode is unsupported, rather than silently changing interaction
502
+ semantics. Accessibility mode is necessarily capability-limited because not
503
+ every React Native control projects an activation action.
504
+
505
+ Physical click executes:
506
+
507
+ 1. validate liveness;
508
+ 2. scroll into view when supported;
509
+ 3. activate the owning window;
510
+ 4. refresh bounds;
511
+ 5. compute an in-view point;
512
+ 6. hit-test the point;
513
+ 7. reject interception;
514
+ 8. send pointer down/up.
515
+
516
+ An explicit extension command may invoke accessibility activation regardless
517
+ of the session default for accessibility-focused validation.
518
+
519
+ Implement W3C Actions with:
520
+
521
+ - key, mouse pointer, wheel, and null sources;
522
+ - tick grouping and duration;
523
+ - viewport, pointer, and element origins;
524
+ - depressed key/button tracking;
525
+ - Release Actions on normal teardown, timeout, cancellation, and host failure.
526
+
527
+ There is one global input mutex per physical desktop. V1 permits exactly one
528
+ active session per physical target.
529
+
530
+ Public rectangles use logical points/DIPs relative to the current window client
531
+ area. Hosts privately convert to screen pixels using window origin, frame
532
+ insets, Windows DPI, Retina backing scale, and virtual-desktop origin. Capture
533
+ metadata records both logical and pixel dimensions and the scale factor.
534
+
535
+ ### Screenshots
536
+
537
+ Standard screenshot commands return Base64 PNG:
538
+
539
+ - session screenshot: current native window content;
540
+ - element screenshot: current window capture cropped to the visible element
541
+ bounds;
542
+ - window decorations excluded by default.
543
+
544
+ Extensions may request:
545
+
546
+ - window frame inclusion;
547
+ - a named artifact;
548
+ - all windows, returned as an artifact manifest;
549
+ - display capture for diagnostics.
550
+
551
+ The platform-neutral stage uses fake captures to establish protocol and
552
+ artifact behavior. Real screenshot support arrives with the later native-host
553
+ stage. Windows native work includes occlusion-independent HWND capture before
554
+ the Windows screenshot capability is advertised.
555
+
556
+ ### Diagnostics and artifacts
557
+
558
+ Provide namespaced commands for:
559
+
560
+ - compact JSON accessibility tree;
561
+ - full normalized tree/source;
562
+ - host and permission diagnostics;
563
+ - recent driver, host, story, input, and device events;
564
+ - named screenshots and evidence bundles;
565
+ - Storybook manifest and current story state.
566
+
567
+ Suggested failure bundle:
568
+
569
+ ```text
570
+ artifacts/desktop-driver/<run-id>/
571
+ run.json
572
+ host.json
573
+ sessions/<session-id>/
574
+ commands.ndjson
575
+ windows.json
576
+ source.xml
577
+ tree.json
578
+ screenshots/
579
+ logs/
580
+ stories/<story-id>/<test-id>/
581
+ result.json
582
+ before.png
583
+ failure.png
584
+ ```
585
+
586
+ Result status distinguishes:
587
+
588
+ - passed;
589
+ - assertion failed;
590
+ - skipped unsupported capability;
591
+ - timed out;
592
+ - cancelled;
593
+ - app crashed;
594
+ - driver/host failed;
595
+ - configuration failed;
596
+ - permission failed.
597
+
598
+ ## Platform-neutral host contract
599
+
600
+ The protocol layer depends only on an injected host:
601
+
602
+ ```ts
603
+ interface DesktopHost {
604
+ readonly endpoint: 'macos' | 'windows' | 'win32';
605
+
606
+ probe(): Promise<DesktopHostInfo>;
607
+ launch(target: RegisteredTarget): Promise<ApplicationLease>;
608
+ attach(target: RegisteredTarget): Promise<ApplicationLease>;
609
+
610
+ windows(app: ApplicationLease): Promise<DesktopWindow[]>;
611
+ activate(window: DesktopWindow): Promise<void>;
612
+ getWindowRect(window: DesktopWindow): Promise<Rect>;
613
+ setWindowRect(window: DesktopWindow, rect: Partial<Rect>): Promise<Rect>;
614
+
615
+ find(root: DesktopRoot, selector: NativeSelector, options: FindOptions): Promise<NativeElement[]>;
616
+ snapshot(element: NativeElement): Promise<NativeElementSnapshot>;
617
+ isAlive(element: NativeElement): Promise<boolean>;
618
+ hitTest(window: DesktopWindow, point: Point): Promise<NativeElement | null>;
619
+
620
+ performActions(actions: readonly NativeActionTick[]): Promise<void>;
621
+ releaseActions(): Promise<void>;
622
+
623
+ captureWindow(window: DesktopWindow): Promise<NativeImage>;
624
+ captureRect(window: DesktopWindow, rect: Rect): Promise<NativeImage>;
625
+
626
+ subscribe(listener: DesktopHostEventListener): Disposable;
627
+ dispose(): Promise<void>;
628
+ }
629
+ ```
630
+
631
+ `ApplicationLease` records:
632
+
633
+ - `launched` or `attached` ownership;
634
+ - PID and process creation time;
635
+ - target identity;
636
+ - known windows;
637
+ - graceful close behavior.
638
+
639
+ Attached apps are preserved by default. Cleanup uses exact owned resource
640
+ records, never process-name matching.
641
+
642
+ Host events should include:
643
+
644
+ - structure changed;
645
+ - focus/property changed;
646
+ - window opened/closed;
647
+ - app exited;
648
+ - host transport failed.
649
+
650
+ The host transport begins with a versioned handshake containing protocol and
651
+ helper versions, endpoint, architecture, capabilities, and permission state.
652
+ A host crash invalidates the session and produces an infrastructure failure; it
653
+ is not silently restarted during a test.
654
+
655
+ ## Native implementation staging
656
+
657
+ Keep the transport replaceable behind `DesktopHost`. Do not make an unproven
658
+ FFI library or an unsigned native binary a permanent API decision.
659
+
660
+ The initial stage contains no Windows or macOS native code. It delivers the
661
+ complete protocol, fake host, Storybook integration, WebdriverIO authoring
662
+ surface, agent API, and native-host contract using TypeScript/Node only.
663
+
664
+ Native platform providers are a separate second delivery stage:
665
+
666
+ - Windows 11 x64 and Win32 Paper share a Windows provider built around UI
667
+ Automation, configurable physical/accessibility interaction, app/window
668
+ ownership, and occlusion-independent Windows Graphics Capture;
669
+ - macOS 14 on Apple Silicon receives a provider selected to preserve the
670
+ required local and hosted-CI authority while implementing the same host
671
+ contract;
672
+ - native build, signing, notarization, and artifact distribution are scoped to
673
+ that stage rather than prerequisites for the platform-neutral package.
674
+
675
+ ### Current constraints
676
+
677
+ - package installation scripts are disabled;
678
+ - new dependencies must satisfy the repository age policy;
679
+ - public packages are built and packed on Linux;
680
+ - the current publish pipeline does not build, sign, or notarize Windows/macOS
681
+ native artifacts;
682
+ - current macOS E2E uses a Mac2/XCTest substrate on hosted macOS CI;
683
+ - current Win32 Storybook smoke uses in-box Windows UI Automation from
684
+ PowerShell on hosted Windows CI.
685
+
686
+ ### Native-stage feasibility gates
687
+
688
+ Evaluate at least these options against the same host contract:
689
+
690
+ | Endpoint | Candidate | Purpose |
691
+ | ------------- | ---------------------------------------------------- | -------------------------------------------------------- |
692
+ | Windows/Win32 | long-lived PowerShell UIA worker with P/Invoke input | zero-published-binary baseline |
693
+ | Windows/Win32 | C++/WinRT helper using UIA, SendInput, and WGC | highest-fidelity capture and typed native implementation |
694
+ | macOS local | direct AX/CGEvent transport | fast developer attach loop, requires TCC |
695
+ | macOS CI | first-party XCTest-based transport | preserve hosted-CI automation without Appium |
696
+ | macOS | Swift helper using AX, CGEvent, and ScreenCaptureKit | stable native implementation if build/signing is funded |
697
+
698
+ Before native implementation begins, the stage must answer:
699
+
700
+ - Can the candidate be built, packaged, and invoked through declared repository
701
+ scripts without install-time compilation?
702
+ - What identity receives Accessibility and Screen Recording permission?
703
+ - Can hosted CI grant or inherit the required authority?
704
+ - Can it enumerate and interact with current app windows?
705
+ - Does physical pointer/keyboard input reach React Native controls?
706
+ - Can it capture composited window content when occluded, scaled, and spread
707
+ across monitors? Occlusion-independent capture is required for the Windows
708
+ provider in this stage.
709
+ - Can it capture secondary Callout windows?
710
+ - What is its cold-start and command latency?
711
+ - How are native errors and events represented?
712
+
713
+ Current direction:
714
+
715
+ - keep these platform experiments out of the initial implementation;
716
+ - start the native Windows/Win32 stage with a long-lived PowerShell UIA worker
717
+ as a contract probe because the current tree proves that substrate can
718
+ inspect Win32 on hosted CI;
719
+ - implement Windows.Graphics.Capture or equivalent direct HWND capture as part
720
+ of that same native stage before advertising full screenshot support;
721
+ - retain an XCTest-backed macOS CI transport unless a non-Appium replacement
722
+ proves the same hosted-runner authority;
723
+ - allow a raw AX/CGEvent macOS provider for local attach workflows;
724
+ - introduce signed Swift/C++ helpers only within the native stage and only
725
+ after build/sign/notarization and artifact-package ownership are approved.
726
+
727
+ Using XCTest as an internal host transport does not make Appium part of the
728
+ authoring or wire contract; the package still owns the W3C server, sessions,
729
+ capabilities, errors, and public APIs.
730
+
731
+ ## Storybook device contract
732
+
733
+ ### Instance manifest
734
+
735
+ Extend the current per-enlistment instance model with one generated manifest:
736
+
737
+ ```ts
738
+ type DesktopStorybookDriverManifest = {
739
+ schemaVersion: 1;
740
+ instanceId: string;
741
+ endpoint: 'macos' | 'windows' | 'win32';
742
+ renderer: 'fabric' | 'paper';
743
+ targetId: string;
744
+ appName: string;
745
+ displayName: string;
746
+ testIDPrefix: string;
747
+ storybookPort: number;
748
+ metroPort: number;
749
+ driverPort: number;
750
+ platformManifestDigest: string;
751
+ portablePlanDigest: string;
752
+ bridgeNonce: string;
753
+ };
754
+ ```
755
+
756
+ This generated projection becomes the single source for the runtime,
757
+ supervisor, server target, and smoke/test commands. Its `testIDPrefix`
758
+ originates from the consuming app's custom `app.json` Storybook identity, so
759
+ the app does not maintain a second identity file or duplicate runtime setting.
760
+
761
+ Use two digests:
762
+
763
+ 1. `platformManifestDigest` covers the exact platform catalog and plans;
764
+ 2. `portablePlanDigest` covers only explicitly portable stories/tests and
765
+ excludes physical paths and platform-only metadata.
766
+
767
+ Manifests contain package names and package-relative POSIX paths. Absolute
768
+ package roots stay in process memory and are excluded from digests and agent
769
+ output.
770
+
771
+ ### Required native markers
772
+
773
+ Every desktop Storybook endpoint must expose:
774
+
775
+ 1. a stable application/root marker;
776
+ 2. a stable story canvas/root `testID`;
777
+ 3. the active story ID in native-observable state;
778
+ 4. the preview generation or run ID in native-observable state.
779
+
780
+ Only the story root is a universal chrome contract. Do not require macOS or
781
+ Windows to fork upstream LiteUI merely to expose the Win32-specific sidebar,
782
+ addon, or resize-handle IDs.
783
+
784
+ ### Runtime bridge
785
+
786
+ The runtime sends versioned channel events:
787
+
788
+ ```ts
789
+ type DesktopBridgeEvent =
790
+ | {
791
+ type: 'furn:desktop:hello';
792
+ version: 1;
793
+ instanceId: string;
794
+ endpoint: 'macos' | 'windows' | 'win32';
795
+ targetId: string;
796
+ platformManifestDigest: string;
797
+ nonce: string;
798
+ }
799
+ | {
800
+ type: 'furn:desktop:story-ready';
801
+ requestId: string;
802
+ runId: string;
803
+ storyId: string;
804
+ previewGeneration: number;
805
+ portablePlanDigest: string;
806
+ }
807
+ | {
808
+ type: 'furn:desktop:story-error';
809
+ requestId: string;
810
+ runId: string;
811
+ storyId: string;
812
+ message: string;
813
+ };
814
+ ```
815
+
816
+ The supervisor rejects wrong instance, endpoint, target, digest, nonce,
817
+ duplicate bridge, and stale reconnect messages.
818
+
819
+ Story selection:
820
+
821
+ 1. authenticate the runtime hello;
822
+ 2. validate the story against the platform manifest;
823
+ 3. create request and run IDs;
824
+ 4. issue selection through the existing Storybook channel;
825
+ 5. await the correlated runtime `story-ready`;
826
+ 6. verify the native story marker and preview generation;
827
+ 7. wait for the native canvas root;
828
+ 8. invalidate prior preview element references;
829
+ 9. optionally wait for stable layout.
830
+
831
+ Each test receives a fresh run ID and remount boundary. The runtime resets local
832
+ story state and, when requested, Storybook args before acknowledging readiness.
833
+
834
+ ### Story Manifest
835
+
836
+ `storybook-desktop` generates a platform-specific manifest from the same story
837
+ configuration used by the app. It must preserve data not guaranteed by the
838
+ current `/index.json` response:
839
+
840
+ - canonical story ID;
841
+ - package name and package-relative source path;
842
+ - platform membership;
843
+ - authored tags;
844
+ - extracted `parameters.desktopDriver`;
845
+ - capability requirements;
846
+ - exact-platform and portable-plan digests.
847
+
848
+ Static extraction fails loudly with file and location when a test plan is not
849
+ serializable. It must never silently omit an authored plan.
850
+
851
+ Pass the manifest to the embedded driver in memory or through an owned
852
+ generated file. Do not introduce a fourth manifest HTTP listener.
853
+
854
+ ## Component-authored story tests
855
+
856
+ The primary contract is a versioned, statically serializable plan in story
857
+ parameters:
858
+
859
+ ```tsx
860
+ import type { DesktopStoryTests } from '@fluentui-react-native/desktop-driver/authoring';
861
+
862
+ export const Default: Story = {
863
+ parameters: {
864
+ desktopDriver: {
865
+ version: 1,
866
+ tests: [
867
+ {
868
+ id: 'pointer-and-keyboard-focus',
869
+ title: 'Supports pointer and keyboard focus',
870
+ requires: ['pointer', 'keyboard', 'focus'],
871
+ steps: [
872
+ {
873
+ expect: {
874
+ target: { testId: 'button-primary' },
875
+ state: 'enabled',
876
+ },
877
+ },
878
+ {
879
+ action: 'click',
880
+ target: { testId: 'button-primary' },
881
+ },
882
+ {
883
+ expect: {
884
+ target: { testId: 'button-primary' },
885
+ state: 'focused',
886
+ },
887
+ },
888
+ {
889
+ action: 'keys',
890
+ value: ['TAB'],
891
+ },
892
+ {
893
+ action: 'screenshot',
894
+ name: 'keyboard-focus',
895
+ },
896
+ ],
897
+ },
898
+ ],
899
+ } satisfies DesktopStoryTests,
900
+ },
901
+ };
902
+ ```
903
+
904
+ Initial actions:
905
+
906
+ - wait for target/state/stable layout;
907
+ - click and double-click;
908
+ - clear and type;
909
+ - key and W3C action sequences;
910
+ - scroll;
911
+ - update Storybook args;
912
+ - screenshot;
913
+ - capture tree/source;
914
+ - annotate evidence.
915
+
916
+ Initial assertions:
917
+
918
+ - exists/count;
919
+ - displayed;
920
+ - enabled;
921
+ - focused/focusable;
922
+ - selected/checked/mixed/expanded;
923
+ - accessible name/help;
924
+ - role;
925
+ - value/text;
926
+ - bounds;
927
+ - active element.
928
+
929
+ Rules:
930
+
931
+ - no platform branches inside a portable plan;
932
+ - differences use declarative `requires`, platform inclusion, and explicit skip
933
+ reasons;
934
+ - selectors use stable IDs for deterministic interaction;
935
+ - plans are hashable, listable before app launch, shardable, and
936
+ agent-readable;
937
+ - the test runner distinguishes unsupported capability from failed assertion.
938
+
939
+ Reserve a later imperative escape hatch for cases the DSL cannot express. It
940
+ must be an explicitly referenced React Native-free module, marked nonportable
941
+ or less agent-readable, and must not enter component package build/publish
942
+ output accidentally. Do not add it until real authored tests demonstrate the
943
+ need.
944
+
945
+ ## Public APIs
946
+
947
+ ### Typed client
948
+
949
+ ```ts
950
+ const client = await createDesktopDriverClient({ url: ready.webdriverUrl });
951
+ const session = await client.newSession({
952
+ platformName: 'windows',
953
+ 'furn:target': 'agentic-storybook-windows',
954
+ });
955
+
956
+ const story = await session.storybook.open('components-button--default');
957
+ const button = await story.find(by.testId('button-primary'));
958
+
959
+ await button.click();
960
+ await button.waitFor({ focused: true });
961
+ await story.screenshot({ name: 'focused-button' });
962
+ await story.runTest('pointer-and-keyboard-focus');
963
+ await session.delete();
964
+ ```
965
+
966
+ The high-level client calls the same W3C and extension routes available to
967
+ external clients.
968
+
969
+ ### WebdriverIO automation API
970
+
971
+ WebdriverIO is the sanctioned high-level test API for component authors and
972
+ automation suites. The package provides supported configuration, typed custom
973
+ commands, selectors, matchers, lifecycle integration, and Storybook commands.
974
+ The serializable story-plan DSL runs through this same WebdriverIO integration.
975
+
976
+ The W3C server remains client-neutral and does not require Appium. Validate it
977
+ with:
978
+
979
+ - a raw HTTP protocol suite;
980
+ - the low-level typed client;
981
+ - the sanctioned WebdriverIO remote client and runner.
982
+
983
+ WebdriverIO is a supported dependency of the high-level testing surface, while
984
+ the protocol server modules remain independent of it.
985
+
986
+ ### Agent API
987
+
988
+ Expose coarse, JSON-safe operations:
989
+
990
+ ```ts
991
+ agent.listStories();
992
+ agent.openStory(storyId);
993
+ agent.describe({ scope: 'canvas', depth: 3 });
994
+ agent.find({ testId: 'button-primary' });
995
+ agent.click({ testId: 'button-primary' });
996
+ agent.type({ testId: 'input', text: 'hello' });
997
+ agent.check({ testId: 'button-primary', role: 'button', enabled: true });
998
+ agent.screenshot({ scope: 'window', name: 'button' });
999
+ agent.runStoryTest(storyId, testId);
1000
+ agent.getArtifacts();
1001
+ agent.dispose();
1002
+ ```
1003
+
1004
+ `describe` returns a bounded projection containing role, name, `testID`,
1005
+ supported state, bounds, and child count. Lookup failures may include bounded
1006
+ nearest-ID suggestions.
1007
+
1008
+ Ship the typed API and JSON CLI first. MCP requires an actual executable
1009
+ adapter, not only a tool-schema file. Later, add an MCP route to the driver
1010
+ listener or a composed adapter owned by `storybook-desktop`; do not create a
1011
+ separate MCP package.
1012
+
1013
+ ### CLI and supervisor
1014
+
1015
+ `desktop-driver`:
1016
+
1017
+ ```text
1018
+ desktop-driver serve
1019
+ desktop-driver doctor --target <id> --json
1020
+ desktop-driver tree --session <id> --json
1021
+ desktop-driver screenshot --session <id> --output <path>
1022
+ ```
1023
+
1024
+ `storybook-desktop`:
1025
+
1026
+ ```text
1027
+ storybook-desktop driver --windows
1028
+ storybook-desktop test --windows [--story <glob>] [--tag <tag>]
1029
+ storybook-desktop agent --windows
1030
+ storybook-desktop manifest --windows
1031
+ storybook-desktop instance --windows --json
1032
+ ```
1033
+
1034
+ The Storybook supervisor:
1035
+
1036
+ 1. resolves platform and instance identity;
1037
+ 2. generates manifests;
1038
+ 3. starts the channel server and embedded driver listener;
1039
+ 4. starts Metro when needed;
1040
+ 5. registers the exact target;
1041
+ 6. launches or attaches the app;
1042
+ 7. authenticates the runtime bridge;
1043
+ 8. runs tests or writes agent-ready connection data;
1044
+ 9. releases input and tears down only owned resources.
1045
+
1046
+ ## Implemented package shape
1047
+
1048
+ ```text
1049
+ packages/agentic/desktop-driver/
1050
+ AGENTS.md
1051
+ PLAN.md
1052
+ README.md
1053
+ SPEC.md
1054
+ package.json
1055
+ tsconfig.json
1056
+ jest.config.cjs
1057
+ config/
1058
+ cli.cjs
1059
+ src/
1060
+ index.ts
1061
+ authoring/
1062
+ index.ts
1063
+ results.ts
1064
+ storyTests.ts
1065
+ artifacts/
1066
+ ArtifactManager.ts
1067
+ index.ts
1068
+ client/
1069
+ DesktopDriverClient.ts
1070
+ index.ts
1071
+ cli/
1072
+ createDesktopDriverCommand.ts
1073
+ index.ts
1074
+ protocol/
1075
+ actions.ts
1076
+ capabilities.ts
1077
+ constants.ts
1078
+ errors.ts
1079
+ timeouts.ts
1080
+ types.ts
1081
+ server/
1082
+ createDesktopDriverServer.ts
1083
+ index.ts
1084
+ SessionManager.ts
1085
+ TargetRegistry.ts
1086
+ host/
1087
+ types.ts
1088
+ hosts/
1089
+ fake/FakeDesktopHost.ts
1090
+ runner/
1091
+ index.ts
1092
+ StoryTestRunner.ts
1093
+ wdio/
1094
+ DesktopWebdriver.ts
1095
+ index.ts
1096
+ agent/
1097
+ DesktopAgent.ts
1098
+ index.ts
1099
+ testing/
1100
+ FakeStoryOrchestrator.ts
1101
+ fakeStoryElements.ts
1102
+ index.ts
1103
+ protocolHarness.ts
1104
+ ```
1105
+
1106
+ The later native stage adds `hosts/windows` and `hosts/macos`, plus any
1107
+ platform artifact packages approved by the native distribution design.
1108
+
1109
+ Potential subpath exports:
1110
+
1111
+ - `.`;
1112
+ - `./authoring`;
1113
+ - `./artifacts`;
1114
+ - `./client`;
1115
+ - `./cli`;
1116
+ - `./server`;
1117
+ - `./agent`;
1118
+ - `./runner`;
1119
+ - `./testing`;
1120
+ - `./wdio`;
1121
+ - `./package.json`.
1122
+
1123
+ Use explicit named exports. Keep platform code under `hosts`. The package
1124
+ `AGENTS.md` should require no Storybook imports, no platform branching outside
1125
+ host providers, exact ownership cleanup, and declared-script validation.
1126
+
1127
+ When implementation starts, conform to repository package rules:
1128
+
1129
+ - `build` is `tsc -b`;
1130
+ - composite TypeScript output and build info are configured;
1131
+ - workspace dependencies and project references match;
1132
+ - the root project references the package;
1133
+ - dependencies satisfy catalog and package-age policy;
1134
+ - publishing checks and a changeset are included.
1135
+
1136
+ ## Milestones
1137
+
1138
+ ### Stage 1: Platform-neutral foundation
1139
+
1140
+ Stage 1 intentionally contains no Windows or macOS native code.
1141
+
1142
+ #### Phase 1: W3C core and fake host - Complete
1143
+
1144
+ Deliver:
1145
+
1146
+ - package skeleton;
1147
+ - W3C router and response/error model;
1148
+ - capability negotiation;
1149
+ - target/session/window/element stores;
1150
+ - timeouts and input state machine;
1151
+ - deterministic fake host;
1152
+ - raw HTTP, typed-client, and WebdriverIO contract tests.
1153
+
1154
+ Exit:
1155
+
1156
+ - a client creates and deletes a session;
1157
+ - portable element, action, wait, and screenshot tests pass against the fake
1158
+ host;
1159
+ - unsupported routes return explicit W3C errors.
1160
+
1161
+ #### Phase 2: Storybook manifests, bridge, and supervisor - Complete
1162
+
1163
+ Deliver:
1164
+
1165
+ - instance/driver manifest and driver port;
1166
+ - Story Manifest and static test-plan extraction;
1167
+ - `StoryOrchestrator` adapter;
1168
+ - correlated runtime bridge and native story marker;
1169
+ - per-test remount/reset;
1170
+ - `storybook-desktop` supervisor;
1171
+ - Storybook extension commands.
1172
+
1173
+ Exit:
1174
+
1175
+ - a fake host can select/reset a story and run a declarative plan;
1176
+ - stale preview elements are deterministic;
1177
+ - exact-platform and portable-plan digests are checked;
1178
+ - no additional Node server process is required.
1179
+
1180
+ #### Phase 3: WebdriverIO, authoring, and agent surface - Complete
1181
+
1182
+ Deliver:
1183
+
1184
+ - public serializable story-plan schema;
1185
+ - representative plans validated against the fake host;
1186
+ - sanctioned WebdriverIO configuration, runner, custom commands, and matchers;
1187
+ - low-level typed client;
1188
+ - JSON CLI and bounded agent API;
1189
+ - standardized reports and failure bundles;
1190
+ - optional real MCP adapter evaluation.
1191
+
1192
+ Exit:
1193
+
1194
+ - an author can declare, list, shard, and run a plan through WebdriverIO against
1195
+ the fake host;
1196
+ - an agent can list, explain, execute, and diagnose the same plan;
1197
+ - protocol, authoring, artifacts, and agent APIs are stable before platform
1198
+ code is introduced.
1199
+
1200
+ ### Stage 2: Native desktop providers
1201
+
1202
+ Stage 2 implements the platform contracts proven in Stage 1.
1203
+
1204
+ #### Phase 4: Windows and Win32 native provider - Not started
1205
+
1206
+ Deliver:
1207
+
1208
+ - selected Windows host transport;
1209
+ - UI Automation tree and event support;
1210
+ - configurable physical and accessibility click modes;
1211
+ - physical keyboard, pointer, and wheel actions;
1212
+ - app attach/launch leases;
1213
+ - occlusion-independent HWND capture through Windows Graphics Capture or an
1214
+ equivalent native implementation;
1215
+ - multi-window and Callout handling.
1216
+
1217
+ Exit:
1218
+
1219
+ - one unchanged Button, Checkbox, Input, scrolling, screenshot, and
1220
+ secondary-window suite passes on Windows Fabric and Win32 Paper;
1221
+ - attached apps survive teardown;
1222
+ - exact owned resources are cleaned;
1223
+ - artifacts distinguish assertion, app, and host failures.
1224
+
1225
+ Land the first platform jobs as non-required until reliability and artifact
1226
+ quality are established.
1227
+
1228
+ #### Phase 5: macOS native provider - Not started
1229
+
1230
+ Deliver:
1231
+
1232
+ - selected local and CI transport(s);
1233
+ - accessibility tree and events;
1234
+ - configurable physical and accessibility click modes;
1235
+ - keyboard and pointer actions;
1236
+ - bundle-identity launch/attach;
1237
+ - direct window capture;
1238
+ - permission diagnostics;
1239
+ - XCTest-backed provider if required to preserve hosted CI.
1240
+
1241
+ Exit:
1242
+
1243
+ - the unchanged portable WebdriverIO suite passes on macOS 14 Apple Silicon;
1244
+ - missing authority fails before session creation with actionable diagnostics;
1245
+ - attached apps survive teardown;
1246
+ - the chosen CI environment is repeatable.
1247
+
1248
+ ### Stage 3: Release hardening
1249
+
1250
+ #### Phase 6: Release readiness - Not started
1251
+
1252
+ Deliver:
1253
+
1254
+ - protocol compatibility suite;
1255
+ - security review;
1256
+ - performance/timeout budgets;
1257
+ - clean-install and package-pack validation;
1258
+ - package-size review;
1259
+ - helper signing/notarization and artifact packages if selected;
1260
+ - documentation, changeset, and CI promotion criteria.
1261
+
1262
+ Exit:
1263
+
1264
+ - public package contents are reproducible;
1265
+ - native artifacts have an owned build/signing pipeline;
1266
+ - no required install scripts are needed;
1267
+ - supported platform jobs are promotable to required gates.
1268
+
1269
+ ## Validation matrix
1270
+
1271
+ | Capability | macOS | Windows Fabric | Win32 Paper |
1272
+ | ------------------------- | ---------------------------- | ---------------------------- | -------------------------------------- |
1273
+ | attach and preserve | bundle/window identity | process/AUMID/HWND | process/HWND |
1274
+ | launch and owned cleanup | provider-defined | packaged activation | prebuilt-host provider |
1275
+ | `testID` lookup | verify AX mapping | verify UIA mapping | current UIA smoke establishes baseline |
1276
+ | role/name/state | AX/XCTest | UIA | UIA |
1277
+ | pointer input | CGEvent/XCTest | SendInput | SendInput |
1278
+ | keyboard/Unicode | CGEvent/XCTest | SendInput | SendInput |
1279
+ | wheel/scroll | provider capability | SendInput/UIA | SendInput/UIA |
1280
+ | window screenshot | SCK/XCTest/provider | WGC/provider | WGC/provider |
1281
+ | element screenshot | crop with scale | crop with DPI | crop with DPI |
1282
+ | multiple windows | app windows | HWNDs | REX/Callout HWNDs |
1283
+ | story select/reset | channel bridge | channel bridge | channel bridge |
1284
+ | stale preview detection | generation + native liveness | generation + native liveness | generation + native liveness |
1285
+ | app/render error | bridge + process watch | bridge + process watch | bridge + process watch |
1286
+ | permission/desktop doctor | TCC/test authority | interactive session/UIPI | interactive session/UIPI |
1287
+
1288
+ Also validate:
1289
+
1290
+ - Windows 11 x64;
1291
+ - macOS 14 on Apple Silicon;
1292
+ - Windows 100%, 150%, and 200% scaling;
1293
+ - Retina and non-Retina macOS where supported;
1294
+ - multiple monitors and non-primary virtual-desktop origins;
1295
+ - light, dark, and high-contrast themes;
1296
+ - foreground, background, minimized, and occluded windows;
1297
+ - denied macOS permissions;
1298
+ - elevated Windows targets;
1299
+ - locked/disconnected desktops;
1300
+ - duplicate matching windows;
1301
+ - secondary Callout windows;
1302
+ - channel reconnect, duplicate runtime, stale nonce, and wrong digest;
1303
+ - app and host failure during a command;
1304
+ - port collision and parallel enlistments;
1305
+ - raw HTTP, first-party, and WebdriverIO clients.
1306
+
1307
+ Pilot stories:
1308
+
1309
+ - Button pointer and keyboard focus;
1310
+ - Checkbox checked/indeterminate state;
1311
+ - Input type and clear;
1312
+ - scrollable content;
1313
+ - a secondary Callout window;
1314
+ - a controlled failure for artifact verification.
1315
+
1316
+ ## Security and reliability
1317
+
1318
+ - Bind loopback only by default.
1319
+ - Reject browser-origin requests; do not enable permissive CORS.
1320
+ - Require explicit authentication and configuration for any non-loopback bind.
1321
+ - Use server-registered targets, not client-supplied commands.
1322
+ - Scope trees and screenshots to registered target windows.
1323
+ - Cap request body size, tree depth/node count, screenshot dimensions, command
1324
+ deadlines, and retained logs.
1325
+ - Confine artifact paths beneath an owned run root; reject absolute paths,
1326
+ traversal, Windows device/alternate-stream paths, and symlink escapes.
1327
+ - Redact environment variables and physical roots from public diagnostics.
1328
+ - Record PID plus process creation time.
1329
+ - Never kill by process name or fixed port.
1330
+ - Preserve attached applications.
1331
+ - Release all input state on every teardown path.
1332
+ - Run automation only in an interactive desktop session.
1333
+ - Treat screenshots and accessibility trees as potentially sensitive evidence.
1334
+
1335
+ ## Principal risks
1336
+
1337
+ | Risk | Mitigation |
1338
+ | ------------------------------------------------------------ | --------------------------------------------------------------- |
1339
+ | platform state projection differs | capability-gated assertions; unsupported is not false |
1340
+ | native element identity changes on remount | session UUIDs, liveness checks, preview generations |
1341
+ | physical input is global and flaky | one input owner, foreground verification, serialized actions |
1342
+ | macOS authority differs locally and in CI | native-stage dual-transport evaluation and fail-fast doctor |
1343
+ | composited-window capture is backend-specific | direct capture gate before advertising screenshots |
1344
+ | native artifacts cannot be built by current publish pipeline | keep binaries off critical path until an owned pipeline exists |
1345
+ | Storybook channel is broadcast-oriented | nonce/instance/digest handshake plus native marker verification |
1346
+ | static test extraction misses dynamic values | literal schema and loud file/location errors |
1347
+ | authored DSL becomes too limited | add an imperative escape hatch only from demonstrated cases |
1348
+ | agent/server can control a real desktop | loopback, origin rejection, target registry, bounded APIs |
1349
+ | Storybook upgrades change channel behavior | isolate behind adapter and contract tests |
1350
+ | sanctioned WebdriverIO surface drifts from raw W3C behavior | run the same contract cases through raw HTTP and WebdriverIO |
1351
+
1352
+ ## Open questions
1353
+
1354
+ 1. **Imperative test escape hatch:** What concrete scenarios must the initial
1355
+ serializable DSL support before an executable sidecar is justified?
1356
+ 2. **MCP:** Is typed API plus JSON CLI enough for the initial agent experience,
1357
+ or is a real MCP endpoint required for the first release?
1358
+ 3. **CI promotion:** What duration and pass-rate threshold should move new
1359
+ desktop-driver jobs from advisory to required?
1360
+
1361
+ ## Future considerations
1362
+
1363
+ ### Visual testing
1364
+
1365
+ V1 captures screenshots and complete scale/window/story metadata as evidence.
1366
+ Baseline storage, image comparison, tolerances, approval workflows, and
1367
+ cross-platform visual-diff policy are deferred until native capture fidelity
1368
+ has been proven on all endpoints.
1369
+
1370
+ ### Legacy E2E migration
1371
+
1372
+ The initial effort does not migrate or retire the existing Appium E2E harness.
1373
+ After the new driver reaches platform and scenario parity, evaluate incremental
1374
+ migration, dual-running duration, and retirement criteria as a separate
1375
+ project.
1376
+
1377
+ ### Additional platforms and concurrency
1378
+
1379
+ V1 supports Windows 11 x64 and macOS 14 on Apple Silicon, with one active
1380
+ session per physical target. Windows 10, Windows ARM64, Intel/universal macOS,
1381
+ and concurrent sessions are future expansion work.
1382
+
1383
+ ## References
1384
+
1385
+ - [W3C WebDriver](https://www.w3.org/TR/webdriver2/)
1386
+ - [Apple Accessibility for macOS](https://developer.apple.com/library/archive/documentation/Accessibility/Conceptual/AccessibilityMacOSX/)
1387
+ - [AXUIElement](https://developer.apple.com/documentation/applicationservices/axuielement)
1388
+ - [Quartz Event Services](https://developer.apple.com/documentation/coregraphics/quartz-event-services)
1389
+ - [ScreenCaptureKit](https://developer.apple.com/documentation/screencapturekit)
1390
+ - [XCUIApplication](https://developer.apple.com/documentation/xcuiautomation/xcuiapplication)
1391
+ - [Microsoft UI Automation](https://learn.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32)
1392
+ - [UI Automation control patterns](https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-controlpatternsoverview)
1393
+ - [SendInput](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput)
1394
+ - [Windows screen capture](https://learn.microsoft.com/en-us/windows/apps/develop/media-authoring-processing/screen-capture)