Skip to content

Infrastructure: Land the desktop test driver on main #4258

Description

@JasonVMo

Summary

Land the existing @fluentui-react-native/desktop-driver branch as a supported
workspace package. Rebase and repath the implementation onto current main,
reconcile its Storybook integration and project references, and preserve the
existing Windows smoke harness until the portable suite has real-platform
parity.

The branch already contains the driver, design documentation, contract tests, a
fake backend, WebdriverIO integration, Storybook controls, and a Button
proof-of-concept. Real-hardware verification and the release gate are tracked
separately.

Goal

Merge @fluentui-react-native/desktop-driver from
origin/user/jasonvmo/test-driver into main as a buildable, tested, documented
workspace package, with the apps/storybook integration wired up, so that a single
WebdriverIO spec can be authored once and executed against React Native desktop
applications through an owned, loopback-only driver host.

Stage

Stage 1 - Foundations. Beta requirement. Everything else in this workstream
consumes this package's configuration factory, story-test manifest, and artifact
contract.

Observed. The branch is a substantial, self-documented implementation: 89 files
and +12,361 / -28 lines relative to its merge base
29f00b32,
tip 8f971021.
It ships the package itself, four long-form design documents, ~20 unit/contract test
files, a fake backend, the apps/storybook WDIO integration and on-device controls,
a Button story-test proof of concept, a changeset, and root tsconfig.json
project references.

This task is the landing work: rebase and path-migrate the branch, reconcile it
with the repository's current layout and build graph, and merge it without
regressing the existing Windows smoke harness. Proving it on real hardware and
passing its release gate is tracked separately in
the release-readiness task.

Why it matters

Observed. The repository has one desktop assertion suite for the agentic
Storybook app, it is Windows-only, and it is written against
@react-native-windows/automation inside a Jest environment
(apps/storybook/jest.windows.config.cjs,
apps/storybook/windows-tests/storybook-smoke.test.cjs).
It covers three smoke stories plus eleven focus tests added by
4777e54f.
The separate legacy suite in apps/E2E targets the FluentTester
apps with per-platform WDIO configs and per-platform spec files
(*.macos.ts, *.win.ts, *.windows.ts), so the same behavior is written more than
once.

Inferred. Without a shared driver, every new desktop endpoint (including the
Win32 Paper endpoint in win32-storybook.md) multiplies test
source rather than reusing it, and each endpoint re-invents application lifecycle,
readiness, and artifact handling. The branch's central claim - one spec, one
manifest digest, multiple platforms - is what makes desktop coverage scale.

Observed current state

Branch architecture

Observed (branch
DESIGN.md):

WebdriverIO runner or standalone client
                |
                | W3C WebDriver over loopback
                v
       owned single-driver host (child process)
          /                 \
   Mac2Driver          WindowsDriver
       |                    |
WebDriverAgentMac      WinAppDriver
                |
                v
       React Native application
  • The driver host is a child process that binds loopback, constructs exactly one
    native driver, and keeps Appium driver classes and base-driver types from crossing
    the process boundary. WebdriverIO sees a plain WebDriver endpoint.
  • The package does not run the Appium CLI, extension manager, or multi-driver
    router; it reuses appium-mac2-driver and appium-windows-driver, with the
    Appium driver-author imports isolated in src/driver-host/backends.ts.
  • Primary integration is createDesktopWdioConfig(), which validates configuration,
    selects a backend, builds protected backend capabilities, registers launcher and
    worker services, groups specs into one warm session, adds the browser.desktop
    command set, and composes consumer hooks/reporters.
  • Default sessionStrategy: 'suite' - one worker, one warm session, because the
    desktop is a single shared resource.

Ownership and lifecycle model

Observed. DesktopAppTarget is a discriminated union of launch and attach.
Only launch grants permission to terminate the application; attach records the
app and window as external and must leave them running. Every owned PID, port,
window, endpoint, and session is written to ownership.json; cleanup targets exact
recorded resources, never a process name, uses a bounded deadline, and preserves
cleanup failures alongside the primary failure. Ownership-sensitive capabilities
cannot be overridden by consumers.

Observed. Lifecycle states are
created -> starting|attaching -> connected -> ready -> stopping -> stopped with
exited, crashed, and timed_out terminal branches, and normalized events
(launchRequested, driverHostStarted, processStarted, windowDiscovered,
webDriverSessionCreated, ready, exitObserved, crashObserved,
shutdownRequested, shutdownCompleted, monitorError).

Observed. Windows attach resolves non-handle targets through a temporary
root-desktop session that enumerates top-level windows; ambiguity is always an
error, and window handles are never cached across runs. macOS attach currently
requires the bundle identifier in identity; PID, title, and window selectors are
rejected at runtime.

Platform and backend decisions

