Skip to content

Commit ca27762

Browse files
feat: replace unreliable iOS simulator action with custom script
1 parent 4606f20 commit ca27762

8 files changed

Lines changed: 246 additions & 35 deletions

File tree

.github/CI_DOCUMENTATION.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ All actions are pinned to specific commit SHAs for security and reproducibility:
7272
| `ruby/setup-ruby` | v1.204.0 | `d697be2f83c6234b20877c3b5eac7a7f342f0d0c` |
7373
| `android-actions/setup-android` | v3.2.1 | `9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407` |
7474
| `reactivecircus/android-emulator-runner` | v2.33.0 | `b530d96654c385303d652368551fb075bc2f0b6b` |
75-
| `futureware-tech/simulator-action` | v3.1.1 | `dab10d813144ef59b48d401cd95da151222ef8cd` |
75+
76+
> **Note on iOS Simulator:** We use a custom shell script (`.github/scripts/launch-ios-simulator.sh`) instead of `futureware-tech/simulator-action` for better reliability and faster boot times. The action was sometimes unstable and could hang for hours without response.
7677
7778
### Version Consistency Rules
7879

@@ -113,7 +114,7 @@ All actions are pinned to specific commit SHAs for security and reproducibility:
113114
7. **React Native Harness**: Must match between:
114115
- `example/package.json`
115116
- All harness platform packages (`@react-native-harness/*`)
116-
117+
117118
## Workflows
118119

119120
#### 1. Lint (`.github/workflows/lint.yml`)
@@ -159,15 +160,22 @@ runners: [
159160
];
160161
```
161162

162-
The workflows handle device setup automatically using `reactivecircus/android-emulator-runner` and `futureware-tech/simulator-action`, reading device configuration from the harness config.
163+
The workflows handle device setup automatically:
164+
165+
- **Android**: Uses `reactivecircus/android-emulator-runner` to launch the emulator
166+
- **iOS**: Uses a custom script (`.github/scripts/launch-ios-simulator.sh`) that launches the simulator with specific OS version pinning
163167

164168
## Running Tests Locally
165169

166170
### iOS
167171

168-
Make sure `iPhone 17 (iOS 26.0)` simulator is up and running
172+
Make sure `iPhone 17 (iOS 26.0)` simulator is up and running. You can use the launch script:
169173

170174
```bash
175+
# Launch simulator automatically
176+
./.github/scripts/launch-ios-simulator.sh "iPhone 17" "26.0"
177+
178+
# Then run tests
171179
corepack enable
172180
yarn install
173181
yarn prepare
@@ -202,13 +210,14 @@ yarn harness:android:with:build
202210
**iOS simulator not found or fails to boot:**
203211

204212
- Verify simulator model exists in Xcode version (`iPhone 17`)
205-
- Check iOS version compatibility (`26.0`)
206-
- Review simulator-action logs for setup errors
213+
- Check iOS version compatibility (`26.0+`)
214+
- Review simulator launch script logs for errors
207215
- Ensure device name matches harness config exactly
216+
- Check if `jq` is installed (script requires it for JSON parsing)
217+
- Simulator script automatically handles stuck processes and timeouts
208218

209219
## References
210220

