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
50 changes: 5 additions & 45 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ jobs:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

- name: Install dependencies
run: |
pnpm install
Expand Down Expand Up @@ -100,7 +92,7 @@ jobs:
key: apk-playground

- name: Run React Native Harness
uses: ./actions/android
uses: ./
with:
app: android/app/build/outputs/apk/debug/app-debug.apk
runner: android
Expand Down Expand Up @@ -131,14 +123,6 @@ jobs:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

- name: Install Watchman
run: brew install watchman

Expand Down Expand Up @@ -189,7 +173,7 @@ jobs:
key: ios-app-playground

- name: Run React Native Harness
uses: ./actions/ios
uses: ./
with:
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
runner: ios
Expand Down Expand Up @@ -221,14 +205,6 @@ jobs:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

- name: Install dependencies
run: |
pnpm install
Expand All @@ -238,7 +214,7 @@ jobs:
pnpm nx run-many -t build --projects="packages/*"

- name: Run React Native Harness
uses: ./actions/web
uses: ./
with:
runner: chromium
projectRoot: apps/playground
Expand Down Expand Up @@ -278,14 +254,6 @@ jobs:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

- name: Install dependencies
run: |
pnpm install
Expand Down Expand Up @@ -323,7 +291,7 @@ jobs:
- name: Run React Native Harness (expect crash)
id: crash-test
continue-on-error: true
uses: ./actions/android
uses: ./
with:
app: android/app/build/outputs/apk/debug/app-debug.apk
runner: android-crash-pre-rn
Expand Down Expand Up @@ -376,14 +344,6 @@ jobs:
node-version: '24.10.0'
cache: 'pnpm'

- name: Metro cache
uses: actions/cache@v4
with:
path: apps/playground/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

- name: Install Watchman
run: brew install watchman

Expand Down Expand Up @@ -436,7 +396,7 @@ jobs:
- name: Run React Native Harness (expect crash)
id: crash-test
continue-on-error: true
uses: ./actions/ios
uses: ./
with:
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
runner: ios-crash-pre-rn
Expand Down
253 changes: 253 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
name: React Native Harness
description: Run React Native Harness tests on iOS, Android or Web
inputs:
runner:
description: The runner to use (must match a runner name defined in your harness config)
required: true
type: string
app:
description: The path to the app (.app for iOS, .apk for Android). Not required for web.
required: false
type: string
projectRoot:
description: The project root directory
required: false
type: string
uploadVisualTestArtifacts:
description: Whether to upload visual test diff and actual images as artifacts
required: false
type: boolean
default: 'true'
harnessArgs:
description: Additional arguments to pass to the Harness CLI
required: false
type: string
default: ''
packageManager:
description: Package manager to use instead of auto-detection (npm, yarn, pnpm, bun, or deno)
required: false
type: string
default: ''
cacheAvd:
description: Whether to cache the AVD
required: false
type: boolean
default: 'true'
runs:
using: 'composite'
steps:
- name: Load React Native Harness configuration
id: load-config
shell: bash
env:
INPUT_RUNNER: ${{ inputs.runner }}
INPUT_PROJECTROOT: ${{ inputs.projectRoot }}
run: |
node ${{ github.action_path }}/actions/shared/index.cjs
- name: Verify native app input
if: fromJson(steps.load-config.outputs.config).platformId != 'web'
shell: bash
run: |
if [ -z "${{ inputs.app }}" ]; then
echo "Error: app input is required for native runners"
echo "Please provide the path to the built app (.apk for Android, .app for iOS)"
exit 1
fi
- name: Metro cache
uses: actions/cache@v4
with:
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/metro-cache
key: ${{ runner.os }}-metro-cache-${{ hashFiles('**/bun.lock', '**/bun.lockb', '**/package-lock.json', '**/npm-shrinkwrap.json', '**/pnpm-lock.yaml', '**/yarn.lock', '**/metro.config.js', '**/metro.config.cjs', '**/metro.config.mjs', '**/metro.config.ts', '**/babel.config.js', '**/babel.config.cjs', '**/babel.config.mjs', '**/babel.config.ts', '**/babel.config.json') }}
restore-keys: |
${{ runner.os }}-metro-cache-

