Skip to content
Open
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
26 changes: 26 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,19 @@ jobs:
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"

# ADR-0001 merge gate: a Harness run must leave no agent-device daemon,
# xcodebuild or AgentDeviceRunner process behind, because those hold the
# host-global device claim and would block the next job on this runner.
- name: Verify no agent-device processes survived
if: always()
shell: bash
run: |
if pgrep -fl 'AgentDeviceRunner|xcodebuild test-without-building|agent-device/dist/src/internal/daemon'; then
echo "ERROR: agent-device processes survived the Harness run"
exit 1
fi
echo "No agent-device, xcodebuild or AgentDeviceRunner process survived the run"

- name: Upload Harness logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down Expand Up @@ -544,6 +557,19 @@ jobs:
echo "Crash report artifacts found:"
find "$CRASH_DIR" -mindepth 4 -type f -print

# ADR-0001 merge gate: a Harness run must leave no agent-device daemon,
# xcodebuild or AgentDeviceRunner process behind, because those hold the
# host-global device claim and would block the next job on this runner.
- name: Verify no agent-device processes survived
if: always()
shell: bash
run: |
if pgrep -fl 'AgentDeviceRunner|xcodebuild test-without-building|agent-device/dist/src/internal/daemon'; then
echo "ERROR: agent-device processes survived the Harness run"
exit 1
fi
echo "No agent-device, xcodebuild or AgentDeviceRunner process survived the run"

- name: Upload Harness logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down
22 changes: 22 additions & 0 deletions .nx/version-plans/version-plan-1789368817816.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
__default__: minor
---

iOS permission prompt automation (`permissions: true`) is now powered by
agent-device instead of a Harness-maintained XCTest agent. Behavior is
unchanged: Harness watches for system permission prompts while your app is
running and taps the first known positive button, and it still requires
`device.codeSign` on physical devices. Three things change for existing
setups. Prompts are now dismissed within about four seconds instead of about
one, because each check is a round trip through the UI test runner. Physical
device provisioning profiles must cover the agent-device runner bundle
identifiers; personal teams can point the runner at their own identifier
with the new `device.codeSign.runnerBundleId` option. Prebuilt `.xctestrun`
artifacts supplied through `HARNESS_IOS_XCTESTRUN_FILE` must be rebuilt with
`agent-device prepare ios-runner`, and the `harness xctest build` command is
removed, along with the `buildXCTestAgent` export from
`@react-native-harness/platform-apple` and its `BuildXCTestAgentOptions`,
`BuildXCTestAgentResult`, `XCTestAgentBuildDestination` and
`XCTestAgentBuildSigning` types. The GitHub Action now caches the
agent-device runner build and requires a matching `react-native-harness`
install.
8 changes: 8 additions & 0 deletions .nx/version-plans/version-plan-1789368825568.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
__default__: minor
---

Every Harness package now declares a Node.js 22.12 floor in its `engines`
field. Harness already required a recent Node.js in practice; this makes the
requirement explicit so installs on older runtimes fail early instead of at
runtime.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
making a change.
- For version plans, see @./docs/agents/version-plans.md.
- Before preparing or opening a pull request, see @./docs/agents/pull-requests.md.
- Architecture decisions are recorded under `docs/internal/adr/`; read the
relevant ADR before implementing a change it covers.
- Preserve unrelated work already present in the working tree.
- Keep changes focused; do not make opportunistic refactors.
- Never commit credentials, secrets, generated build output, or local
Expand Down
32 changes: 21 additions & 11 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ runs:
# workspace version), and it would also reject semver-correct
# prereleases of that release. Bump REQUIRED_PROTOCOL only when a
# change to the `harness ci` contract is breaking for this action.
REQUIRED_PROTOCOL=1
REQUIRED_PROTOCOL=2
INSTALLED_VERSION=$(node -p "require(require.resolve('react-native-harness/package.json',{paths:[process.cwd()]})).version" 2>/dev/null)
if [ -z "$INSTALLED_VERSION" ]; then
echo "Error: could not resolve the 'react-native-harness' package in this project."
Expand Down Expand Up @@ -195,15 +195,25 @@ runs:
METRO_RESTORED_KEY: ${{ steps.restore-metro.outputs.cache-matched-key }}
run: |
${{ steps.detect-pm.outputs.runner }}react-native-harness ci snapshot-metro
- name: Restore Harness cache
id: cache-harness-restore
# agent-device rebuilds its XCUITest runner whenever its own version, Xcode
# or the SDK changes, so the key is exact and carries no restore-keys: a
# near-miss entry would be ~165 MB of DerivedData the runner cannot reuse.
- name: Plan agent-device runner cache
id: plan-ios-runner-cache
if: fromJson(steps.load-config.outputs.config).platformId == 'ios'
shell: bash
working-directory: ${{ steps.load-config.outputs.projectRoot }}
env:
INPUT_PROJECTROOT: ${{ steps.load-config.outputs.projectRoot }}
run: |
${{ steps.detect-pm.outputs.runner }}react-native-harness ci plan-ios-runner-cache
- name: Restore agent-device runner cache (.harness/cache/agent-device-runner)
id: cache-ios-runner-restore
if: ${{ fromJson(steps.load-config.outputs.config).platformId == 'ios' && steps.plan-ios-runner-cache.outputs.iosRunnerCacheKey != '' }}
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/cache/xctest-agent-simulator-*
key: harness-ios-${{ runner.os }}-${{ hashFiles(format('{0}/.harness/cache/**/cache.json', steps.load-config.outputs.projectRoot)) }}
restore-keys: |
harness-ios-${{ runner.os }}-
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/cache/agent-device-runner
key: ${{ steps.plan-ios-runner-cache.outputs.iosRunnerCacheKey }}

