Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/better-cases-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@fluentui-react-native/storybook-desktop-runtime": patch
"@fluentui-react-native/storybook-desktop": patch
"@fluentui-react-native/desktop-driver": patch
"@fluentui-react-native/components": patch
"@fluentui-react-native/focus-zone": patch
---

Add the native macOS and Windows implementations, explicit native build
verification, and prebuilt-only Storybook PR pipeline integration for the
desktop-driver package. Keep the FocusZone Windows WinMD compatible with the
consuming Storybook application's target SDK.
16 changes: 16 additions & 0 deletions .changeset/quiet-drivers-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@fluentui-react-native/desktop-driver": minor
"@fluentui-react-native/components": patch
"@fluentui-react-native/storybook-desktop": minor
"@fluentui-react-native/storybook-desktop-runtime": patch
---

Build the source-shipped native desktop helper explicitly, reuse verified
content-addressed artifacts, and add the Windows/Win32 C++ and macOS Swift
providers. Storybook can build the helper independently, ensures it during
prep, and attaches authored smoke tests to the exact app process it launched.
macOS cache resolution pins stable signatures to the leaf certificate and
designated requirement, makes source builds reproducible, verifies Hardened
Runtime and secure timestamps, reports TCC/AX diagnostics, and normalizes Fabric
accessibility roles, window identity, input, and Retina ScreenCaptureKit
evidence.
195 changes: 195 additions & 0 deletions .github/actions/setup-desktop-driver/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
name: Set up Desktop Driver
description: Build and verify the native Desktop Driver helper for a desktop endpoint.

inputs:
platform:
description: Desktop endpoint to provision.
required: true
working-directory:
description: Workspace that exposes the desktop-driver script.
required: false
default: apps/storybook
run-native-contract:
description: Run the target-OS native package contract before provisioning.
required: false
default: 'false'
macos-signing-identity:
description: Pre-provisioned macOS code-signing identity. Leave empty for an ad hoc CI signature.
required: false
default: ''
disabled-input-features:
description: Comma-separated input features unavailable on the runner.
required: false
default: ''

outputs:
cache-root:
description: Job-local native helper cache.
value: ${{ steps.configure-macos.outputs.cache-root || steps.configure-windows.outputs.cache-root }}
doctor-path:
description: Native helper diagnostic report.
value: ${{ steps.build-macos.outputs.doctor-path || steps.build-windows.outputs.doctor-path }}

runs:
using: composite
steps:
- name: Validate Desktop Driver platform
shell: bash
env:
DRIVER_PLATFORM: ${{ inputs.platform }}
run: |
set -euo pipefail

case "$DRIVER_PLATFORM:$RUNNER_OS" in
macos:macOS|windows:Windows|win32:Windows) ;;
macos:*|windows:*|win32:*)
echo "Desktop Driver endpoint '$DRIVER_PLATFORM' cannot run on '$RUNNER_OS'." >&2
exit 1
;;
*)
echo "Unsupported Desktop Driver endpoint '$DRIVER_PLATFORM'." >&2
exit 1
;;
esac

- name: Configure macOS Desktop Driver
id: configure-macos
if: inputs.platform == 'macos'
shell: bash
env:
DISABLED_INPUT_FEATURES: ${{ inputs.disabled-input-features }}
MACOS_SIGNING_IDENTITY: ${{ inputs.macos-signing-identity }}
run: |
set -euo pipefail

cache_root="$RUNNER_TEMP/furn-desktop-driver-native"
mkdir -p "$cache_root"
echo "FURN_DESKTOP_DRIVER_CACHE_ROOT=$cache_root" >> "$GITHUB_ENV"
echo "cache-root=$cache_root" >> "$GITHUB_OUTPUT"