Observed (branch src/types.ts):

export type DesktopPlatform = 'macos' | 'windows' | 'fake';
export type DesktopBackendId = 'mac2' | 'windows' | 'novawindows' | 'fake';
  • mac2 (appium-mac2-driver over WebDriverAgentMac) and windows
    (appium-windows-driver over WinAppDriver) are the supported native backends.
  • fake is an in-process deterministic contract backend used to exercise the host,
    commands, Storybook controller, service, generated plans, lifecycle, and artifact
    pipeline with no GUI. The branch is explicit that it "does not prove native driver
    behavior and cannot substitute for the identical suite on Windows and macOS".
  • novawindows exists as an identifier but is Observed to be declared
    unsupported: "Keep NovaWindows unavailable until its dependency resolves and the
    real-backend contract passes."
  • Observed: there is no win32 platform or backend. The Win32 Paper endpoint is
    therefore new work for the driver as well as for Storybook - see
    win32-storybook.md.
  • Observed rationale recorded on the branch: direct WinAppDriver access loses
    appium-windows-driver compatibility shims; a private Appium server is
    decision-gated; agent-device has no Windows backend; MCP is not the internal
    transport.

Public API surface

Observed (branch package.json): @fluentui-react-native/desktop-driver@0.1.0,
"type": "module", bin = ./lib/cli/main.js, engines.node >= 20.19.0,
dependency @babel/parser, peer webdriverio ^9.0.0 (optional), optional
dependencies appium, appium-mac2-driver, appium-windows-driver (catalog).

Export Responsibility
. Portable selectors (byTestId), Storybook story helper, plan and lifecycle/result types
./wdio createDesktopWdioConfig(), WebdriverIO services, startDesktopDriver() standalone lifecycle
./storybook Manifest generation, channel controller, loopback run service
./cli JSON command-line interface (main)
./macos, ./windows Explicit platform-only execute extensions (non-portable by construction)
./driver-host Internal host integration contract

Observed CLI surface: desktop-driver doctor, stories generate,
stories list, serve, start, version.

Observed portable command matrix (versioned, reported at runtime by
browser.desktop.getSessionInfo()): findElement, findElements, isExisting,
isDisplayed, isEnabled, isSelected, click, clearValue, setValue,
getText, getValue, waitForDisplayed, waitForExist, getPageSource,
takeScreenshot via standard WebdriverIO, plus isFocused and scrollIntoView
via browser.desktop because WebdriverIO implements those with DOM scripts that no
native desktop driver can execute. A missing portable capability is an
infrastructure error, never a silent skip.

Observed added browser commands: getSessionInfo, waitForAppState,
captureArtifacts, selectStory, waitForStory, isFocused, scrollIntoView.

Storybook integration on the branch

Observed. A story opts in with parameters.desktopTest, either an inline
serializable plan or a link to a colocated *.desktop.spec.ts.
desktop-driver stories generate statically parses story source and emits
story-tests.manifest.json (stable story IDs, tags, resolved spec paths, exact
Mocha grep, executable-content digest) and story-plans.generated.spec.ts. The app
never receives or evaluates test code.

Observed. desktop-driver serve runs a loopback HTTP service
(GET /v1/health, GET /v1/stories, POST /v1/runs, GET /v1/runs/:id,
GET /v1/runs/:id/events, POST /v1/runs/:id/cancel) with a per-boot token, one
mutating run at a time, and manifest-allowlisted story IDs. It announces
{ url, token, protocolVersion, manifestDigest } over the existing Storybook
channel, which is what lets the on-device
DesktopTestControls.tsx / useDesktopTestService.ts render Run current test,
Run all tests, and Cancel without build-time configuration.

Observed apps/storybook additions on the branch: wdio.conf.ts,
desktop-tests/desktop-driver.mjs, desktop-tests/fake-scene.json, .gitignore
entries for desktop-tests/generated/, and scripts desktop:doctor,
desktop:generate, desktop:test, desktop:test:fake, desktop:test:macos,
desktop:test:windows, desktop:service, desktop:service:macos,
desktop:service:windows. wdio.conf.ts defaults to attach with window title
Agentic Components Storybook so a run never terminates the app that invoked it.

Observed. desktop-tests/desktop-driver.mjs exists because the optional
webdriverio peer causes Yarn to virtualize the workspace and the pnpm linker does
not materialize a virtualized workspace's bin; the CLI is invoked through its
module entry instead.

Observed proof of concept: button.stories.tsx gains Default with an inline
plan (button-default) and Interaction linked to
button.desktop.spec.ts ([story:components-button--interaction]), including an
explicit accessibilityLabel because React Native Windows publishes a Button
labelled only through content with an empty UI Automation Name.

Implemented versus remaining