# ── iOS ──────────────────────────────────────────────────────────────────
- uses: futureware-tech/simulator-action@v4
if: fromJson(steps.load-config.outputs.config).platformId == 'ios'
with:
model: ${{ fromJson(steps.load-config.outputs.config).config.device.name }}
os: iOS
os_version: ${{ fromJson(steps.load-config.outputs.config).config.device.systemVersion }}
wait_for_boot: true
erase_before_boot: false
- name: Install app
if: fromJson(steps.load-config.outputs.config).platformId == 'ios'
shell: bash
working-directory: ${{ steps.load-config.outputs.projectRoot }}
run: |
xcrun simctl install booted ${{ inputs.app }}

# ── Android ──────────────────────────────────────────────────────────────
- name: Verify Android config
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
shell: bash
run: |
CONFIG='${{ steps.load-config.outputs.config }}'
if [ -z "$CONFIG.config.device.avd" ] || [ "$CONFIG.config.device.avd" = "null" ]; then
echo "Error: AVD config is required for Android emulators"
echo "Please define the 'avd' property in the runner config"
exit 1
fi
- name: Get architecture of the runner
id: arch
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
shell: bash
run: |
case "${{ runner.arch }}" in
X64)
echo "arch=x86_64" >> $GITHUB_OUTPUT
;;
ARM64)
echo "arch=arm64-v8a" >> $GITHUB_OUTPUT
;;
ARM32)
echo "arch=armeabi-v7a" >> $GITHUB_OUTPUT
;;
*)
echo "arch=x86_64" >> $GITHUB_OUTPUT
;;
esac
- name: Enable KVM group perms
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
shell: bash
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
ls /dev/kvm
- name: Compute AVD cache key
id: avd-key
if: ${{ fromJson(steps.load-config.outputs.config).platformId == 'android' && fromJSON(inputs.cacheAvd) }}
shell: bash
run: |
CONFIG='${{ steps.load-config.outputs.config }}'
AVD_CONFIG=$(echo "$CONFIG" | jq -c '.config.device.avd')
AVD_CONFIG_HASH=$(echo "$AVD_CONFIG" | sha256sum | cut -d' ' -f1)
ARCH="${{ steps.arch.outputs.arch }}"
CACHE_KEY="avd-$ARCH-$AVD_CONFIG_HASH"
echo "key=$CACHE_KEY" >> $GITHUB_OUTPUT
- name: Restore AVD cache
uses: actions/cache/restore@v4
id: avd-cache
if: ${{ fromJson(steps.load-config.outputs.config).platformId == 'android' && fromJSON(inputs.cacheAvd) }}
with:
path: |
~/.android/avd
~/.android/adb*
key: ${{ steps.avd-key.outputs.key }}
- name: Create AVD and generate snapshot for caching
if: ${{ fromJson(steps.load-config.outputs.config).platformId == 'android' && fromJSON(inputs.cacheAvd) && steps.avd-cache.outputs.cache-hit != 'true' }}
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.apiLevel }}
arch: ${{ steps.arch.outputs.arch }}
profile: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.profile }}
disk-size: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.diskSize }}
heap-size: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.heapSize }}
force-avd-creation: false
avd-name: ${{ fromJson(steps.load-config.outputs.config).config.device.name }}
disable-animations: true
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: echo "Generated AVD snapshot for caching."
- name: Save AVD cache
if: ${{ fromJson(steps.load-config.outputs.config).platformId == 'android' && fromJSON(inputs.cacheAvd) && steps.avd-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: |
~/.android/avd
~/.android/adb*
key: ${{ steps.avd-key.outputs.key }}

# ── Web ──────────────────────────────────────────────────────────────────
- name: Install Playwright Browsers
if: fromJson(steps.load-config.outputs.config).platformId == 'web'
shell: bash
run: npx playwright install --with-deps chromium

# ── Shared ───────────────────────────────────────────────────────────────
- name: Detect Package Manager
id: detect-pm
shell: bash
working-directory: ${{ steps.load-config.outputs.projectRoot }}
run: |
if [ -n "${{ inputs.packageManager }}" ]; then
case "${{ inputs.packageManager }}" in
pnpm)
echo "manager=pnpm" >> $GITHUB_OUTPUT
echo "runner=pnpm exec " >> $GITHUB_OUTPUT
;;
yarn)
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "runner=yarn " >> $GITHUB_OUTPUT
;;
bun)
echo "manager=bun" >> $GITHUB_OUTPUT
echo "runner=bunx " >> $GITHUB_OUTPUT
;;
deno)
echo "manager=deno" >> $GITHUB_OUTPUT
echo "runner=deno run -A npm:" >> $GITHUB_OUTPUT
;;
npm)
echo "manager=npm" >> $GITHUB_OUTPUT
echo "runner=npx " >> $GITHUB_OUTPUT
;;
*)
echo "Error: Unsupported packageManager '${{ inputs.packageManager }}'"
echo "Supported values: npm, yarn, pnpm, bun, deno"
exit 1
;;
esac
elif [ -f "pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
echo "runner=pnpm exec " >> $GITHUB_OUTPUT
elif [ -f "yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "runner=yarn " >> $GITHUB_OUTPUT
elif [ -f "bun.lock" ] || [ -f "bun.lockb" ]; then
echo "manager=bun" >> $GITHUB_OUTPUT
echo "runner=bunx " >> $GITHUB_OUTPUT
elif [ -f "deno.lock" ]; then
echo "manager=deno" >> $GITHUB_OUTPUT
echo "runner=deno run -A npm:" >> $GITHUB_OUTPUT
else
echo "manager=npm" >> $GITHUB_OUTPUT
echo "runner=npx " >> $GITHUB_OUTPUT
fi
- name: Run E2E tests
if: fromJson(steps.load-config.outputs.config).platformId != 'android'
shell: bash
working-directory: ${{ steps.load-config.outputs.projectRoot }}
run: ${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
- name: Run E2E tests
id: run-tests
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
uses: reactivecircus/android-emulator-runner@v2
with:
working-directory: ${{ steps.load-config.outputs.projectRoot }}
api-level: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.apiLevel }}
arch: ${{ steps.arch.outputs.arch }}
force-avd-creation: false
avd-name: ${{ fromJson(steps.load-config.outputs.config).config.device.name }}
disable-animations: true
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: |
echo $(pwd)
adb install -r ${{ inputs.app }}
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
- name: Upload visual test artifacts
if: always() && inputs.uploadVisualTestArtifacts == 'true'
uses: actions/upload-artifact@v4
with:
name: visual-test-diffs-${{ fromJson(steps.load-config.outputs.config).platformId }}
path: |
${{ steps.load-config.outputs.projectRoot }}/**/__image_snapshots__/**/*-diff.png
${{ steps.load-config.outputs.projectRoot }}/**/__image_snapshots__/**/*-actual.png
if-no-files-found: ignore
- name: Upload crash report artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: harness-crash-reports-${{ fromJson(steps.load-config.outputs.config).platformId }}
path: ${{ steps.load-config.outputs.projectRoot }}/.harness/crash-reports/**/*
if-no-files-found: ignore
2 changes: 1 addition & 1 deletion actions/android/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: React Native Harness for Android
description: Run React Native Harness tests on Android
description: '[Deprecated] Use callstackincubator/react-native-harness instead. Run React Native Harness tests on Android'
inputs:
app:
description: The path to the Android app (.apk)
Expand Down
2 changes: 1 addition & 1 deletion actions/ios/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: React Native Harness for iOS
description: Run React Native Harness tests on iOS
description: '[Deprecated] Use callstackincubator/react-native-harness instead. Run React Native Harness tests on iOS'
inputs:
app:
description: The path to the iOS app (.app)
Expand Down
Loading
Loading