echo "FURN_DESKTOP_DRIVER_MACOS_SIGNING_IDENTITY=$MACOS_SIGNING_IDENTITY" >> "$GITHUB_ENV"
if [[ -n "$DISABLED_INPUT_FEATURES" ]]; then
echo "FURN_DESKTOP_DRIVER_DISABLED_INPUT_FEATURES=$DISABLED_INPUT_FEATURES" >> "$GITHUB_ENV"
fi

- name: Configure Windows Desktop Driver
id: configure-windows
if: inputs.platform == 'windows' || inputs.platform == 'win32'
shell: pwsh
env:
DISABLED_INPUT_FEATURES: ${{ inputs.disabled-input-features }}
run: |
$ErrorActionPreference = 'Stop'

$cacheRoot = Join-Path $env:RUNNER_TEMP 'furn-desktop-driver-native'
New-Item -ItemType Directory -Force -Path $cacheRoot | Out-Null
"FURN_DESKTOP_DRIVER_CACHE_ROOT=$cacheRoot" |
Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"cache-root=$cacheRoot" |
Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

if ($env:DISABLED_INPUT_FEATURES) {
"FURN_DESKTOP_DRIVER_DISABLED_INPUT_FEATURES=$env:DISABLED_INPUT_FEATURES" |
Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
}

- name: Verify macOS Desktop Driver native contract
if: inputs.run-native-contract == 'true' && inputs.platform == 'macos'
shell: bash
working-directory: ${{ github.workspace }}
env:
FURN_NATIVE_DRIVER_TEST: '1'
run: yarn workspace @fluentui-react-native/desktop-driver test --runInBand

- name: Verify Windows Desktop Driver native contract
if: inputs.run-native-contract == 'true' && (inputs.platform == 'windows' || inputs.platform == 'win32')
shell: pwsh
working-directory: ${{ github.workspace }}
env:
FURN_NATIVE_DRIVER_TEST: '1'
run: |
yarn workspace @fluentui-react-native/desktop-driver test --runInBand
if ($LASTEXITCODE -ne 0) {
throw "Desktop Driver native contract exited with code $LASTEXITCODE."
}

- name: Build and diagnose macOS Desktop Driver
id: build-macos
if: inputs.platform == 'macos'
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
DOCTOR_PATH: ${{ runner.temp }}/desktop-driver-macos-doctor.json
MACOS_SIGNING_IDENTITY: ${{ inputs.macos-signing-identity }}
run: |
set -euo pipefail

doctor_path="$RUNNER_TEMP/desktop-driver-macos-doctor.json"
echo "doctor-path=$doctor_path" >> "$GITHUB_OUTPUT"
yarn desktop-driver build-driver --platform macos
# Hosted CI reports actual TCC authority; it must not prompt or mutate the permission database.
yarn desktop-driver doctor --platform macos --permissions | tee "$doctor_path"

node <<'NODE'
const fs = require('node:fs');

const report = JSON.parse(fs.readFileSync(process.env.DOCTOR_PATH, 'utf8'));
const expectedIdentity = process.env.MACOS_SIGNING_IDENTITY.trim();
const expectedMode = expectedIdentity ? 'signed' : 'adhoc';
if (!report.ready || report.result?.signing?.mode !== expectedMode) {
throw new Error(`The macOS Desktop Driver helper is not ready with the expected ${expectedMode} signature.`);
}
if (expectedIdentity) {
const expectedHash = /^[0-9a-f]{40}$/i.test(expectedIdentity) ? expectedIdentity.toUpperCase() : undefined;
if (
(expectedHash && report.result.signing.certificateHash !== expectedHash) ||
(!expectedHash && report.result.signing.identity !== expectedIdentity)
) {
throw new Error('The verified helper certificate does not match the configured macOS signing identity.');
}
}
if (report.permissions?.schemaVersion !== 1 || report.permissions?.type !== 'permissions') {
throw new Error('The macOS Desktop Driver permission diagnostic is missing or unsupported.');
}
NODE
echo "FURN_DESKTOP_DRIVER_BUILD_POLICY=never" >> "$GITHUB_ENV"
echo "FURN_DESKTOP_DRIVER_BUILD_POLICY=never" >> "$GITHUB_ENV"