Implemented on the branch (Observed): package and build config; single-driver
host with mac2, windows, and fake backends; W3C loopback server; WDIO config
factory, launcher/worker services, and command augmentation; portable command matrix
and capability reporting; launch/attach ownership, process supervision, and
ownership.json; window discovery for Windows; lifecycle events, JUnit, run
reports, and artifact capture with redaction; Storybook manifest generation,
controller, run executor, and loopback service; CLI; on-device controls; ~20 test
files plus a fake-scene contract fixture; four documentation files; a changeset.

Remaining (Observed, from the branch's own
NEXT-STEPS.md):

Priority 0 real-platform proof on macOS and Windows; Priority 1
failure-normalization and report/artifact integrity; Priority 2 transport and
runtime hardening; Priority 3 consolidation; 14 open decisions; and a 7-item
release gate. All of that is tracked in
test-driver-release-readiness.md.

Superseded by current main (Observed). The branch's NEXT-STEPS.md also lists
a "Blocking application defect": an agentic Button crash that terminated
ReactApp.exe on click. That is a historical branch observation recorded at tip
8f971021.
origin/main now contains
4777e54f
"Fix windows crash in components"
(PR #4208), whose
changeset reads "Render persistent single- and dual-ring focus visuals across
agentic components to avoid React Native Windows Fabric crashes". Inferred:
the landing rebase in this task integrates that fix, so no new diagnosis,
crash-dump collection, or defect-filing work is required; the residual work is a
Windows rerun, tracked in
test-driver-release-readiness.md.

Remaining, landing-specific (Observed):

  • The branch is not rebased: merge base
    29f00b32
    versus origin/main
    51fab45c.
  • The branch still targets the pre-repath layout. git ls-tree origin/user/jasonvmo/test-driver packages/agentic/
    returns only packages/agentic/test-driver, while origin/main has
    packages/agentic/components and packages/agentic/design. Every branch
    reference to packages/agentic-components/src - in wdio.conf.ts, the
    desktop:generate script, apps/storybook/README.md, and the root
    tsconfig.json reference - must be migrated to packages/agentic/components.
  • The branch adds no CI configuration: its diffstat contains no
    .github/workflows or .ado changes.

Release and CI gates declared by the branch

Observed release gate, verbatim in substance from NEXT-STEPS.md:

  1. Run one unchanged shared spec manifest on Windows and macOS.
  2. Compare manifest digest, test IDs, and portable command-matrix version.
  3. Prove launch ownership, attach preservation, unexpected termination,
    cancellation, and bounded cleanup on both platforms.
  4. Complete the Priority 1 reporting work so every failure surface produces truthful
    structured output.
  5. Validate a clean external consumer with no globally installed Appium driver.
  6. Document verified native dependency and platform compatibility ranges.
  7. Add the required changeset and publishing metadata.

Observed validation strategy declared by the branch: unit tests, fake-backend
contract tests, shared real-platform tests with identical digests, and
platform-specific integration tests; a portable command is added only after one
unchanged contract assertion passes on both real platforms; platform jobs remain
serial until isolated multi-session execution is designed and measured.

Scope

  • Rebase origin/user/jasonvmo/test-driver onto current main and resolve the
    packages/agentic-components -> packages/agentic/components path migration
    across the package, apps/storybook/wdio.conf.ts, apps/storybook/package.json
    scripts, apps/storybook/README.md, apps/storybook/AGENTS.md, and root
    tsconfig.json references.
  • Verify the package joins the unified tsc -b project-references graph and that
    its Jest tests run under yarn lage test.
  • Verify yarn lint, yarn format:check, depcheck, knip, check-publishing,
    and lint-repo pass for the new package and the modified app.
  • Keep the existing windows:test / windows:agent smoke harness working and
    document the port and command conflict with desktop:service.
  • Land the Button story-test proof of concept and the changeset.
  • Reconcile the branch's Button story additions with the current-main Button
    implementation, which changed in
    4777e54f
    (role: 'button' instead of accessibilityRole, enableFocusRing: false, and
    FocusVisual-based focus rendering), and update NEXT-STEPS.md at merge so it no
    longer describes the resolved crash as blocking.
  • Land the four package documents (README.md, USAGE.md, DESIGN.md,
    NEXT-STEPS.md) and keep NEXT-STEPS.md accurate at merge time.
  • Decide and document the package's final location and name (currently
    packages/agentic/test-driver publishing as
    @fluentui-react-native/desktop-driver) before merge, because renaming after
    publication is a breaking change.

Out of scope

  • Real-platform verification, Priority 1/2/3 work, and the release gate - see
    test-driver-release-readiness.md.
  • Any CI job definition - see storybook-e2e.md.
  • A win32 platform or backend - see win32-storybook.md.
  • Mobile platforms, browsers, React Native Web, visual-diff approval
    infrastructure, and device-farm scheduling; Observed: the branch explicitly
    excludes all of these.
  • Migrating apps/E2E or the FluentTester suites onto the new
    driver.

