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
38 changes: 35 additions & 3 deletions .github/actions/maestro-ios/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Maestro E2E iOS
description: Runs E2E Tests on iOS using Maestro
description: Runs E2E Tests on iOS using Maestro against a remote simulator via sim-remote
inputs:
app-path:
required: true
Expand Down Expand Up @@ -27,9 +27,33 @@ runs:
run: export MAESTRO_VERSION=2.6.1; curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Installing Maestro dependencies
shell: bash
# libplist-utils provides plistutil, which backs the sim-remote plutil
# shim (Maestro pipes `simctl listapps` output through `plutil`).
run: |
brew tap facebook/fb
brew install facebook/fb/idb-companion
sudo apt-get update
sudo apt-get install -y libplist-utils
- name: Install sim-remote
shell: bash
run: |
case "$(uname -m)" in
x86_64) TARGET="x86_64-unknown-linux-gnu" ;;
aarch64 | arm64) TARGET="aarch64-unknown-linux-gnu" ;;
*) echo "Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
mkdir -p "$HOME/.local/bin"
curl -fL --retry 3 -o "$HOME/.local/bin/sim-remote" \
"https://github.com/software-mansion/sim-remote-releases/releases/download/softu/sim-remote-${TARGET}"
chmod +x "$HOME/.local/bin/sim-remote"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Acquire remote simulator
shell: bash
# Credentials come from the calling job's env:
# SIM_ROUTER_URL, SIM_ROUTER_USERNAME, SIM_ROUTER_API_KEY.
run: |
# An unset SIM_ROUTER_URL secret arrives as an empty env var, which
# would override the router URL baked into the binary — drop it.
[ -n "${SIM_ROUTER_URL:-}" ] || unset SIM_ROUTER_URL
sim-remote login --timeout 900
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
Expand All @@ -53,6 +77,10 @@ runs:
id: run-tests
shell: bash
run: |
# Put the Maestro shims (xcrun, xcodebuild, plutil, ...) on PATH so
# stock Maestro drives the remote simulator through sim-remote.
eval "$(sim-remote install-shims)"

# Avoid exit from the job if one of the command returns an error.
# Maestro can fail in case of flakyness, we have some retry logic.
set +e
Expand All @@ -64,6 +92,10 @@ runs:
"Hermes" \
"${{ inputs.flavor }}" \
"${{ inputs.working-directory }}"
- name: Release remote simulator
if: ${{ always() }}
shell: bash
run: sim-remote logout || true
- name: Store video record
if: always()
uses: actions/upload-artifact@v6
Expand Down
38 changes: 30 additions & 8 deletions .github/workflow-scripts/maestro-ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ function findAvailableSimulator() {
const devices = Object.values(JSON.parse(String(output)).devices)
.flat()
.reverse();
const simulator = devices.find(device => /^iPhone .* Pro$/.test(device.name));
const simulator =
devices.find(device => /^iPhone .* Pro$/.test(device.name)) ??
devices.find(device => /^iPhone /.test(device.name));

if (simulator == null) {
throw new Error('Unable to find an available iPhone Pro simulator');
throw new Error('Unable to find an available iPhone simulator');
}

return simulator;
Expand All @@ -54,9 +56,26 @@ function launchSimulator(simulator) {
}
}