- name: Build and diagnose Windows Desktop Driver
id: build-windows
if: inputs.platform == 'windows' || inputs.platform == 'win32'
shell: pwsh
working-directory: ${{ inputs.working-directory }}
env:
DRIVER_PLATFORM: ${{ inputs.platform }}
run: |
$ErrorActionPreference = 'Stop'

yarn desktop-driver build-driver --platform $env:DRIVER_PLATFORM
if ($LASTEXITCODE -ne 0) {
throw "Desktop Driver build exited with code $LASTEXITCODE."
}

$doctorPath = Join-Path $env:RUNNER_TEMP "desktop-driver-$env:DRIVER_PLATFORM-doctor.json"
"doctor-path=$doctorPath" |
Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
$doctorOutput = yarn desktop-driver doctor --platform $env:DRIVER_PLATFORM
$doctorExitCode = $LASTEXITCODE
$doctorOutput | Set-Content -Path $doctorPath -Encoding utf8
if ($doctorExitCode -ne 0) {
throw "Desktop Driver doctor exited with code $doctorExitCode."
}

$report = ($doctorOutput -join [Environment]::NewLine) | ConvertFrom-Json
if (
-not $report.ready -or
$report.result.provider -ne 'windows' -or
$report.result.architecture -ne 'x64' -or
$report.result.wireProtocol.major -ne 1 -or
-not ($report.result.endpoints -contains $env:DRIVER_PLATFORM)
) {
throw "The verified helper does not satisfy the $env:DRIVER_PLATFORM x64 provider contract."
}

"FURN_DESKTOP_DRIVER_BUILD_POLICY=never" |
Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
47 changes: 44 additions & 3 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ jobs:
- name: Build packages
run: yarn build

- name: Set up macOS Desktop Driver
id: desktop-driver
uses: ./.github/actions/setup-desktop-driver
with:
platform: macos
disabled-input-features: physicalClick

- name: Bundle macOS
run: |
set -eox pipefail
Expand All @@ -221,6 +228,15 @@ jobs:
env:
CCACHE_DISABLE: 1

- name: Upload macOS Storybook artifacts
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Storybook_macos_Dump
path: |
apps/storybook/artifacts/macos
${{ steps.desktop-driver.outputs.doctor-path }}

ios:
name: iOS PR
runs-on: macos-26
Expand Down Expand Up @@ -336,7 +352,7 @@ jobs:
windows-storybook:
name: Windows Storybook PR
runs-on: windows-latest
timeout-minutes: 40
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
Expand All @@ -355,6 +371,14 @@ jobs:
- name: Build packages
run: yarn build

- name: Set up Windows Desktop Driver
id: desktop-driver
uses: ./.github/actions/setup-desktop-driver
with:
platform: windows
run-native-contract: 'true'
disabled-input-features: keyboard,physicalClick,wheel