# ── iOS ──────────────────────────────────────────────────────────────────
# iOS simulator boot and app installation are handled by Harness itself.
Expand Down Expand Up @@ -318,12 +328,12 @@ runs:
~/.android/avd
~/.android/adb*
key: ${{ steps.avd-key.outputs.key }}
- name: Save Harness cache
if: ${{ always() && fromJson(steps.load-config.outputs.config).platformId == 'ios' && steps.cache-harness-restore.outputs.cache-hit != 'true' }}
- name: Save agent-device runner cache
if: ${{ always() && fromJson(steps.load-config.outputs.config).platformId == 'ios' && steps.plan-ios-runner-cache.outputs.iosRunnerCacheKey != '' && steps.cache-ios-runner-restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/cache/xctest-agent-simulator-*
key: ${{ steps.cache-harness-restore.outputs.cache-primary-key }}
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/cache/agent-device-runner
key: ${{ steps.plan-ios-runner-cache.outputs.iosRunnerCacheKey }}
- name: Plan Metro cache save
id: plan-metro-save
if: always()
Expand Down
3 changes: 2 additions & 1 deletion docs/github-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ Keeping the action ref aligned with the `react-native-harness` version in your p
- `cacheSavePolicy` (optional): When to save a new Metro cache entry -- `default-branch` (default), `always`, or `never`
- Crash artifacts persisted to `.harness/crash-reports/` are uploaded automatically when present
- Metro cache persisted to `.harness/cache/metro/` and `.harness/cache/metro-file-map/` is restored and saved automatically when present. The cache key is computed by `@react-native-harness/cache` from your lockfile(s), Metro/Babel config, the installed `@react-native-harness/bundler-metro` version, and your Harness config's `cache.version` salt -- a new entry is only saved when the run's cache content actually changed and `cacheSavePolicy` allows it
- For iOS runners, the `agent-device` UI test runner build in `.harness/cache/agent-device-runner/` is restored and saved automatically. The key is computed by `react-native-harness ci plan-ios-runner-cache` from the installed `agent-device` version and `xcodebuild -version`, with no restore-key prefixes: a near-miss entry cannot be reused by the runner, so an Xcode or `agent-device` upgrade means exactly one cold runner build

## Behavior

Depending on the selected runner, the action:

- For Android runners, loads and validates your Harness configuration, restores Metro cache, sets up the Android emulator with architecture detection, optionally caches AVD snapshots, installs your app on the emulator, runs the hooks inside the emulator session, and runs the Harness tests
- For iOS runners, loads and validates your Harness configuration, restores Metro cache, sets up the iOS simulator, installs your app on the simulator, runs the hooks around the Harness invocation, and runs the Harness tests
- For iOS runners, loads and validates your Harness configuration, restores Metro cache and the `agent-device` runner cache, sets up the iOS simulator, installs your app on the simulator, runs the hooks around the Harness invocation, and runs the Harness tests
- For web runners, loads and validates your Harness configuration, restores Metro cache, installs Playwright Chromium, runs the hooks around the Harness invocation, and runs the Harness tests

Hook behavior:
Expand Down
Loading
Loading