Deliverables

  1. packages/agentic/test-driver merged to main with its documentation set.
  2. Root tsconfig.json reference and yarn lage task participation.
  3. apps/storybook integration: wdio.conf.ts, desktop-tests/desktop-driver.mjs,
    desktop-tests/fake-scene.json, the desktop:* scripts, and .gitignore
    entries for generated manifests and artifacts.
  4. On-device DesktopTestControls wired into StorybookApp.tsx.
  5. Button story tests (one inline plan, one linked spec) as the reference example.
  6. Updated apps/storybook/README.md and apps/storybook/AGENTS.md covering the
    desktop story-test workflow, attach-by-default rule, and generated/ignored paths.
  7. A changeset covering the new package and the components change.

Acceptance criteria

  • The package builds from a clean tree with the root yarn build (tsc -b) and
    exposes lib/index.js, lib/wdio/index.js, lib/storybook/index.js,
    lib/cli/main.js, lib/driver-host/index.js, lib/platforms/macos.js, and
    lib/platforms/windows.js matching its exports map.
  • yarn lage test runs the package's Jest suite and it passes with no
    packages/agentic-components path references remaining anywhere in the diff.
  • yarn lage lint, yarn format:check, yarn lint-repo, and
    yarn check-publishing pass.
  • yarn workspace @fluentui-react-native/agentic-components-storybook desktop:generate
    produces desktop-tests/generated/story-tests.manifest.json and
    story-plans.generated.spec.ts, both git-ignored.
  • desktop:test:fake passes end to end with no native driver, no application,
    and no GUI.
  • desktop:doctor --platform macos and --platform windows each report backend
    availability and prerequisites, and report unverified prerequisites as
    unknown rather than satisfied.
  • yarn windows:test (the existing @react-native-windows/automation smoke
    harness) still passes unchanged on a Windows machine.
  • The README/AGENTS documentation states that specs must not branch on platform
    or import a platform extension, that attach is the default, and that
    desktop-tests/generated and artifacts/desktop-tests are never committed.
  • A changeset exists and yarn change:check passes.
  • NEXT-STEPS.md at merge accurately reflects unfinished work - specifically,
    the "Blocking application defect" section is removed or rewritten to reference
    4777e54f
    and the outstanding Windows rerun - and the package README.md still marks
    the package status as alpha.

Dependencies and ordering

  • Depends on: nothing else in this workstream.
  • Blocks: test-driver-release-readiness.md,
    storybook-e2e.md, and the automation portion of
    win32-storybook.md.
  • Cross-workstream: story-test authoring conventions land in the agentic
    components package, so the Components workstream's story and component-test work
    should adopt parameters.desktopTest rather than inventing a parallel mechanism.
  • Ordering note (Inferred): land behind, not instead of, the existing Windows
    smoke harness. Replacing it in the same change would remove the only currently
    working Windows validation path before the replacement has real-platform evidence.

Risks and open decisions

Item Evidence Decision needed
Rebase cost Observed: branch is 15 commits off a merge base that main has moved past, and the repository was repathed (packages/agentic-components -> packages/agentic/components) in between. Rebase versus re-land as a fresh squashed change against current main.
Branch Button edits versus the landed fix Observed: the branch edits button.stories.tsx; current main edits both button.stories.tsx and useButton.ts in 4777e54f, and adds focus-visual plus eleven component migrations. Expect merge conflicts in the Button story and component files; keep main's fix and re-apply the branch's story-test declarations on top.
Package location and name Observed: directory packages/agentic/test-driver, published name @fluentui-react-native/desktop-driver. Confirm both before the first publish; renaming later breaks consumers.
Optional-peer virtualization workaround Observed: desktop-tests/desktop-driver.mjs exists solely because the pnpm linker does not materialize a virtualized workspace's bin. Accept the shim, or make webdriverio a non-optional peer/dependency and drop it.
Appium driver-author API Observed: the host uses routeConfiguringFunction / server from appium/driver.js, which the branch notes is deprecated for Appium 4. Keep the isolation boundary and defer; revisit under the release-readiness task.
Two Windows automation harnesses Observed: branch documentation says the two "must not be run at the same time" and that desktop-driver "replaces the smoke harness once it reaches parity". Retain both with documented separation for beta; decide retirement for production.
terminateLaunchedApp remaining public Observed: listed as an open decision on the branch, recommendation is to deprecate. Resolve before the public API is published.
Node engine floor Observed: engines.node >= 20.19.0; CI workflows use node-version: 22. No conflict observed; confirm no consumer requires an older Node.

Evidence and references

Retrieved 2026-08-20.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions