Skip to content

Commit 300cba3

Browse files
abueideclaude
andcommitted
feat(tests): standardize test suites on user-layer API
Replace internal script sourcing and direct xcrun/adb calls in test suites with user-layer commands (ios.sh, android.sh). This ensures tests exercise the same commands users run. New commands added: - ios.sh deploy, ios.sh app status/stop, ios.sh simulator ready - android.sh deploy, android.sh app status/stop, android.sh emulator ready Suite namespacing via SUITE_NAME env var enables concurrent test execution with isolated runtime state files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2ba18bd commit 300cba3

14 files changed

Lines changed: 635 additions & 744 deletions

File tree

.github/workflows/e2e-full.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Full E2E Tests
1+
tname: Full E2E Tests
22

33
on:
44
workflow_dispatch:

examples/android/devbox.d/android/devices/devices.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414
}
1515
],
1616
"checksum": "8df4d3393b61fbbb08e45cf8762f95c521316938e514527916e4fce88a849d57",
17-
"generated_at": "2026-02-19T00:51:12Z"
17+
"generated_at": "2026-02-19T04:09:58Z"
1818
}

examples/android/tests/test-suite.yaml

Lines changed: 25 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ version: "0.5"
22

33
environment:
44
- "EMU_HEADLESS=${EMU_HEADLESS:-}"
5+
- "SUITE_NAME=android-e2e"
56
- "TEST_TIMEOUT=300"
67
- "BOOT_TIMEOUT=90"
78
- "TEST_TUI=${TEST_TUI:-false}"
@@ -14,130 +15,62 @@ is_strict: true
1415
processes:
1516
# Phase 1: Build - runs first (no dependency)
1617
build-app:
17-
command: |
18-
echo '[BUILD-APP] Starting build process (PID=$$)...'
19-
# Source Android environment to set ANDROID_SDK_ROOT
20-
. ${ANDROID_RUNTIME_DIR}/scripts/init/setup.sh
21-
echo '📦 Building Android app...'
22-
gradle assembleDebug --info
23-
echo '✓ Build complete'
18+
command: "android.sh build"
2419
availability:
2520
restart: "no"
2621

2722
# Phase 2a: Sync AVDs - ensure all AVDs match device definitions
2823
sync-avds:
29-
command: "echo '[SYNC-AVDS] Starting (PID=$$)...' && echo '[SYNC-AVDS] About to source setup.sh...' && . ${ANDROID_RUNTIME_DIR}/scripts/init/setup.sh && echo '[SYNC-AVDS] setup.sh sourced' && echo '🔄 Syncing AVDs with device definitions...' && android.sh devices sync && echo '✓ Sync complete'"
24+
command: "android.sh devices sync"
3025
availability:
3126
restart: "no"
3227

3328
# Phase 2b: Start emulator (depends on sync completing)
34-
# Reuses existing emulator if already running
35-
# Max boot time: 5 minutes, 1 retry allowed, total timeout: 10 minutes
3629
android-emulator:
3730
depends_on:
3831
sync-avds:
3932
condition: process_completed_successfully
4033
environment:
41-
- "ANDROID_EMULATOR_FOREGROUND=1" # Run emulator in foreground for process-compose monitoring
34+
- "ANDROID_EMULATOR_FOREGROUND=1"
4235
command: |
43-
echo "[ANDROID-EMULATOR] Starting emulator in foreground mode..."
44-
# Source Android environment
45-
. ${ANDROID_RUNTIME_DIR}/scripts/init/setup.sh
46-
47-
# Determine which device to start
36+
echo "[ANDROID-EMULATOR] Starting emulator..."
4837
device="${ANDROID_DEFAULT_DEVICE:-max}"
4938
50-
# In pure mode, always start fresh
51-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
52-
echo "🚀 Starting fresh Android emulator (pure mode): $device"
53-
export ANDROID_EMULATOR_PURE=1
54-
55-
# Write expected serial for readiness probe (emulator always starts on 5554 first)
56-
echo "emulator-5554" > ${ANDROID_RUNTIME_DIR}/emulator-serial.txt
57-
58-
# Start emulator in foreground - this blocks and runs the emulator
59-
# Process-compose will monitor it and use readiness probe to check when ready
39+
if [ "${IN_NIX_SHELL:-}" = "pure" ] || [ "${DEVBOX_PURE_SHELL:-}" = "1" ]; then
40+
echo "Starting fresh Android emulator (pure mode): $device"
6041
android.sh emulator start "$device"
6142
else
62-
echo "🚀 Verifying Android emulator is running: $device"
63-
64-
# Write expected serial for readiness probe
65-
serial="emulator-5554"
66-
echo "$serial" > ${ANDROID_RUNTIME_DIR}/emulator-serial.txt
67-
68-
# Check if emulator is already running
69-
if adb devices | grep -q "$serial"; then
70-
echo "✓ Emulator already running: $serial"
71-
else
72-
echo "Starting emulator in background: $device"
73-
# Start emulator in background (no foreground mode for reuse)
74-
unset ANDROID_EMULATOR_FOREGROUND
75-
android.sh emulator start "$device" &
76-
echo "✓ Emulator starting (PID: $!)"
77-
fi
78-
79-
# Wait for emulator to be fully booted
80-
echo "Waiting for emulator to boot..."
81-
max_attempts=60
82-
attempt=0
83-
while [ $attempt -lt $max_attempts ]; do
84-
if adb -s $serial shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
85-
echo "✓ Emulator ready for testing"
86-
break
87-
fi
88-
attempt=$((attempt + 1))
89-
sleep 2
90-
done
91-
92-
if [ $attempt -ge $max_attempts ]; then
93-
echo "ERROR: Emulator did not boot within timeout"
94-
exit 1
95-
fi
43+
echo "Starting Android emulator: $device"
44+
# In non-pure mode, don't run foreground - start in background and wait
45+
unset ANDROID_EMULATOR_FOREGROUND
46+
android.sh emulator start "$device"
9647
97-
# Let process complete - emulator stays running in background
98-
echo "✓ Emulator will remain running after tests complete"
48+
echo "Emulator will remain running after tests complete"
9949
exit 0
10050
fi
10151
availability:
10252
restart: "no"
10353
readiness_probe:
10454
exec:
105-
command: |
106-
serial=$(cat ${ANDROID_RUNTIME_DIR}/emulator-serial.txt 2>/dev/null || echo "")
107-
[ -n "$serial" ] && adb -s $serial shell getprop sys.boot_completed 2>/dev/null | grep -q "1"
55+
command: "android.sh emulator ready"
10856
initial_delay_seconds: 10
10957
period_seconds: 10
11058
timeout_seconds: 180
11159
success_threshold: 1
11260
failure_threshold: 12
11361

