Skip to content

Commit 641f63a

Browse files
authored
chore: Make Harness AWS Device Farm tests run on iOS too (#3848)
* update harness which should build the XCTest helper * android: remove explicit perm handling * add ios test section back lol * adjust running on sim * request permissions * restore more * ios: build & upload XCTest ipa + install * actually download oops * does my team id magically work? * harness patch to naturally specify external xctest run, but not sure if AWS accepts a test spec file when its XCUITest … * fix stopping early * apply: callstackincubator/react-native-harness#113 * team id * massively simplify test spec * ios: aws tst spec collect logs corerctly * github action: split android in build + test & remove prepare stage as android and iOS have no longer shared test package * apply: callstackincubator/react-native-harness#117 * add verbose for more test details * force colors?
1 parent 209636a commit 641f63a

15 files changed

Lines changed: 2279 additions & 140 deletions

.github/workflows/harness-aws-device.yml

Lines changed: 268 additions & 78 deletions
Large diffs are not rendered by default.

apps/simple-camera/__tests__/visioncamera.constraints.harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('VisionCamera - Constraints', () => {
2222
let backDevice: CameraDevice
2323

2424
beforeAll(async () => {
25+
await VisionCamera.requestCameraPermission();
2526
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
2627
factory = await VisionCamera.createDeviceFactory()
2728
const back = factory.getDefaultCamera('back')

apps/simple-camera/__tests__/visioncamera.controller.harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('VisionCamera - Controller', () => {
1010
let backDevice: CameraDevice
1111

1212
beforeAll(async () => {
13+
await VisionCamera.requestCameraPermission();
1314
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
1415
factory = await VisionCamera.createDeviceFactory()
1516
const back = factory.getDefaultCamera('back')

apps/simple-camera/__tests__/visioncamera.frame.harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('VisionCamera - Frame', () => {
1919
let backDevice: CameraDevice
2020

2121
beforeAll(async () => {
22+
await VisionCamera.requestCameraPermission();
2223
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
2324
factory = await VisionCamera.createDeviceFactory()
2425
const back = factory.getDefaultCamera('back')

apps/simple-camera/__tests__/visioncamera.photo.harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('VisionCamera - Photo', () => {
2121
let backDevice: CameraDevice
2222

2323
beforeAll(async () => {
24+
await VisionCamera.requestCameraPermission();
2425
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
2526
factory = await VisionCamera.createDeviceFactory()
2627
const back = factory.getDefaultCamera('back')

apps/simple-camera/__tests__/visioncamera.session.harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe('VisionCamera - Session', () => {
77
let factory: CameraDeviceFactory
88

99
beforeAll(async () => {
10+
await VisionCamera.requestCameraPermission();
1011
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
1112
factory = await VisionCamera.createDeviceFactory()
1213
})

apps/simple-camera/__tests__/visioncamera.video.harness.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ describe('VisionCamera - Video', () => {
2323
let backDevice: CameraDevice
2424

2525
beforeAll(async () => {
26+
await VisionCamera.requestCameraPermission();
27+
await VisionCamera.requestMicrophonePermission();
2628
expect(VisionCamera.cameraPermissionStatus).toBe('authorized')
2729
expect(VisionCamera.microphonePermissionStatus).toBe('authorized')
2830
factory = await VisionCamera.createDeviceFactory()

apps/simple-camera/device-farm-tests/AwsTestSpec.yml

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,6 @@ phases:
1414
- adb wait-for-device
1515
- adb devices -l
1616

17-
# Grant all runtime permissions we need up-front. AWS Device Farm has no UI
18-
# operator to tap through permission dialogs, so any runtime permission
19-
# request would hang the test. The APK is already installed by Device Farm
20-
# before the test spec runs, so `pm grant` targets the installed package.
21-
- |
22-
APP_PACKAGE="com.margelo.nitro.camera.example.simple"
23-
for PERMISSION in \
24-
android.permission.CAMERA \
25-
android.permission.RECORD_AUDIO \
26-
android.permission.ACCESS_FINE_LOCATION \
27-
android.permission.ACCESS_COARSE_LOCATION \
28-
android.permission.READ_EXTERNAL_STORAGE \
29-
android.permission.WRITE_EXTERNAL_STORAGE; do
30-
echo "Granting ${PERMISSION} to ${APP_PACKAGE}"
31-
adb shell pm grant "${APP_PACKAGE}" "${PERMISSION}" || echo "Failed to grant ${PERMISSION} (may not be applicable on this API level)"
32-
done
33-
3417
test:
3518
commands:
3619
- MANUFACTURER="$(adb shell getprop ro.product.manufacturer | tr -d '\r')"
@@ -39,7 +22,7 @@ phases:
3922
- HARNESS_JUNIT="$WORKING_DIRECTORY/harness-results.junit.xml"
4023
- HARNESS_LOG="$WORKING_DIRECTORY/harness-output.log"
4124
- cd apps/simple-camera
42-
- set -o pipefail; HARNESS_ANDROID_DEVICE_MANUFACTURER="$MANUFACTURER" HARNESS_ANDROID_DEVICE_MODEL="$MODEL" CI=true JEST_JUNIT_OUTPUT_FILE="$HARNESS_JUNIT" ~/.bun/bin/bun run test:harness:android -- --reporters=default --reporters=jest-junit 2>&1 | tee "$HARNESS_LOG"
25+
- set -o pipefail; HARNESS_ANDROID_DEVICE_MANUFACTURER="$MANUFACTURER" HARNESS_ANDROID_DEVICE_MODEL="$MODEL" CI=true FORCE_COLOR=1 JEST_JUNIT_OUTPUT_FILE="$HARNESS_JUNIT" ~/.bun/bin/bun run test:harness:android -- --reporters=default --reporters=jest-junit --verbose 2>&1 | tee "$HARNESS_LOG"
4326

4427
artifacts:
4528
- $WORKING_DIRECTORY

apps/simple-camera/device-farm-tests/AwsTestSpecIOS.yml

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,76 @@ phases:
77
commands:
88
- devicefarm-cli use node 20
99
- node -v
10+
- xcodebuild -version
1011
- curl -fsSL https://bun.com/install | bash
11-
- ~/.bun/bin/bun install
1212
- xcrun devicectl list devices
1313

14+
# The XCTEST_UI package contains the Harness XCTest runner IPA and a
15+
# repo archive. Unpack the repo so the Node/Bun Harness tests can run.
16+
- |
17+
set -euo pipefail
18+
19+
REPO_ARCHIVE="$DEVICEFARM_XCTEST_BUILD_DIRECTORY/react-native-vision-camera.zip"
20+
HARNESS_REPO_ROOT="$WORKING_DIRECTORY/react-native-vision-camera"
21+
22+
test -f "$REPO_ARCHIVE"
23+
rm -rf "$HARNESS_REPO_ROOT"
24+
mkdir -p "$HARNESS_REPO_ROOT"
25+
unzip -q "$REPO_ARCHIVE" -d "$HARNESS_REPO_ROOT"
26+
27+
cd "$HARNESS_REPO_ROOT"
28+
~/.bun/bin/bun install --frozen-lockfile
29+
1430
test:
1531
commands:
16-
- HARNESS_JUNIT="$WORKING_DIRECTORY/harness-results.junit.xml"
17-
- HARNESS_LOG="$WORKING_DIRECTORY/harness-output.log"
32+
- HARNESS_ARTIFACTS="$WORKING_DIRECTORY/harness-artifacts"
33+
- HARNESS_JUNIT="$HARNESS_ARTIFACTS/harness-results.junit.xml"
34+
- HARNESS_LOG="$HARNESS_ARTIFACTS/harness-output.log"
35+
- HARNESS_METRO_HOST_FILE="$HARNESS_ARTIFACTS/metro-host-ip.txt"
36+
- mkdir -p "$HARNESS_ARTIFACTS"
1837

19-
# Resolve host IPv6 for Metro. On macOS runners ipv4 won't work, as confirmed by AWS support.
20-
# AWS specifically recommends using the utun interfaces for this, which is what we do here.
21-
# If we fail to resolve an IP address, we error out, as without this the tests won't be able to connect to Metro at all.
38+
# Resolve host IPv6 for Metro. On macOS runners IPv4 won't work, as
39+
# confirmed by AWS support. AWS recommends utun interfaces for this.
2240
- |
23-
METRO_HOST_IP="$(ifconfig utun1 2>/dev/null | awk '/inet6 / {ip=$2} END {print ip}')"
41+
set -euo pipefail
42+
43+
METRO_HOST_IP="$(ifconfig utun1 2>/dev/null | awk '/inet6 / {ip=$2} END {print ip}' || true)"
2444
if [ -z "$METRO_HOST_IP" ]; then
25-
METRO_HOST_IP="$(ifconfig utun0 2>/dev/null | awk '/inet6 / {ip=$2} END {print ip}')"
45+
METRO_HOST_IP="$(ifconfig utun0 2>/dev/null | awk '/inet6 / {ip=$2} END {print ip}' || true)"
2646
fi
47+
2748
METRO_HOST_IP="${METRO_HOST_IP%%%*}"
28-
[ -n "$METRO_HOST_IP" ] || { echo "Failed to resolve Metro host IPv6." >&2; exit 1; }
49+
if [ -z "$METRO_HOST_IP" ]; then
50+
echo "Failed to resolve Metro host IPv6." >&2
51+
exit 1
52+
fi
53+
2954
echo "Using Metro host IPv6 for iOS harness: ${METRO_HOST_IP}"
30-
echo "$METRO_HOST_IP" > "$WORKING_DIRECTORY/metro-host-ip.txt"
55+
echo "$METRO_HOST_IP" > "$HARNESS_METRO_HOST_FILE"
56+
57+
# Actually run the JS Harness tests. Harness owns the XCTest agent
58+
# lifecycle, but uses AWS's generated .xctestrun instead of building one.
59+
- |
60+
set -euo pipefail
61+
62+
: "${DEVICEFARM_DEVICE_UDID:?Missing DEVICEFARM_DEVICE_UDID. Device Farm must provide the physical iOS device UDID.}"
3163
32-
# Find the physical device connected to this runner we want to run the test on:
33-
- IOS_DEVICE_UDID="${DEVICEFARM_DEVICE_UDID:-${DEVICEFARM_DEVICE_NAME:-}}"
34-
- IOS_DEVICES_JSON="$(mktemp)"
35-
- xcrun devicectl list devices --json-output "$IOS_DEVICES_JSON"
36-
- IOS_DEVICE_NAME="$(node -e "const fs=require('node:fs');const payload=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));const devices=payload?.result?.devices ?? [];const udid=process.env.IOS_DEVICE_UDID || '';const matchedByUdid=devices.find((d)=>d?.identifier===udid);const selected=matchedByUdid ?? devices[0] ?? null;const name=selected?.deviceProperties?.name ?? '';if(!name){process.exit(1)};process.stdout.write(name);" "$IOS_DEVICES_JSON")"
37-
- 'echo "Resolved Device Farm iOS device for harness: ${IOS_DEVICE_NAME} (${IOS_DEVICE_UDID:-unknown-udid})"'
64+
cd "$WORKING_DIRECTORY/react-native-vision-camera/apps/simple-camera"
3865
39-
# Actually run the test:
40-
- cd apps/simple-camera
41-
- set -o pipefail; HARNESS_METRO_BIND_HOST="::" HARNESS_IOS_METRO_HOST="$(cat "$WORKING_DIRECTORY/metro-host-ip.txt")" HARNESS_IOS_DEVICE_NAME="$IOS_DEVICE_NAME" HARNESS_DETECT_NATIVE_CRASHES=false CI=true JEST_JUNIT_OUTPUT_FILE="$HARNESS_JUNIT" ~/.bun/bin/bun run test:harness -- --harnessRunner ios --reporters=default --reporters=jest-junit 2>&1 | tee "$HARNESS_LOG"
66+
HARNESS_IOS_XCTESTRUN_FILE="$DEVICEFARM_XCUITESTRUN_FILE" \
67+
HARNESS_IOS_XCTEST_DERIVED_DATA_PATH="$DEVICEFARM_DERIVED_DATA_PATH" \
68+
HARNESS_METRO_BIND_HOST="::" \
69+
HARNESS_IOS_METRO_HOST="$(cat "$HARNESS_METRO_HOST_FILE")" \
70+
HARNESS_IOS_DEVICE_ID="$DEVICEFARM_DEVICE_UDID" \
71+
HARNESS_DETECT_NATIVE_CRASHES=false \
72+
CI=true \
73+
FORCE_COLOR=1 \
74+
JEST_JUNIT_OUTPUT_FILE="$HARNESS_JUNIT" \
75+
~/.bun/bin/bun run test:harness -- \
76+
--harnessRunner ios \
77+
--reporters=default \
78+
--reporters=jest-junit \
79+
--verbose 2>&1 | tee "$HARNESS_LOG"
4280
4381
artifacts:
44-
- $WORKING_DIRECTORY
82+
- $WORKING_DIRECTORY/harness-artifacts

apps/simple-camera/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
},
1717
"dependencies": {
1818
"@react-native-community/blur": "^4.4.1",
19-
"@react-native-harness/platform-apple": "^1.1.0-rc.4",
2019
"@react-native-menu/menu": "^2.0.0",
2120
"@react-native-vector-icons/ionicons": "12.4.1",
2221
"@react-navigation/native": "^7.1.31",
@@ -48,12 +47,13 @@
4847
"@react-native-community/cli": "20.1.2",
4948
"@react-native-community/cli-platform-android": "20.1.2",
5049
"@react-native-community/cli-platform-ios": "20.1.2",
51-
"@react-native-harness/platform-android": "1.1.0-rc.4",
50+
"@react-native-harness/platform-android": "1.2.0-rc.1",
51+
"@react-native-harness/platform-apple": "1.2.0-rc.1",
5252
"@react-native/babel-preset": "0.84.0",
5353
"@react-native/metro-config": "0.84.0",
5454
"@react-native/typescript-config": "0.84.0",
5555
"@types/react": "19.2.14",
56-
"react-native-harness": "1.1.0-rc.4",
56+
"react-native-harness": "1.2.0-rc.1",
5757
"typescript": "5.9.3"
5858
},
5959
"engines": {

0 commit comments

Comments
 (0)