@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.
- package/.cache/tsconfig.tsbuildinfo +1 -0
- package/AGENTS.md +68 -0
- package/CHANGELOG.md +16 -0
- package/PLAN.md +1394 -0
- package/README.md +223 -0
- package/SPEC.md +105 -0
- package/config/cli.cjs +8 -0
- package/jest.config.cjs +9 -0
- package/lib/agent/DesktopAgent.d.ts +56 -0
- package/lib/agent/DesktopAgent.d.ts.map +1 -0
- package/lib/agent/DesktopAgent.js +118 -0
- package/lib/agent/DesktopAgent.js.map +1 -0
- package/lib/agent/DesktopAgent.test.d.ts +2 -0
- package/lib/agent/DesktopAgent.test.d.ts.map +1 -0
- package/lib/agent/DesktopAgent.test.js +97 -0
- package/lib/agent/DesktopAgent.test.js.map +1 -0
- package/lib/agent/agent.contract.cjs +66 -0
- package/lib/agent/agent.contract.cjs.map +1 -0
- package/lib/agent/agent.contract.d.cts +2 -0
- package/lib/agent/agent.contract.d.cts.map +1 -0
- package/lib/agent/index.d.ts +3 -0
- package/lib/agent/index.d.ts.map +1 -0
- package/lib/agent/index.js +2 -0
- package/lib/agent/index.js.map +1 -0
- package/lib/artifacts/ArtifactManager.d.ts +13 -0
- package/lib/artifacts/ArtifactManager.d.ts.map +1 -0
- package/lib/artifacts/ArtifactManager.js +88 -0
- package/lib/artifacts/ArtifactManager.js.map +1 -0
- package/lib/artifacts/ArtifactManager.test.d.ts +2 -0
- package/lib/artifacts/ArtifactManager.test.d.ts.map +1 -0
- package/lib/artifacts/ArtifactManager.test.js +19 -0
- package/lib/artifacts/ArtifactManager.test.js.map +1 -0
- package/lib/artifacts/index.d.ts +2 -0
- package/lib/artifacts/index.d.ts.map +1 -0
- package/lib/artifacts/index.js +2 -0
- package/lib/artifacts/index.js.map +1 -0
- package/lib/authoring/index.d.ts +4 -0
- package/lib/authoring/index.d.ts.map +1 -0
- package/lib/authoring/index.js +2 -0
- package/lib/authoring/index.js.map +1 -0
- package/lib/authoring/results.d.ts +43 -0
- package/lib/authoring/results.d.ts.map +1 -0
- package/lib/authoring/results.js +2 -0
- package/lib/authoring/results.js.map +1 -0
- package/lib/authoring/storyTests.d.ts +81 -0
- package/lib/authoring/storyTests.d.ts.map +1 -0
- package/lib/authoring/storyTests.js +272 -0
- package/lib/authoring/storyTests.js.map +1 -0
- package/lib/authoring/storyTests.test.d.ts +2 -0
- package/lib/authoring/storyTests.test.d.ts.map +1 -0
- package/lib/authoring/storyTests.test.js +46 -0
- package/lib/authoring/storyTests.test.js.map +1 -0
- package/lib/cli/DesktopDriverCli.test.d.ts +2 -0
- package/lib/cli/DesktopDriverCli.test.d.ts.map +1 -0
- package/lib/cli/DesktopDriverCli.test.js +141 -0
- package/lib/cli/DesktopDriverCli.test.js.map +1 -0
- package/lib/cli/createDesktopDriverCommand.d.ts +8 -0
- package/lib/cli/createDesktopDriverCommand.d.ts.map +1 -0
- package/lib/cli/createDesktopDriverCommand.js +242 -0
- package/lib/cli/createDesktopDriverCommand.js.map +1 -0
- package/lib/cli/index.d.ts +3 -0
- package/lib/cli/index.d.ts.map +1 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/client/DesktopDriverClient.d.ts +60 -0
- package/lib/client/DesktopDriverClient.d.ts.map +1 -0
- package/lib/client/DesktopDriverClient.js +155 -0
- package/lib/client/DesktopDriverClient.js.map +1 -0
- package/lib/client/index.d.ts +3 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +2 -0
- package/lib/client/index.js.map +1 -0
- package/lib/host/types.d.ts +153 -0
- package/lib/host/types.d.ts.map +1 -0
- package/lib/host/types.js +2 -0
- package/lib/host/types.js.map +1 -0
- package/lib/hosts/fake/FakeDesktopHost.d.ts +72 -0
- package/lib/hosts/fake/FakeDesktopHost.d.ts.map +1 -0
- package/lib/hosts/fake/FakeDesktopHost.js +452 -0
- package/lib/hosts/fake/FakeDesktopHost.js.map +1 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -0
- package/lib/protocol/actions.d.ts +11 -0
- package/lib/protocol/actions.d.ts.map +1 -0
- package/lib/protocol/actions.js +137 -0
- package/lib/protocol/actions.js.map +1 -0
- package/lib/protocol/capabilities.d.ts +11 -0
- package/lib/protocol/capabilities.d.ts.map +1 -0
- package/lib/protocol/capabilities.js +103 -0
- package/lib/protocol/capabilities.js.map +1 -0
- package/lib/protocol/constants.d.ts +2 -0
- package/lib/protocol/constants.d.ts.map +1 -0
- package/lib/protocol/constants.js +2 -0
- package/lib/protocol/constants.js.map +1 -0
- package/lib/protocol/errors.d.ts +37 -0
- package/lib/protocol/errors.d.ts.map +1 -0
- package/lib/protocol/errors.js +66 -0
- package/lib/protocol/errors.js.map +1 -0
- package/lib/protocol/timeouts.d.ts +2 -0
- package/lib/protocol/timeouts.d.ts.map +1 -0
- package/lib/protocol/timeouts.js +23 -0
- package/lib/protocol/timeouts.js.map +1 -0
- package/lib/protocol/types.d.ts +46 -0
- package/lib/protocol/types.d.ts.map +1 -0
- package/lib/protocol/types.js +2 -0
- package/lib/protocol/types.js.map +1 -0
- package/lib/runner/StoryTestRunner.d.ts +38 -0
- package/lib/runner/StoryTestRunner.d.ts.map +1 -0
- package/lib/runner/StoryTestRunner.js +497 -0
- package/lib/runner/StoryTestRunner.js.map +1 -0
- package/lib/runner/StoryTestRunner.test.d.ts +2 -0
- package/lib/runner/StoryTestRunner.test.d.ts.map +1 -0
- package/lib/runner/StoryTestRunner.test.js +349 -0
- package/lib/runner/StoryTestRunner.test.js.map +1 -0
- package/lib/runner/index.d.ts +3 -0
- package/lib/runner/index.d.ts.map +1 -0
- package/lib/runner/index.js +2 -0
- package/lib/runner/index.js.map +1 -0
- package/lib/server/SessionManager.d.ts +52 -0
- package/lib/server/SessionManager.d.ts.map +1 -0
- package/lib/server/SessionManager.js +208 -0
- package/lib/server/SessionManager.js.map +1 -0
- package/lib/server/TargetRegistry.d.ts +9 -0
- package/lib/server/TargetRegistry.d.ts.map +1 -0
- package/lib/server/TargetRegistry.js +24 -0
- package/lib/server/TargetRegistry.js.map +1 -0
- package/lib/server/createDesktopDriverServer.d.ts +19 -0
- package/lib/server/createDesktopDriverServer.d.ts.map +1 -0
- package/lib/server/createDesktopDriverServer.js +609 -0
- package/lib/server/createDesktopDriverServer.js.map +1 -0
- package/lib/server/desktopDriver.test.d.ts +2 -0
- package/lib/server/desktopDriver.test.d.ts.map +1 -0
- package/lib/server/desktopDriver.test.js +389 -0
- package/lib/server/desktopDriver.test.js.map +1 -0
- package/lib/server/index.d.ts +6 -0
- package/lib/server/index.d.ts.map +1 -0
- package/lib/server/index.js +4 -0
- package/lib/server/index.js.map +1 -0
- package/lib/server/webdriverio.contract.cjs +35 -0
- package/lib/server/webdriverio.contract.cjs.map +1 -0
- package/lib/server/webdriverio.contract.d.cts +2 -0
- package/lib/server/webdriverio.contract.d.cts.map +1 -0
- package/lib/server/webdriverio.test.d.ts +2 -0
- package/lib/server/webdriverio.test.d.ts.map +1 -0
- package/lib/server/webdriverio.test.js +40 -0
- package/lib/server/webdriverio.test.js.map +1 -0
- package/lib/storybook.d.ts +36 -0
- package/lib/storybook.d.ts.map +1 -0
- package/lib/storybook.js +2 -0
- package/lib/storybook.js.map +1 -0
- package/lib/testing/FakeStoryOrchestrator.d.ts +21 -0
- package/lib/testing/FakeStoryOrchestrator.d.ts.map +1 -0
- package/lib/testing/FakeStoryOrchestrator.js +44 -0
- package/lib/testing/FakeStoryOrchestrator.js.map +1 -0
- package/lib/testing/fakeStoryElements.d.ts +4 -0
- package/lib/testing/fakeStoryElements.d.ts.map +1 -0
- package/lib/testing/fakeStoryElements.js +143 -0
- package/lib/testing/fakeStoryElements.js.map +1 -0
- package/lib/testing/fakeStoryElements.test.d.ts +2 -0
- package/lib/testing/fakeStoryElements.test.d.ts.map +1 -0
- package/lib/testing/fakeStoryElements.test.js +38 -0
- package/lib/testing/fakeStoryElements.test.js.map +1 -0
- package/lib/testing/index.d.ts +7 -0
- package/lib/testing/index.d.ts.map +1 -0
- package/lib/testing/index.js +5 -0
- package/lib/testing/index.js.map +1 -0
- package/lib/testing/protocolHarness.d.ts +17 -0
- package/lib/testing/protocolHarness.d.ts.map +1 -0
- package/lib/testing/protocolHarness.js +46 -0
- package/lib/testing/protocolHarness.js.map +1 -0
- package/lib/wdio/DesktopWebdriver.d.ts +47 -0
- package/lib/wdio/DesktopWebdriver.d.ts.map +1 -0
- package/lib/wdio/DesktopWebdriver.js +76 -0
- package/lib/wdio/DesktopWebdriver.js.map +1 -0
- package/lib/wdio/DesktopWebdriver.test.d.ts +2 -0
- package/lib/wdio/DesktopWebdriver.test.d.ts.map +1 -0
- package/lib/wdio/DesktopWebdriver.test.js +74 -0
- package/lib/wdio/DesktopWebdriver.test.js.map +1 -0
- package/lib/wdio/index.d.ts +3 -0
- package/lib/wdio/index.d.ts.map +1 -0
- package/lib/wdio/index.js +2 -0
- package/lib/wdio/index.js.map +1 -0
- package/lib/wdio/wdioRunner.contract.cjs +64 -0
- package/lib/wdio/wdioRunner.contract.cjs.map +1 -0
- package/lib/wdio/wdioRunner.contract.d.cts +2 -0
- package/lib/wdio/wdioRunner.contract.d.cts.map +1 -0
- package/package.json +96 -0
- package/src/agent/DesktopAgent.test.ts +102 -0
- package/src/agent/DesktopAgent.ts +170 -0
- package/src/agent/agent.contract.cjs +32 -0
- package/src/agent/index.ts +8 -0
- package/src/artifacts/ArtifactManager.test.ts +20 -0
- package/src/artifacts/ArtifactManager.ts +105 -0
- package/src/artifacts/index.ts +1 -0
- package/src/authoring/index.ts +26 -0
- package/src/authoring/results.ts +47 -0
- package/src/authoring/storyTests.test.ts +58 -0
- package/src/authoring/storyTests.ts +345 -0
- package/src/cli/DesktopDriverCli.test.ts +150 -0
- package/src/cli/createDesktopDriverCommand.ts +290 -0
- package/src/cli/index.ts +2 -0
- package/src/client/DesktopDriverClient.ts +218 -0
- package/src/client/index.ts +2 -0
- package/src/host/types.ts +158 -0
- package/src/hosts/fake/FakeDesktopHost.ts +558 -0
- package/src/index.ts +101 -0
- package/src/protocol/actions.ts +163 -0
- package/src/protocol/capabilities.ts +129 -0
- package/src/protocol/constants.ts +1 -0
- package/src/protocol/errors.ts +76 -0
- package/src/protocol/timeouts.ts +26 -0
- package/src/protocol/types.ts +55 -0
- package/src/runner/StoryTestRunner.test.ts +362 -0
- package/src/runner/StoryTestRunner.ts +624 -0
- package/src/runner/index.ts +9 -0
- package/src/server/SessionManager.ts +302 -0
- package/src/server/TargetRegistry.ts +29 -0
- package/src/server/createDesktopDriverServer.ts +775 -0
- package/src/server/desktopDriver.test.ts +421 -0
- package/src/server/index.ts +5 -0
- package/src/server/webdriverio.contract.cjs +38 -0
- package/src/server/webdriverio.test.ts +41 -0
- package/src/storybook.ts +40 -0
- package/src/testing/FakeStoryOrchestrator.ts +55 -0
- package/src/testing/fakeStoryElements.test.ts +40 -0
- package/src/testing/fakeStoryElements.ts +167 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/protocolHarness.ts +66 -0
- package/src/wdio/DesktopWebdriver.test.ts +76 -0
- package/src/wdio/DesktopWebdriver.ts +130 -0
- package/src/wdio/index.ts +2 -0
- package/src/wdio/wdioRunner.contract.cjs +30 -0
- 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)
|