114-
# Phase 3: Run app - depends on both build and emulator being ready
115-
run-app:
62+
# Phase 3: Deploy app - depends on both build and emulator being ready
63+
deploy-app:
11664
command: |
117-
# Source Android environment
118-
. ${ANDROID_RUNTIME_DIR}/scripts/init/setup.sh
119-
120-
serial=$(cat ${ANDROID_RUNTIME_DIR}/emulator-serial.txt 2>/dev/null || echo emulator-5554)
121-
export ANDROID_EMULATOR_SERIAL="$serial"
122-
123-
echo "📲 Running app on emulator: $serial"
124-
# Use already-built APK from build-app phase
125-
android.sh run ${ANDROID_APP_APK:-app/build/outputs/apk/debug/app-debug.apk}
65+
android.sh deploy
12666
12767
echo ""
128-
# Resolve app ID: env var > auto-detected from APK > fail
129-
app_id="${ANDROID_APP_ID:-$(cat ${ANDROID_RUNTIME_DIR}/app-id.txt 2>/dev/null || true)}"
130-
if [ -z "$app_id" ]; then
131-
echo "ERROR: Cannot determine app ID. Set ANDROID_APP_ID or run deploy first." >&2
132-
exit 1
133-
fi
134-
135-
echo "Waiting for app to start ($app_id)..."
68+
echo "Waiting for app to start..."
13669
max_attempts=10
13770
attempt=0
13871
while [ $attempt -lt $max_attempts ]; do
139-
if adb -s $serial shell pidof "$app_id" >/dev/null 2>&1; then
140-
echo "App process running (PID: $(adb -s $serial shell pidof "$app_id"))"
72+
if android.sh app status; then
73+
echo "App is running"
14174
mkdir -p reports
14275
touch reports/.e2e-passed
14376
exit 0
@@ -148,10 +81,6 @@ processes:
14881
done
14982
15083
echo "ERROR: App did not start within timeout" >&2
151-
echo "Installed packages:" >&2
152-
adb -s $serial shell pm list packages | grep -i devbox || true
153-
echo "Running processes:" >&2
154-
adb -s $serial shell ps | grep -i devbox || true
15584
exit 1
15685
depends_on:
15786
build-app:
@@ -164,24 +93,16 @@ processes:
16493
# Cleanup - stop everything in pure mode, keep running in dev mode
16594
cleanup-app:
16695
command: |
167-
# Source Android environment
168-
. ${ANDROID_RUNTIME_DIR}/scripts/init/setup.sh
169-
170-
serial=$(cat ${ANDROID_RUNTIME_DIR}/emulator-serial.txt 2>/dev/null || echo emulator-5554)
171-
172-
# In pure mode (CI), stop app and emulator for reproducibility
173-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
174-
echo "🧹 Cleaning up (pure mode): stopping app and emulator..."
175-
app_id="${ANDROID_APP_ID:-$(cat ${ANDROID_RUNTIME_DIR}/app-id.txt 2>/dev/null || true)}"
176-
[ -n "$app_id" ] && adb -s $serial shell am force-stop "$app_id" 2>/dev/null || true
96+
if [ "${IN_NIX_SHELL:-}" = "pure" ] || [ "${DEVBOX_PURE_SHELL:-}" = "1" ]; then
97+
echo "Cleaning up (pure mode)..."
98+
android.sh app stop || true
17799
android.sh emulator stop || true
178-
echo "App stopped, emulator stopped"
100+
echo "App stopped, emulator stopped"
179101
else
180-
# In dev mode, keep app and emulator running for interaction
181-
echo "✓ Test complete - app and emulator still running"
102+
echo "Test complete - app and emulator still running"
182103
fi
183104
depends_on:
184-
run-app:
105+
deploy-app:
185106
condition: process_completed
186107
availability:
187108
restart: "no"