- name: Prep Windows Storybook
run: yarn storybook prep --windows
working-directory: apps/storybook
Expand Down Expand Up @@ -383,7 +407,12 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Storybook_windows_Dump
path: apps/storybook/artifacts/windows
path: |
apps/storybook/artifacts/windows
${{ steps.desktop-driver.outputs.doctor-path }}
${{ steps.desktop-driver.outputs.cache-root }}/v1/artifacts/**/build.log
${{ steps.desktop-driver.outputs.cache-root }}/v1/diagnostics/**
if-no-files-found: warn

win32:
name: Win32 PR
Expand Down Expand Up @@ -453,6 +482,13 @@ jobs:
- name: Build packages
run: yarn build

- name: Set up Win32 Desktop Driver
id: desktop-driver
uses: ./.github/actions/setup-desktop-driver
with:
platform: win32
disabled-input-features: keyboard,physicalClick,wheel

- name: Smoke test Win32 Storybook
run: yarn storybook smoke --win32 --mode stories-and-tests
working-directory: apps/storybook
Expand All @@ -462,7 +498,12 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Storybook_win32_Dump
path: apps/storybook/artifacts/win32
path: |
apps/storybook/artifacts/win32
${{ steps.desktop-driver.outputs.doctor-path }}
${{ steps.desktop-driver.outputs.cache-root }}/v1/artifacts/**/build.log
${{ steps.desktop-driver.outputs.cache-root }}/v1/diagnostics/**
if-no-files-found: warn

check-changesets:
name: Check for Changesets
Expand Down
24 changes: 22 additions & 2 deletions apps/storybook/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ Read [`agent-map.yaml`](agent-map.yaml) first for the compact architecture, look

- Use `yarn storybook manifest --<platform>` to validate static story-plan
extraction.
- Use `yarn storybook build-driver --<platform>` for an isolated native helper
build. `prep` ensures the same verified helper before app preparation.
- Use `yarn storybook driver --<platform>` to start Metro, the Storybook
channel/MCP listener, and the WebDriver listener under one owned supervisor.
- Use the app's `yarn desktop-driver` script for JSON story-run and agent
commands against that listener.
- The Stage 1 provider is deliberately fake. Do not add Windows or macOS native
automation code until the corresponding Stage 2 plan begins.
- macOS, Windows, and Win32 use their source-built native helpers. Keep the
deterministic fake provider limited to package contract tests.
- Authored tests belong in component story `parameters.desktopDriver`, not in
this app. The app owns identity, package discovery, platform exclusions, and
generated manifests.
Expand All @@ -56,6 +58,13 @@ Read [`agent-map.yaml`](agent-map.yaml) first for the compact architecture, look

## macOS native workflow

- In CI, use `.github/actions/setup-desktop-driver` to create the job-local
cache, build and diagnose the helper, and pin later resolution to the
verified artifact. GitHub-hosted runners use an ad hoc signature because
changing certificate trust requires interactive authorization, and disable
physical clicks because the hosted desktop is not the input qualification
environment. Managed self-hosted runners pass a pre-provisioned stable
signing identity and leave physical input enabled.
- Run `yarn storybook prep --macos` for project generation and Pod installation. Do not run CocoaPods from the
repository root because subprocess dependency resolution must start in this workspace.
- Run `yarn storybook bundle --macos` for the JavaScript bundle, `yarn storybook build --macos` for a non-launching
Expand All @@ -73,6 +82,13 @@ Read [`agent-map.yaml`](agent-map.yaml) first for the compact architecture, look

## Windows native workflow

- In CI, use `.github/actions/setup-desktop-driver` to run the shared Windows
native contract, build and diagnose the helper in a job-local cache, and pin
later resolution to the verified artifact before `prep` or `smoke`.
- GitHub-hosted Windows is not the physical-input qualification environment.
Disable keyboard, physical-click, and wheel through
`FURN_DESKTOP_DRIVER_DISABLED_INPUT_FEATURES`; leave them enabled on the
interactive self-hosted runner.
- Use `yarn storybook prep --windows`, `bundle --windows`, `build --windows`, and `run --windows` for individual
stages. Use `yarn storybook smoke --windows --mode stories` for the package-owned generation, channel server, native
build and registration, Metro launch, full indexed-story traversal, and ownership-safe cleanup. Use
Expand All @@ -90,6 +106,10 @@ Read [`agent-map.yaml`](agent-map.yaml) first for the compact architecture, look

## Win32 native workflow

- In CI, use `.github/actions/setup-desktop-driver` to build and diagnose the
`win32` endpoint in a job-local cache and pin later resolution before smoke.
The separate Windows Storybook job owns the shared provider's opt-in native
contract.
- Win32 is the `@office-iss/react-native-win32` Paper endpoint hosted by
`@office-iss/rex-win32`; do not treat it as the React Native Windows Fabric
endpoint or generate a `react-native-test-app` project for it.
Expand Down
Loading
Loading