@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/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # React Native Desktop Driver
2
+
3
+ `@fluentui-react-native/desktop-driver` is a W3C WebDriver-compatible remote end
4
+ for React Native desktop applications. It does not use Appium.
5
+
6
+ Stage 1 provides the complete platform-neutral protocol, sanctioned WebdriverIO
7
+ API, serializable story-test contract, deterministic fake host, evidence
8
+ reports, JSON CLI, and bounded agent API. Native Windows, Win32, and macOS host
9
+ providers are separate Stage 2 work described in [PLAN.md](PLAN.md).
10
+
11
+ ## Package boundaries
12
+
13
+ | Surface | Responsibility |
14
+ | --------------------------------------- | ----------------------------------------------------------- |
15
+ | `@fluentui-react-native/desktop-driver` | Public types and common APIs |
16
+ | `/authoring` | Serializable story plans, selectors, and result types |
17
+ | `/wdio` | Sanctioned WebdriverIO connection, commands, and runner |
18
+ | `/agent` | Bounded JSON-safe inspection and action API |
19
+ | `/client` | Low-level typed W3C client |
20
+ | `/server` | Embeddable W3C remote end and target/session state |
21
+ | `/artifacts` | Confined atomic evidence persistence |
22
+ | `/testing` | Fake host, fake Storybook orchestration, and test harnesses |
23
+
24
+ The protocol server remains client-neutral. WebdriverIO is a dependency of the
25
+ high-level `/wdio` surface, not an implementation primitive for routing,
26
+ capability negotiation, or native hosts.
27
+
28
+ ## Authoring story tests
29
+
30
+ Plans are inline static data under `parameters.desktopDriver`. Storybook can
31
+ extract and shard them without importing a React Native story module, and
32
+ agents can explain the same steps before execution.
33
+
34
+ ```tsx
35
+ import type { DesktopStoryTests } from '@fluentui-react-native/desktop-driver/authoring';
36
+
37
+ export const Default: Story = {
38
+ tags: ['desktop-e2e'],
39
+ parameters: {
40
+ desktopDriver: {
41
+ version: 1,
42
+ tests: [
43
+ {
44
+ id: 'pointer-focus',
45
+ title: 'Responds to activation and receives focus',
46
+ requires: ['focus', 'screenshot'],
47
+ steps: [
48
+ { action: 'wait', target: { testId: 'my-button' } },
49
+ { expect: { state: 'role', target: { testId: 'my-button' }, value: 'button' } },
50
+ { action: 'click', target: { testId: 'my-button' } },
51
+ { expect: { state: 'focused', target: { testId: 'my-button' }, value: true } },
52
+ { action: 'screenshot', name: 'focused-button', target: { testId: 'my-button' } },
53
+ ],
54
+ },
55
+ ],
56
+ } satisfies DesktopStoryTests,
57
+ },
58
+ };
59
+ ```
60
+
61
+ Static extraction accepts JSON literals wrapped by TypeScript `satisfies` or
62
+ `as`. Do not hide plans behind variables, functions, spreads, computed
63
+ properties, or runtime platform branches. Invalid or dynamic plans fail
64
+ manifest generation with their source file, story, and line.
65
+
66
+ Portable selectors are:
67
+
68
+ - `{ testId }` for deterministic interaction;
69
+ - `{ role, name? }` for semantic lookup;
70
+ - `{ accessibleName }`;
71
+ - `{ text }`.
72
+
73
+ Supported actions include click, double-click, clear, type, keys, W3C actions,
74
+ scroll, Storybook arg updates, waits, screenshots, source capture, and notes.
75
+ Assertions cover existence/count, role, accessible name, text/value, displayed,
76
+ enabled, focused, selected, checked/mixed, and expanded state.
77
+
78
+ Use `requires` for capabilities such as `keyboard`, `focus`, `wheel`, or
79
+ `screenshot`. Missing capabilities produce an explicit skipped result rather
80
+ than a false pass. Platform divergence belongs in declarative `platforms` or
81
+ capability requirements, not branches inside a plan.
82
+
83
+ ## Sanctioned WebdriverIO API
84
+
85
+ ```ts
86
+ import { connectDesktopWebdriver } from '@fluentui-react-native/desktop-driver/wdio';
87
+
88
+ const desktop = await connectDesktopWebdriver({
89
+ platformName: 'windows',
90
+ targetId: 'agenticstorybook-windows',
91
+ url: 'http://127.0.0.1:39859',
92
+ });
93
+
94
+ try {
95
+ const manifest = await desktop.browser.desktopListStories();
96
+ await desktop.browser.desktopOpenStory('components-button--default');
97
+ await desktop.browser.desktopExpect({
98
+ state: 'enabled',
99
+ target: { testId: 'agentic-storybook-button' },
100
+ value: true,
101
+ });
102
+ const result = await desktop.browser.desktopRunStoryTests({
103
+ artifactsRoot: 'artifacts/windows/desktop-driver',
104
+ selection: { tag: 'desktop-e2e', shardCount: 2, shardIndex: 0 },
105
+ });
106
+ } finally {
107
+ await desktop.delete();
108
+ }
109
+ ```
110
+
111
+ Registered browser commands are:
112
+
113
+ - `desktopListStories()`;
114
+ - `desktopOpenStory(storyId, runId?)`;
115
+ - `desktopResetStory(storyId, runId?)`;
116
+ - `desktopExpect(expectation)`;
117
+ - `desktopRunStoryTests(options?)`.
118
+
119
+ The runner filters by story, test, and tag, shards the sorted
120
+ `storyId/testId` list deterministically, checks required capabilities, resets
121
+ the preview for every run, and distinguishes assertion, timeout, cancellation,
122
+ skip, and infrastructure outcomes.
123
+
124
+ The server serializes commands per session and all input globally. Timeout
125
+ paths abort host work before releasing input, while runner cancellation drains
126
+ its in-flight request before cleanup, so a later test cannot inherit a late key
127
+ or pointer action.
128
+
129
+ ## Evidence
130
+
131
+ Supplying `artifactsRoot` writes atomically beneath that root:
132
+
133
+ ```text
134
+ artifactsRoot/
135
+ host.json
136
+ run.json
137
+ tests/
138
+ <story>-<test>/
139
+ <named screenshots and source>
140
+ failure.png
141
+ failure-source.xml
142
+ failure-tree.json
143
+ ```
144
+
145
+ Artifact names are sanitized and confined beneath the configured root.
146
+ Failures attempt screenshot, source, and compact-tree capture; evidence-capture
147
+ errors are reported without replacing the original test failure.
148
+
149
+ ## JSON CLI
150
+
151
+ The CLI prints structured JSON for automation:
152
+
153
+ ```sh
154
+ desktop-driver serve --manifest story-manifest.windows.json --target fake-windows
155
+
156
+ desktop-driver stories list \
157
+ --url http://127.0.0.1:4444 \
158
+ --target fake-windows
159
+
160
+ desktop-driver stories explain components-button--default \
161
+ --url http://127.0.0.1:4444 \
162
+ --target fake-windows
163
+
164
+ desktop-driver stories run \
165
+ --url http://127.0.0.1:4444 \
166
+ --target fake-windows \
167
+ --tag desktop-e2e \
168
+ --artifacts artifacts/windows/desktop-driver
169
+
170
+ desktop-driver agent describe \
171
+ --url http://127.0.0.1:4444 \
172
+ --target fake-windows \
173
+ --scope story \
174
+ --artifacts artifacts/windows/desktop-driver
175
+ ```
176
+
177
+ `serve` is the Stage 1 fake target. It is not a native-provider substitute.
178
+
179
+ ## Agent API
180
+
181
+ ```ts
182
+ import { connectDesktopAgent } from '@fluentui-react-native/desktop-driver/agent';
183
+
184
+ const agent = await connectDesktopAgent({
185
+ artifactsRoot: 'artifacts/windows/desktop-driver',
186
+ platformName: 'windows',
187
+ targetId: 'agenticstorybook-windows',
188
+ url: 'http://127.0.0.1:39859',
189
+ });
190
+
191
+ try {
192
+ await agent.listStories();
193
+ await agent.openStory('components-button--default');
194
+ await agent.describe({ scope: 'story', depth: 3, maxNodes: 100 });
195
+ await agent.click({ testId: 'agentic-storybook-button' });
196
+ await agent.screenshot('button');
197
+ await agent.runStoryTest('components-button--default', 'pointer-focus');
198
+ } finally {
199
+ await agent.delete();
200
+ }
201
+ ```
202
+
203
+ The API intentionally exposes coarse operations and bounded tree projections.
204
+ It does not reveal native handles or permit arbitrary process, environment, or
205
+ artifact-path capabilities.
206
+
207
+ ## MCP decision
208
+
209
+ Phase 3 does not add another MCP listener. The Storybook MCP remains the
210
+ documentation and story-metadata surface; the typed agent API and JSON CLI are
211
+ the executable native-validation surface. Reconsider a composed MCP adapter
212
+ after Stage 2 proves the native commands and their security model. A tool-schema
213
+ file without an executable adapter is not considered an MCP integration.
214
+
215
+ ## Protocol and security
216
+
217
+ See [SPEC.md](SPEC.md) for implemented W3C routes, extension commands,
218
+ capabilities, device contracts, and unsupported browser behavior.
219
+
220
+ The server binds loopback, rejects browser-origin requests, accepts only
221
+ server-registered targets, permits one session per physical target, applies
222
+ host command deadlines, and preserves attached applications. Never expose it
223
+ remotely without a separately designed authentication and transport policy.
package/SPEC.md ADDED
@@ -0,0 +1,105 @@
1
+ # Desktop Driver Contract
2
+
3
+ ## Scope
4
+
5
+ Desktop Driver is a W3C WebDriver Classic-compatible native desktop remote end.
6
+ It intentionally implements native application semantics rather than
7
+ pretending to be a browser.
8
+
9
+ V1 platform names are `macos` and `windows`; endpoint names are `macos`,
10
+ `windows`, and `win32`.
11
+
12
+ ## Sessions and targets
13
+
14
+ - Targets are registered by the server.
15
+ - Capabilities select a target with `furn:target`.
16
+ - Capabilities never carry executable paths, environment variables, or output
17
+ directories.
18
+ - Exactly one active session may own a physical target.
19
+ - Commands are serialized per session, and input operations share one global
20
+ mutex.
21
+ - A target records whether the application was launched or attached.
22
+ - Deleting an attached session preserves the application.
23
+ - Cleanup uses owned process identity, never process-name matching.
24
+
25
+ Supported extension capabilities:
26
+
27
+ - `furn:target`;
28
+ - `furn:endpoint`;
29
+ - `furn:renderer`;
30
+ - `furn:clickMode`: `physical`, `accessibility`, or `auto`;
31
+ - returned `furn:features`.
32
+
33
+ ## W3C routes
34
+
35
+ Implemented standard behavior:
36
+
37
+ - status and new/delete session;
38
+ - get/set timeouts;
39
+ - current window, handles, switching, closing, and rectangles;
40
+ - element lookup from a window or element;
41
+ - active element;
42
+ - element name, text, attribute, property, rectangle, enabled, selected, and
43
+ displayed queries;
44
+ - click, clear, and send keys;
45
+ - perform/release actions;
46
+ - window and element screenshots;
47
+ - accessibility source.
48
+
49
+ Implemented `furn` extensions:
50
+
51
+ - `GET /session/{id}/furn/manifest`;
52
+ - `GET|POST /session/{id}/furn/story`;
53
+ - `POST /session/{id}/furn/story/reset`;
54
+ - `POST /session/{id}/furn/story/args`;
55
+ - `GET /session/{id}/furn/tree`.
56
+
57
+ Navigation, cookies, frames, shadow roots, prompts, printing, arbitrary
58
+ JavaScript execution, and browser CSS behavior return `unsupported operation`.
59
+
60
+ ## Elements
61
+
62
+ Public references use only the standard
63
+ `element-6066-11e4-a52e-4f735466cecf` key with session UUIDs. Native handles,
64
+ UIA runtime IDs, AX references, React tags, and HWNDs are private.
65
+
66
+ Every command validates native liveness. Story reset increments the preview
67
+ generation and invalidates preview elements while preserving live application
68
+ and Storybook chrome references.
69
+
70
+ Pointer and wheel actions may use WebDriver element origins. The server resolves
71
+ the public session element UUID, validates preview generation and native
72
+ liveness, and passes only `{ elementId: <native-id> }` to the host contract.
73
+ Public WebDriver element references never cross into platform providers.
74
+
75
+ Every host operation receives an `AbortSignal`. On timeout, the server aborts
76
+ the host operation before input release, session teardown, or a subsequent
77
+ command. Providers must stop side effects and settle promptly; they may never
78
+ apply late input after cleanup.
79
+
80
+ Portable lookup uses accessibility ID, normalized role, accessible name, and
81
+ the namespaced `-furn:text` strategy. Deterministic authored interactions use
82
+ `testID`.
83
+
84
+ ## Storybook readiness
85
+
86
+ The runtime and server validate:
87
+
88
+ - instance, target, endpoint, and catalog identity;
89
+ - a private bridge nonce;
90
+ - request and run IDs;
91
+ - portable-plan digest;
92
+ - preview generation;
93
+ - the native story-root marker.
94
+
95
+ Only the authenticated bridge client can complete or fail a selection. Every
96
+ test receives a new run ID and keyed preview remount.
97
+
98
+ ## Results
99
+
100
+ Run status is `passed` or `failed`. Tests distinguish `passed`, `failed`,
101
+ `skipped`, `timed-out`, `cancelled`, and `infrastructure-error`. Steps record
102
+ status, duration, error, and artifacts.
103
+
104
+ Unsupported required capabilities are skipped explicitly. An unavailable
105
+ native property never becomes a false-shaped passing assertion.
package/config/cli.cjs ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import('../lib/cli/index.js')
4
+ .then(({ runDesktopDriverCli }) => runDesktopDriverCli())
5
+ .catch((error) => {
6
+ process.stderr.write(`${error.stack || error.message}\n`);
7
+ process.exitCode = 1;
8
+ });
@@ -0,0 +1,9 @@
1
+ const config = require('@fluentui-react-native/scripts/jest-config');
2
+
3
+ module.exports = {
4
+ ...config,
5
+ moduleNameMapper: {
6
+ ...config.moduleNameMapper,
7
+ '^(\\.{1,2}/.*)\\.js$': '$1',
8
+ },
9
+ };
@@ -0,0 +1,56 @@
1
+ import type { DesktopArtifact, DesktopStoryRunResult } from '../authoring/results.js';
2
+ import type { DesktopStoryExpectation, DesktopStorySelector } from '../authoring/storyTests.js';
3
+ import { ArtifactManager } from '../artifacts/ArtifactManager.js';
4
+ import type { DesktopTreeNode } from '../host/types.js';
5
+ import type { DesktopStoryManifestEntry } from '../storybook.js';
6
+ import type { DesktopWebdriverOptions, DesktopWebdriverSession } from '../wdio/DesktopWebdriver.js';
7
+ export type DesktopAgentOptions = DesktopWebdriverOptions & {
8
+ artifactsRoot: string;
9
+ };
10
+ export type DesktopAgentStory = Pick<DesktopStoryManifestEntry, 'id' | 'name' | 'tags' | 'title'> & {
11
+ tests: readonly {
12
+ id: string;
13
+ title: string;
14
+ }[];
15
+ };
16
+ export type DesktopAgentElement = {
17
+ accessibleName: unknown;
18
+ enabled: boolean;
19
+ id: string;
20
+ role: string;
21
+ selected: boolean;
22
+ text: string;
23
+ };
24
+ export type DesktopAgentCheckResult = {
25
+ message?: string;
26
+ passed: boolean;
27
+ };
28
+ export type DesktopAgentDescribeOptions = {
29
+ depth?: number;
30
+ maxNodes?: number;
31
+ scope?: 'application' | 'story';
32
+ };
33
+ export declare class DesktopAgent {
34
+ private readonly artifacts;
35
+ private readonly desktop;
36
+ private readonly captured;
37
+ constructor(desktop: DesktopWebdriverSession, artifacts: ArtifactManager);
38
+ listStories(): Promise<DesktopAgentStory[]>;
39
+ explainStory(storyId: string): Promise<DesktopAgentStory>;
40
+ openStory(storyId: string): Promise<{
41
+ previewGeneration: number;
42
+ runId: string;
43
+ storyId: string;
44
+ }>;
45
+ describe(options?: DesktopAgentDescribeOptions): Promise<DesktopTreeNode[]>;
46
+ find(selector: DesktopStorySelector): Promise<DesktopAgentElement>;
47
+ click(selector: DesktopStorySelector): Promise<void>;
48
+ type(selector: DesktopStorySelector, text: string): Promise<void>;
49
+ check(expectation: DesktopStoryExpectation): Promise<DesktopAgentCheckResult>;
50
+ screenshot(name: string): Promise<DesktopArtifact>;
51
+ runStoryTest(storyId: string, testId: string): Promise<DesktopStoryRunResult>;
52
+ getArtifacts(): readonly DesktopArtifact[];
53
+ delete(): Promise<void>;
54
+ }
55
+ export declare function connectDesktopAgent(options: DesktopAgentOptions): Promise<DesktopAgent>;
56
+ //# sourceMappingURL=DesktopAgent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopAgent.d.ts","sourceRoot":"","sources":["../../src/agent/DesktopAgent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { ArtifactManager } from '../artifacts/ArtifactManager.js';
2
+ import { DesktopAssertionError, assertDesktopExpectation, findDesktopElement } from '../runner/StoryTestRunner.js';
3
+ import { WebDriverError } from '../protocol/errors.js';
4
+ import { connectDesktopWebdriver } from '../wdio/DesktopWebdriver.js';
5
+ export class DesktopAgent {
6
+ artifacts;
7
+ desktop;
8
+ captured = [];
9
+ constructor(desktop, artifacts) {
10
+ this.desktop = desktop;
11
+ this.artifacts = artifacts;
12
+ }
13
+ async listStories() {
14
+ const manifest = await this.desktop.listStories();
15
+ return manifest.entries.map((entry) => ({
16
+ id: entry.id,
17
+ name: entry.name,
18
+ tags: entry.tags,
19
+ tests: (entry.tests?.tests ?? []).map((test) => ({ id: test.id, title: test.title ?? test.id })),
20
+ title: entry.title,
21
+ }));
22
+ }
23
+ async explainStory(storyId) {
24
+ const story = (await this.listStories()).find(({ id }) => id === storyId);
25
+ if (!story) {
26
+ throw new Error(`Story "${storyId}" is not present in the active platform manifest.`);
27
+ }
28
+ return story;
29
+ }
30
+ openStory(storyId) {
31
+ return this.desktop.openStory(storyId);
32
+ }
33
+ async describe(options = {}) {
34
+ const depth = options.depth ?? 3;
35
+ const maxNodes = options.maxNodes ?? 100;
36
+ if (!Number.isInteger(depth) || depth < 0 || !Number.isInteger(maxNodes) || maxNodes < 1) {
37
+ throw new TypeError('Agent tree depth must be non-negative and maxNodes must be positive.');
38
+ }
39
+ const roots = await this.desktop.session.getTree();
40
+ const scoped = options.scope === 'story'
41
+ ? roots.flatMap((root) => findTreeNodes(root, (node) => node.testId === 'story-root' || (node.testId?.endsWith('-story-root') ?? false)))
42
+ : roots;
43
+ let remaining = maxNodes;
44
+ return scoped.flatMap((root) => {
45
+ const projected = projectTree(root, depth, () => remaining-- > 0);
46
+ return projected ? [projected] : [];
47
+ });
48
+ }
49
+ async find(selector) {
50
+ const element = await findDesktopElement(this.desktop.session, selector);
51
+ return {
52
+ accessibleName: await element.getAttribute('name'),
53
+ enabled: await element.isEnabled(),
54
+ id: element.id,
55
+ role: await element.getTagName(),
56
+ selected: await element.isSelected(),
57
+ text: await element.getText(),
58
+ };
59
+ }
60
+ async click(selector) {
61
+ await (await findDesktopElement(this.desktop.session, selector)).click();
62
+ }
63
+ async type(selector, text) {
64
+ await (await findDesktopElement(this.desktop.session, selector)).sendKeys(text);
65
+ }
66
+ async check(expectation) {
67
+ try {
68
+ await assertDesktopExpectation(this.desktop.session, expectation);
69
+ return { passed: true };
70
+ }
71
+ catch (error) {
72
+ if (error instanceof DesktopAssertionError || (error instanceof WebDriverError && error.code === 'no such element')) {
73
+ return { message: error.message, passed: false };
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+ async screenshot(name) {
79
+ const artifact = this.artifacts.writeScreenshot('agent', name, await this.desktop.session.takeScreenshot());
80
+ this.captured.push(artifact);
81
+ return artifact;
82
+ }
83
+ runStoryTest(storyId, testId) {
84
+ return this.desktop.runStoryTests({
85
+ artifactsRoot: this.artifacts.root,
86
+ selection: { story: storyId, test: testId },
87
+ });
88
+ }
89
+ getArtifacts() {
90
+ return [...this.captured];
91
+ }
92
+ delete() {
93
+ return this.desktop.delete();
94
+ }
95
+ }
96
+ export async function connectDesktopAgent(options) {
97
+ const artifacts = new ArtifactManager(options.artifactsRoot);
98
+ const desktop = await connectDesktopWebdriver(options);
99
+ return new DesktopAgent(desktop, artifacts);
100
+ }
101
+ function findTreeNodes(node, predicate) {
102
+ return [...(predicate(node) ? [node] : []), ...node.children.flatMap((child) => findTreeNodes(child, predicate))];
103
+ }
104
+ function projectTree(node, depth, take) {
105
+ if (!take()) {
106
+ return undefined;
107
+ }
108
+ return {
109
+ ...node,
110
+ children: depth === 0
111
+ ? []
112
+ : node.children.flatMap((child) => {
113
+ const projected = projectTree(child, depth - 1, take);
114
+ return projected ? [projected] : [];
115
+ }),
116
+ };
117
+ }
118
+ //# sourceMappingURL=DesktopAgent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopAgent.js","sourceRoot":"","sources":["../../src/agent/DesktopAgent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DesktopAgent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopAgent.test.d.ts","sourceRoot":"","sources":["../../src/agent/DesktopAgent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { createDesktopDriverClient } from '../client/DesktopDriverClient.js';
6
+ import { createDesktopDriverTestHarness } from '../testing/protocolHarness.js';
7
+ import { createDesktopDriverStoryHarness } from '../testing/protocolHarness.js';
8
+ import { connectDesktopAgent } from './DesktopAgent.js';
9
+ describe('DesktopAgent', () => {
10
+ test('lists, explains, inspects, acts, checks, captures, and runs the same authored plan', async () => {
11
+ const manifest = {
12
+ endpoint: 'windows',
13
+ entries: [
14
+ {
15
+ id: 'components-button--default',
16
+ name: 'Default',
17
+ packageName: '@fluentui-react-native/components',
18
+ sourcePath: 'src/components/button/button.stories.tsx',
19
+ tags: ['e2e', 'story'],
20
+ tests: {
21
+ version: 1,
22
+ tests: [
23
+ {
24
+ id: 'agent-plan',
25
+ steps: [{ expect: { state: 'enabled', target: { testId: 'button-primary' }, value: true } }],
26
+ },
27
+ ],
28
+ },
29
+ title: 'Components/Button',
30
+ },
31
+ ],
32
+ platformManifestDigest: 'platform-digest',
33
+ portablePlanDigest: 'portable-digest',
34
+ schemaVersion: 1,
35
+ };
36
+ const harness = await createDesktopDriverStoryHarness(manifest);
37
+ const artifactsRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-agent-'));
38
+ try {
39
+ const response = await runContract(harness.server.url, harness.target.id, artifactsRoot);
40
+ expect(response).toMatchObject({
41
+ check: { passed: true },
42
+ run: { status: 'passed', tests: [{ testId: 'agent-plan' }] },
43
+ screenshot: { kind: 'screenshot', name: 'agent-button' },
44
+ stories: 1,
45
+ tree: 1,
46
+ });
47
+ expect(fs.existsSync(path.join(artifactsRoot, 'run.json'))).toBe(true);
48
+ }
49
+ finally {
50
+ await harness.close();
51
+ fs.rmSync(artifactsRoot, { force: true, recursive: true });
52
+ }
53
+ });
54
+ test('validates the artifact root before reserving a target session', async () => {
55
+ const harness = await createDesktopDriverTestHarness();
56
+ const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-driver-agent-invalid-'));
57
+ const invalidRoot = path.join(temporaryDirectory, 'file');
58
+ fs.writeFileSync(invalidRoot, 'not a directory');
59
+ try {
60
+ await expect(connectDesktopAgent({
61
+ artifactsRoot: invalidRoot,
62
+ platformName: 'windows',
63
+ targetId: harness.target.id,
64
+ url: harness.server.url,
65
+ })).rejects.toThrow();
66
+ const client = createDesktopDriverClient({ url: harness.server.url });
67
+ const session = await client.newSession({
68
+ alwaysMatch: { platformName: 'windows', 'furn:target': harness.target.id },
69
+ });
70
+ await session.delete();
71
+ }
72
+ finally {
73
+ await harness.close();
74
+ fs.rmSync(temporaryDirectory, { force: true, recursive: true });
75
+ }
76
+ });
77
+ });
78
+ function runContract(url, targetId, artifactsRoot) {
79
+ return new Promise((resolve, reject) => {
80
+ const child = spawn(process.execPath, [path.join(__dirname, 'agent.contract.cjs'), url, targetId, artifactsRoot], {
81
+ stdio: ['ignore', 'pipe', 'pipe'],
82
+ });
83
+ const stdout = [];
84
+ const stderr = [];
85
+ child.stdout.on('data', (chunk) => stdout.push(chunk));
86
+ child.stderr.on('data', (chunk) => stderr.push(chunk));
87
+ child.once('error', reject);
88
+ child.once('exit', (code) => {
89
+ if (code !== 0) {
90
+ reject(new Error(Buffer.concat(stderr).toString('utf8') || `Desktop agent exited with code ${code}.`));
91
+ return;
92
+ }
93
+ resolve(JSON.parse(Buffer.concat(stdout).toString('utf8')));
94
+ });
95
+ });
96
+ }
97
+ //# sourceMappingURL=DesktopAgent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesktopAgent.test.js","sourceRoot":"","sources":["../../src/agent/DesktopAgent.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAE7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,QAAQ,GAAyB;YACrC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE;gBACP;oBACE,EAAE,EAAE,4BAA4B;oBAChC,IAAI,EAAE,SAAS;oBACf,WAAW,EAAE,mCAAmC;oBAChD,UAAU,EAAE,0CAA0C;oBACtD,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC;oBACtB,KAAK,EAAE;wBACL,OAAO,EAAE,CAAC;wBACV,KAAK,EAAE;4BACL;gCACE,EAAE,EAAE,YAAY;gCAChB,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;6BAC7F;yBACF;qBACF;oBACD,KAAK,EAAE,mBAAmB;iBAC3B;aACF;YACD,sBAAsB,EAAE,iBAAiB;YACzC,kBAAkB,EAAE,iBAAiB;YACrC,aAAa,EAAE,CAAC;SACjB,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,+BAA+B,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,aAAa,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;YACzF,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC;gBAC7B,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;gBACvB,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,EAAE;gBAC5D,UAAU,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE;gBACxD,OAAO,EAAE,CAAC;gBACV,IAAI,EAAE,CAAC;aACR,CAAC,CAAC;YACH,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,OAAO,GAAG,MAAM,8BAA8B,EAAE,CAAC;QACvD,MAAM,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,+BAA+B,CAAC,CAAC,CAAC;QACnG,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,CACV,mBAAmB,CAAC;gBAClB,aAAa,EAAE,WAAW;gBAC1B,YAAY,EAAE,SAAS;gBACvB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;gBAC3B,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG;aACxB,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAEpB,MAAM,MAAM,GAAG,yBAAyB,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YACtE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;gBACtC,WAAW,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE;aAC3E,CAAC,CAAC;YACH,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QACzB,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,EAAE,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,GAAW,EAAE,QAAgB,EAAE,aAAqB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE;YAChH,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,kCAAkC,IAAI,GAAG,CAAC,CAAC,CAAC;gBACvG,OAAO;YACT,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA4B,CAAC,CAAC;QACzF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}