examples/ios/tests/test-suite.yaml

Lines changed: 17 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ version: "0.5"
22

33
environment:
44
- "IOS_DEVICE=${IOS_DEFAULT_DEVICE:-max}"
5+
- "SUITE_NAME=ios-e2e"
56
- "TEST_TIMEOUT=300"
67
- "BOOT_TIMEOUT=120"
78

@@ -25,61 +26,20 @@ processes:
2526
# Phase 3: Start simulator - depends on sync completing
2627
ios-simulator:
2728
command: |
28-
# Ensure reports/logs directory exists
29-
mkdir -p reports/logs
29+
ios.sh simulator start ${IOS_DEVICE}
3030
31-
# In pure mode (IN_NIX_SHELL=pure), start fresh simulator with clean state
32-
# Otherwise, reuse existing simulator if available
33-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
34-
echo "🚀 Starting fresh iOS simulator with clean state (${IOS_DEVICE})..."
35-
output=$(ios.sh simulator start --pure ${IOS_DEVICE} 2>&1)
36-
echo "$output"
37-
38-
# Extract and save test simulator UDID for cleanup
39-
test_udid=$(echo "$output" | grep -oE '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' | tail -1)
40-
if [ -n "$test_udid" ]; then
41-
echo "$test_udid" > reports/logs/ios-test-sim-udid.txt
42-
echo "✓ Test simulator UDID saved: $test_udid"
43-
fi
44-
else
45-
echo "🚀 Starting iOS simulator (${IOS_DEVICE})..."
46-
ios.sh simulator start ${IOS_DEVICE}
47-
48-
# Wait for simulator to be fully booted
49-
echo "Waiting for simulator to boot..."
50-
DEVICE_UDID=$(xcrun simctl list devices | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1 || echo "")
51-
if [ -z "$DEVICE_UDID" ]; then
52-
echo "ERROR: No booted simulator found"
53-
exit 1
54-
fi
55-
xcrun simctl bootstatus "$DEVICE_UDID" >/dev/null 2>&1 || true
56-
57-
echo "✓ Simulator ready for testing"
58-
echo "✓ Simulator will remain running after tests complete"
59-
60-
# In reuse mode, let process complete - simulator stays running in background
61-
exit 0
31+
# In pure mode, keep process alive so cleanup can shut down test simulator
32+
if [ "${IN_NIX_SHELL:-}" = "pure" ] || [ "${DEVBOX_PURE_SHELL:-}" = "1" ]; then
33+
tail -f /dev/null
6234
fi
63-
64-
# Pure mode: Keep process alive so cleanup can shut down test simulator
65-
tail -f /dev/null
6635
depends_on:
6736
sync-simulators:
6837
condition: process_completed_successfully
6938
availability:
7039
restart: "no"
7140
readiness_probe:
7241
exec:
73-
command: |
74-
# Find booted simulator (either test simulator or regular)
75-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
76-
# In pure mode, look for test simulator
77-
DEVICE_UDID=$(xcrun simctl list devices | grep "Test" | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1 || echo "")
78-
else
79-
# In normal mode, look for regular simulator by device selection
80-
DEVICE_UDID=$(xcrun simctl list devices | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1 || echo "")
81-
fi
82-
[ -n "$DEVICE_UDID" ] && xcrun simctl bootstatus "$DEVICE_UDID" >/dev/null 2>&1
42+
command: "ios.sh simulator ready"
8343
initial_delay_seconds: 10
8444
period_seconds: 5
8545
timeout_seconds: 120
@@ -89,31 +49,15 @@ processes:
8949
# Phase 4: Deploy app - depends on both build and simulator
9050
deploy-app:
9151
command: |
92-
# Find the booted simulator (test or regular)
93-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
94-
DEVICE_UDID=$(xcrun simctl list devices | grep "Test" | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1)
95-
else
96-
DEVICE_UDID=$(xcrun simctl list devices | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1)
97-
fi
98-
99-
if [ -z "$DEVICE_UDID" ]; then
100-
echo "ERROR: No booted simulator found"
101-
exit 1
102-
fi
103-
104-
echo "📲 Installing app on simulator: $DEVICE_UDID"
105-
xcrun simctl install "$DEVICE_UDID" "${IOS_APP_ARTIFACT}"
106-
107-
echo "🚀 Launching app: ${IOS_APP_BUNDLE_ID}"
108-
xcrun simctl launch "$DEVICE_UDID" "${IOS_APP_BUNDLE_ID}"
52+
ios.sh deploy
10953
11054
echo ""
11155
echo "Waiting for app to start..."
11256
max_attempts=10
11357
attempt=0
11458
while [ $attempt -lt $max_attempts ]; do
115-
if xcrun simctl spawn "$DEVICE_UDID" launchctl list | grep -q "${IOS_APP_BUNDLE_ID}"; then
116-
echo "App is running"
59+
if ios.sh app status; then
60+
echo "App is running"
11761
exit 0
11862
fi
11963
attempt=$((attempt + 1))
@@ -122,8 +66,6 @@ processes:
12266
done
12367
12468
echo "ERROR: App did not start within timeout" >&2
125-
echo "Checking app status:" >&2
126-
xcrun simctl get_app_container "$DEVICE_UDID" "${IOS_APP_BUNDLE_ID}" 2>&1 || true
12769
exit 1
12870
depends_on:
12971
build-app:
@@ -136,16 +78,8 @@ processes:
13678
# Phase 5: Verify app is running (liveness check)
13779
verify-app-running:
13880
command: |
139-
# Find the booted simulator
140-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
141-
DEVICE_UDID=$(xcrun simctl list devices | grep "Test" | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1)
142-
else
143-
DEVICE_UDID=$(xcrun simctl list devices | grep "Booted" | grep -oE '[0-9A-F-]{36}' | head -1)
144-
fi
145-
146-
echo "Verifying app is running on simulator: $DEVICE_UDID"
147-
if xcrun simctl spawn "$DEVICE_UDID" launchctl list | grep -q "${IOS_APP_BUNDLE_ID}"; then
148-
echo "✓ App is running: ${IOS_APP_BUNDLE_ID}"
81+
if ios.sh app status; then
82+
echo "App is running"
14983
mkdir -p reports
15084
touch reports/.e2e-passed
15185
exit 0
@@ -162,36 +96,13 @@ processes:
16296
# Cleanup - stop everything in pure mode, keep running in dev mode
16397
cleanup:
16498
command: |
165-
# In pure mode (CI), stop app and delete test simulator for reproducibility
166-
if [ "${IN_NIX_SHELL:-}" = "pure" ]; then
167-
echo "🧹 Cleaning up (pure mode): stopping app and deleting test simulator..."
168-
169-
# Get test simulator UDID
170-
test_sim_udid=$(cat reports/logs/ios-test-sim-udid.txt 2>/dev/null || echo "")
171-
172-
if [ -z "$test_sim_udid" ]; then
173-
# Fallback: find test simulator by name
174-
test_sim_udid=$(xcrun simctl list devices | grep "Test" | grep -oE '[0-9A-F-]{36}' | head -1 || true)
175-
fi
176-
177-
if [ -n "$test_sim_udid" ]; then
178-
# Stop the app first
179-
xcrun simctl terminate "$test_sim_udid" "${IOS_APP_BUNDLE_ID}" 2>/dev/null || true
180-
181-
# Shutdown and delete the simulator
182-
xcrun simctl shutdown "$test_sim_udid" >/dev/null 2>&1 || true
183-
xcrun simctl delete "$test_sim_udid" >/dev/null 2>&1 || true
184-
185-
# Clean up temp file
186-
rm -f reports/logs/ios-test-sim-udid.txt
187-
188-
echo "✓ App stopped, test simulator deleted"
189-
else
190-
echo "✓ No test simulator to clean up"
191-
fi
99+
if [ "${IN_NIX_SHELL:-}" = "pure" ] || [ "${DEVBOX_PURE_SHELL:-}" = "1" ]; then
100+
echo "Cleaning up (pure mode)..."
101+
ios.sh app stop || true
102+
ios.sh simulator stop
103+
echo "App stopped, test simulator deleted"
192104
else
193-
# In dev mode, keep app and simulator running for interaction
194-
echo "✓ Test complete - app and simulator still running"
105+
echo "Test complete - app and simulator still running"
195106
fi
196107
depends_on:
197108
verify-app-running:

0 commit comments

Comments
 (0)