211221
- [React Native Harness Documentation](https://www.react-native-harness.dev/)
212222
- [React Native Harness CI/CD Guide](https://www.react-native-harness.dev/docs/guides/ci-cd)
213223
- [Android Emulator Runner Action](https://github.com/ReactiveCircus/android-emulator-runner)
214-
- [iOS Simulator Action](https://github.com/futureware-tech/simulator-action)

.github/actions/setup-node-yarn/action.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,27 @@ runs:
88
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
99
with:
1010
node-version-file: '.nvmrc'
11-
cache: yarn
1211

1312
- name: Enable corepack
1413
shell: bash
1514
run: corepack enable
1615

16+
- name: Get yarn cache directory path
17+
id: yarn-cache-dir-path
18+
shell: bash
19+
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
20+
21+
- name: Cache Yarn dependencies
22+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
23+
with:
24+
path: |
25+
${{ steps.yarn-cache-dir-path.outputs.dir }}
26+
.yarn/cache
27+
.yarn/install-state.gz
28+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
29+
restore-keys: |
30+
${{ runner.os }}-yarn-
31+
1732
- name: Install dependencies
1833
shell: bash
1934
run: yarn install --immutable
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/bin/bash
2+
3+
# Script to reliably launch iOS Simulator
4+
# Usage: ./launch-ios-simulator.sh <device_name> <os_version> [timeout_seconds]
5+
#
6+
# Examples:
7+
# ./launch-ios-simulator.sh "iPhone 17" "26.0"
8+
# ./launch-ios-simulator.sh "iPhone 17" "26.0" 120
9+
#
10+
# Exit codes:
11+
# 0 - Success
12+
# 1 - Device not found
13+
# 2 - Boot timeout
14+
# 3 - Invalid arguments
15+
16+
set -euo pipefail
17+
18+
# Configuration
19+
DEVICE_NAME="${1:-}"
20+
OS_VERSION="${2:-}"
21+
TIMEOUT="${3:-120}"
22+
CHECK_INTERVAL=2
23+
24+
# Colors for output
25+
RED='\033[0;31m'
26+
GREEN='\033[0;32m'
27+
YELLOW='\033[1;33m'
28+
BLUE='\033[0;34m'
29+
NC='\033[0m' # No Color
30+
31+
# Helper functions
32+
log_info() {
33+
echo -e "${BLUE}${NC} $1"
34+
}
35+
36+
log_success() {
37+
echo -e "${GREEN}${NC} $1"
38+
}
39+
40+
log_warning() {
41+
echo -e "${YELLOW}${NC} $1"
42+
}
43+
44+
log_error() {
45+
echo -e "${RED}${NC} $1" >&2
46+
}
47+
48+
# Validate arguments
49+
if [ -z "$DEVICE_NAME" ] || [ -z "$OS_VERSION" ]; then
50+
log_error "Usage: $0 <device_name> <os_version> [timeout_seconds]"
51+
log_error "Example: $0 \"iPhone 17\" \"26.0\" 120"
52+
exit 3
53+
fi
54+
55+
log_info "Starting iOS Simulator setup..."
56+
log_info "Device: $DEVICE_NAME"
57+
log_info "OS Version: $OS_VERSION"
58+
log_info "Timeout: ${TIMEOUT}s"
59+
60+
# Kill any stuck simulators
61+
log_info "Checking for existing Simulator processes..."
62+
if pgrep -x "Simulator" > /dev/null; then
63+
log_warning "Found existing Simulator process, shutting down..."
64+
killall Simulator 2>/dev/null || true
65+
sleep 2
66+
fi
67+
68+
# Find the device UDID
69+
log_info "Finding device UDID for '$DEVICE_NAME' with iOS $OS_VERSION..."
70+
DEVICE_UDID=$(xcrun simctl list devices available --json | \
71+
jq -r --arg name "$DEVICE_NAME" --arg os "iOS $OS_VERSION" \
72+
'.devices[$os] | .[] | select(.name == $name and .isAvailable == true) | .udid' | \
73+
head -n 1)
74+
75+
if [ -z "$DEVICE_UDID" ]; then
76+
log_error "Device '$DEVICE_NAME' with iOS $OS_VERSION not found or not available"
77+
log_info "Available devices for iOS $OS_VERSION:"
78+
xcrun simctl list devices available --json | jq -r --arg os "iOS $OS_VERSION" '.devices[$os] | .[] | "\(.name) (\(.udid)) - \(.state)"'
79+
log_info ""
80+
log_info "All available iOS versions:"
81+
xcrun simctl list devices available --json | jq -r '.devices | keys[] | select(startswith("iOS"))'
82+
exit 1
83+
fi
84+
85+
log_success "Found device: $DEVICE_NAME (UDID: $DEVICE_UDID)"
86+
87+
# Check current state
88+
CURRENT_STATE=$(xcrun simctl list devices --json | \
89+
jq -r --arg udid "$DEVICE_UDID" \
90+
'.devices[] | .[] | select(.udid == $udid) | .state')
91+
92+
log_info "Current state: $CURRENT_STATE"
93+
94+
# Shutdown if already booted to ensure clean state
95+
if [ "$CURRENT_STATE" = "Booted" ]; then
96+
log_warning "Device already booted, shutting down for clean start..."
97+
xcrun simctl shutdown "$DEVICE_UDID" 2>/dev/null || true
98+
sleep 2
99+
fi
100+
101+
# Boot the simulator
102+
log_info "Booting simulator..."
103+
xcrun simctl boot "$DEVICE_UDID" 2>/dev/null || {
104+
# Check if it's already booting or booted
105+
CURRENT_STATE=$(xcrun simctl list devices --json | \
106+
jq -r --arg udid "$DEVICE_UDID" \
107+
'.devices[] | .[] | select(.udid == $udid) | .state')
108+
109+
if [ "$CURRENT_STATE" != "Booted" ] && [ "$CURRENT_STATE" != "Booting" ]; then
110+
log_error "Failed to boot simulator"
111+
exit 2
112+
fi
113+
}
114+
115+
# Wait for simulator to boot
116+
log_info "Waiting for simulator to boot (timeout: ${TIMEOUT}s)..."
117+
ELAPSED=0
118+
while [ $ELAPSED -lt $TIMEOUT ]; do
119+
STATE=$(xcrun simctl list devices --json | \
120+
jq -r --arg udid "$DEVICE_UDID" \
121+
'.devices[] | .[] | select(.udid == $udid) | .state')
122+
123+
if [ "$STATE" = "Booted" ]; then
124+
log_success "Simulator booted successfully!"
125+
break
126+
fi
127+
128+
if [ $((ELAPSED % 10)) -eq 0 ] && [ $ELAPSED -gt 0 ]; then
129+
log_info "Still waiting... (${ELAPSED}s elapsed)"
130+
fi
131+
132+
sleep $CHECK_INTERVAL
133+
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
134+
done
135+
136+
# Verify boot completed
137+
FINAL_STATE=$(xcrun simctl list devices --json | \
138+
jq -r --arg udid "$DEVICE_UDID" \
139+
'.devices[] | .[] | select(.udid == $udid) | .state')
140+
141+
if [ "$FINAL_STATE" != "Booted" ]; then
142+
log_error "Simulator boot timeout after ${TIMEOUT}s (state: $FINAL_STATE)"
143+
exit 2
144+
fi
145+
146+
# Open Simulator.app (optional, for visibility)
147+
log_info "Opening Simulator.app..."
148+
open -a Simulator --args -CurrentDeviceUDID "$DEVICE_UDID" 2>/dev/null || {
149+
log_warning "Failed to open Simulator.app (non-critical)"
150+
}
151+
152+
# Wait for SpringBoard to be ready
153+
log_info "Waiting for SpringBoard to be ready..."
154+
ELAPSED=0
155+
while [ $ELAPSED -lt 30 ]; do
156+
if xcrun simctl spawn "$DEVICE_UDID" launchctl print system | grep -q "com.apple.springboard"; then
157+
log_success "SpringBoard is ready!"
158+
break
159+
fi
160+
sleep 1
161+
ELAPSED=$((ELAPSED + 1))
162+
done
163+
164+
# Final verification
165+
log_info "Verifying simulator status..."
166+
xcrun simctl bootstatus "$DEVICE_UDID" -b 2>/dev/null || true
167+
168+
# Print simulator info
169+
log_success "Simulator is ready!"
170+
echo ""
171+
echo "Device Information:"
172+
echo " Name: $DEVICE_NAME"
173+
echo " OS Version: iOS $OS_VERSION"
174+
echo " UDID: $DEVICE_UDID"
175+
echo " State: $(xcrun simctl list devices --json | jq -r --arg udid "$DEVICE_UDID" '.devices[] | .[] | select(.udid == $udid) | .state')"
176+
echo ""
177+
178+
# Export UDID for use in subsequent steps
179+
echo "SIMULATOR_UDID=$DEVICE_UDID" >> "${GITHUB_ENV:-/dev/null}"
180+
log_success "Simulator UDID exported to GITHUB_ENV"
181+
182+
log_success "Setup complete! Simulator is ready for testing."
183+
exit 0

.github/workflows/android.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: Android Build & Test
33
on:
44
workflow_call:
55

6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8+
cancel-in-progress: true
9+
610
env:
711
DEVICE_API_LEVEL: '34'
812
DEVICE_ARCH: 'x86_64'

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: CI
33
on:
44
pull_request:
55

6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8+
cancel-in-progress: true
9+
610
jobs:
711
lint:
812
name: Lint

.github/workflows/ios.yml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: iOS Build & Test
33
on:
44
workflow_call:
55

6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8+
cancel-in-progress: true
9+
610
env:
711
DEVICE_MODEL: 'iPhone 17'
812
IOS_VERSION: '26.0'
@@ -66,14 +70,15 @@ jobs:
6670
run: yarn app:package:ios
6771

6872
# Setup simulator (after build)
69-
- name: Setup iOS Simulator
70-
uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd
71-
with:
72-
model: ${{ env.DEVICE_MODEL }}
73-
os: iOS
74-
os_version: ${{ env.IOS_VERSION }}
75-
wait_for_boot: true
76-
erase_before_boot: false
73+
- name: Launch iOS Simulator
74+
run: |
75+
# Install jq if not available
76+
if ! command -v jq &> /dev/null; then
77+
brew install jq
78+
fi
79+
80+
# Launch simulator with our custom script
81+
bash .github/scripts/launch-ios-simulator.sh "${{ env.DEVICE_MODEL }}" "${{ env.IOS_VERSION }}" 120
7782
7883
# Install app on simulator
7984
- name: Install app on simulator

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Mendix Native
22

3+
[![CI](https://github.com/mendix/mendix-native/actions/workflows/ci.yml/badge.svg)](https://github.com/mendix/mendix-native/actions/workflows/ci.yml)
4+
[![Lint](https://github.com/mendix/mendix-native/actions/workflows/lint.yml/badge.svg)](https://github.com/mendix/mendix-native/actions/workflows/lint.yml)
5+
[![Android](https://github.com/mendix/mendix-native/actions/workflows/android.yml/badge.svg)](https://github.com/mendix/mendix-native/actions/workflows/android.yml)
6+
[![iOS](https://github.com/mendix/mendix-native/actions/workflows/ios.yml/badge.svg)](https://github.com/mendix/mendix-native/actions/workflows/ios.yml)
7+
38
Mendix native mobile package for React Native applications.
49

510
## Prerequisites
@@ -15,6 +20,7 @@ Before you begin, ensure you have the following installed:
1520
## Local Development Setup
1621

1722
This project is a monorepo managed using Yarn workspaces with:
23+
1824
- The library package in the root directory
1925
- An example app in the `example/` directory
2026

@@ -80,10 +86,12 @@ yarn example android
8086
#### Edit Native Code
8187

8288
**iOS (Objective-C/Swift):**
89+
8390
- Open `example/ios/MendixNativeExample.xcworkspace` in Xcode
8491
- Find source files at: `Pods > Development Pods > mendix-native`
8592

8693
**Android (Java/Kotlin):**
94+
8795
- Open `example/android` in Android Studio
8896
- Find source files under: `mendix-native` in the Android view
8997

@@ -231,6 +239,7 @@ Example:
231239
## [Unreleased]
232240

233241
## [1.2.0] - 2025-01-15
242+
234243
<previous unreleased notes>
235244
```
236245

0 commit comments

Comments
 (0)