function installAppOnSimulator(appPath) {
function installAppOnSimulator(appPath, udid) {
console.log(`Installing app at path ${appPath}`);
childProcess.execSync(`xcrun simctl install booted "${appPath}"`);
childProcess.execSync(`xcrun simctl install "${udid}" "${appPath}"`);
}

// When running against a remote simulator through the sim-remote Maestro
// shims (`sim-remote install-shims` exports SIM_REMOTE_BIN), localhost inside
// the simulator is the remote host, not this machine — so the app cannot
// reach the local Metro server on its own. A reverse tunnel exposes the local
// Metro port to the simulator via localhost.
function exposeMetroToSimulator(udid) {
const simRemote = process.env.SIM_REMOTE_BIN;
if (!simRemote) {
return;
}

console.log('Exposing local Metro port 8081 to the remote simulator');
childProcess.execSync(`"${simRemote}" reverse start "${udid}" 8081`, {
stdio: 'inherit',
});
}

function bringSimulatorInForeground() {
Expand All @@ -80,13 +99,13 @@ async function launchAppOnSimulator(appId, udid, isDebug) {
}
}

function startVideoRecording(jsengine, currentAttempt) {
function startVideoRecording(udid, currentAttempt) {
console.log(
`Start video record using pid: video_record_${currentAttempt}.pid`,
);

const recordingArgs =
`simctl io booted recordVideo --force video_record_${currentAttempt}.mov`.split(
`simctl io ${udid} recordVideo --force video_record_${currentAttempt}.mov`.split(
' ',
);
const recordingProcess = childProcess.spawn('xcrun', recordingArgs, {
Expand All @@ -109,7 +128,7 @@ function stopVideoRecording(recordingProcess) {
}

function executeFlowWithRetries(appId, udid, flow, jsengine, currentAttempt) {
const recProcess = startVideoRecording(jsengine, currentAttempt);
const recProcess = startVideoRecording(udid, currentAttempt);
try {
const timeout = 1000 * 60 * 10; // 10 minutes
const command = `$HOME/.maestro/bin/maestro --udid="${udid}" test "${flow}" --format junit -e APP_ID="${appId}"`;
Expand Down Expand Up @@ -176,8 +195,11 @@ async function main(args = process.argv.slice(2)) {

const simulator = findAvailableSimulator();
launchSimulator(simulator);
installAppOnSimulator(appPath);
installAppOnSimulator(appPath, simulator.udid);
bringSimulatorInForeground();
if (isDebug) {
exposeMetroToSimulator(simulator.udid);
}
await launchAppOnSimulator(appId, simulator.udid, isDebug);
executeFlows(appId, simulator.udid, maestroFlow, jsengine);
console.log('Test finished');
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/analyze-pr.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Analyze Pull Request

on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
workflow_dispatch:

permissions:
pull-requests: write
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/api-changes.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Analyze API Changes

on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
workflow_dispatch:

permissions:
pull-requests: write
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/autorebase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: Automatic Rebase
# containing the text "/rebase". It uses the cirrus-actions/rebase action.
# See https://github.com/cirrus-actions/rebase
on:
issue_comment:
types: [created]
workflow_dispatch:

permissions:
contents: read
jobs:
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/cache-reaper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: Keep Github Actions Cache < 10GB

on:
workflow_dispatch:
schedule:
# Run every 2hrs during weekdays
- cron: '0 0/2 * * 1-5'

jobs:
cache-cleaner:
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/check-for-reproducer.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Check for reproducer
# This workflow is triggered when issue is created or edited.
on:
issues:
types: [opened, edited]
workflow_dispatch:

jobs:
check-for-reproducer:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/close-pr.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Label closed PR as merged and leave a comment
on: push
on:
workflow_dispatch:

permissions:
contents: read
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/e2e-ios-rntester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ on:

jobs:
test:
runs-on: macos-26-large
runs-on: ubuntu-latest
env:
SIM_ROUTER_URL: ${{ secrets.SIM_ROUTER_URL }}
SIM_ROUTER_USERNAME: ${{ vars.SIM_ROUTER_USERNAME }}
SIM_ROUTER_API_KEY: ${{ secrets.SIM_ROUTER_API_KEY }}
outputs:
status: ${{ steps.report-status.outputs.status }}
strategy:
Expand Down
70 changes: 65 additions & 5 deletions .github/workflows/e2e-ios-templateapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ on:
value: ${{ jobs.report.outputs.status }}

jobs:
test:
# Building the app needs Xcode, so it stays on a macOS runner. The E2E run
# itself drives a remote simulator via sim-remote, so it runs on Linux.
build:
runs-on: macos-26-large
outputs:
status: ${{ steps.report-status.outputs.status }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
run: |
git config --global user.email "react-native-bot@meta.com"
git config --global user.name "React Native Bot"
- name: Prepare artifacts
- name: Build the app
run: |
REACT_NATIVE_PKG=$(find /tmp/react-native-tmp -type f -name "*.tgz")
echo "React Native tgs is $REACT_NATIVE_PKG"
Expand Down Expand Up @@ -92,12 +92,72 @@ jobs:
-sdk "iphonesimulator" \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath "/tmp/RNTestProject"
- name: Upload app
uses: actions/upload-artifact@v6
with:
name: RNTestProject-${{ matrix.flavor }}
path: /tmp/RNTestProject/Build/Products/${{ matrix.flavor }}-iphonesimulator/RNTestProject.app

test:
needs: build
runs-on: ubuntu-latest
env:
SIM_ROUTER_URL: ${{ secrets.SIM_ROUTER_URL }}
SIM_ROUTER_USERNAME: ${{ vars.SIM_ROUTER_USERNAME }}
SIM_ROUTER_API_KEY: ${{ secrets.SIM_ROUTER_API_KEY }}
outputs:
status: ${{ steps.report-status.outputs.status }}
strategy:
fail-fast: false
matrix:
flavor: [Debug, Release]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Run yarn
uses: ./.github/actions/yarn-install
- name: Download app
uses: actions/download-artifact@v7
with:
name: RNTestProject-${{ matrix.flavor }}
path: /tmp/RNTestProjectBuild/RNTestProject.app
- name: Check downloaded folder content
run: ls -l /tmp/RNTestProjectBuild/RNTestProject.app
- name: Download React Native Package
if: ${{ matrix.flavor == 'Debug' }}
uses: actions/download-artifact@v7
with:
name: react-native-package
path: /tmp/react-native-tmp
- name: Configure git
if: ${{ matrix.flavor == 'Debug' }}
shell: bash
run: |
git config --global user.email "react-native-bot@meta.com"
git config --global user.name "React Native Bot"
- name: Prepare project for Metro
if: ${{ matrix.flavor == 'Debug' }}
# In Debug the app loads its bundle from Metro, which must run from an
# initialized project. Re-initialize it here (JS only — no pods); the
# native app itself comes prebuilt from the `build` job.
run: |
REACT_NATIVE_PKG=$(find /tmp/react-native-tmp -type f -name "*.tgz")
echo "React Native tgs is $REACT_NATIVE_PKG"

BRANCH=${{ github.ref_name }}
if ! [[ $BRANCH == *-stable* ]]; then
BRANCH=main
fi

node ./scripts/e2e/init-project-e2e.js --projectName RNTestProject --currentBranch $BRANCH --directory /tmp/RNTestProject --pathToLocalReactNative $REACT_NATIVE_PKG
- name: Run E2E Tests
id: run-tests
continue-on-error: true
uses: ./.github/actions/maestro-ios
with:
app-path: '/tmp/RNTestProject/Build/Products/${{ matrix.flavor }}-iphonesimulator/RNTestProject.app'
app-path: '/tmp/RNTestProjectBuild/RNTestProject.app'
app-id: org.reactjs.native.example.RNTestProject
maestro-flow: ./scripts/e2e/.maestro/
flavor: ${{ matrix.flavor }}
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/monitor-new-issues.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Monitor React Native New Issues

on:
schedule:
- cron: '0 0,6,12,18 * * *'
workflow_dispatch:

# Reminder for when we have to update the schedule (before Jan 2026):
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/needs-attention.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Issue Needs Attention
# This workflow is triggered on issue comments.
on:
issue_comment:
types: [created]
workflow_dispatch:

permissions:
contents: read
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/on-issue-labeled.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: On Issue Labeled
# This workflow is triggered when a label is added to an issue.
on:
issues:
types: [labeled]
workflow_dispatch:

permissions:
contents: write
Expand Down
10 changes: 0 additions & 10 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,7 @@
name: Publish to npm

on:
push:
tags:
- 'v0.*.*' # This should match v0.X.Y
- 'v0.*.*-rc.*' # This should match v0.X.Y-RC.0
branches:
- 'main'
- '*-stable'
workflow_dispatch:
# nightly build @ 2:15 AM UTC
schedule:
- cron: '15 2 * * *'

permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/stale-bot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Stale bot
on:
schedule:
- cron: '*/10 5 * * *'
workflow_dispatch:

jobs:
stale:
runs-on: ubuntu-latest
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ name: Test All

on:
workflow_dispatch:
pull_request:
push:
branches:
- main
- '*-stable'

permissions:
contents: read
Expand Down
Loading