Skip to content

Commit 5678119

Browse files
committed
feat: startup crash detection
1 parent 9c014a1 commit 5678119

78 files changed

Lines changed: 5654 additions & 521 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010
jobs:
1111
check:
1212
runs-on: ubuntu-latest
13+
timeout-minutes: 5
1314
steps:
1415
- name: Checkout
1516
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

.github/workflows/e2e-tests.yml

Lines changed: 221 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ jobs:
2626
e2e-android:
2727
name: E2E Android
2828
runs-on: ubuntu-22.04
29+
timeout-minutes: 30
2930
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')) }}
3031
env:
3132
HARNESS_DEBUG: true
32-
3333
steps:
3434
- name: Checkout code
3535
uses: actions/checkout@v4
@@ -108,6 +108,7 @@ jobs:
108108
e2e-ios:
109109
name: E2E iOS
110110
runs-on: macos-latest
111+
timeout-minutes: 30
111112
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')) }}
112113
steps:
113114
- name: Checkout code
@@ -194,10 +195,10 @@ jobs:
194195
e2e-web:
195196
name: E2E Web
196197
runs-on: ubuntu-22.04
198+
timeout-minutes: 30
197199
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'web')) }}
198200
env:
199201
HARNESS_DEBUG: true
200-
201202
steps:
202203
- name: Checkout code
203204
uses: actions/checkout@v4
@@ -237,3 +238,221 @@ jobs:
237238
with:
238239
runner: chromium
239240
projectRoot: apps/playground
241+
242+
crash-validate-android:
243+
name: Crash Validation Android
244+
runs-on: ubuntu-22.04
245+
timeout-minutes: 30
246+
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android') }}
247+
env:
248+
HARNESS_DEBUG: true
249+
250+
steps:
251+
- name: Checkout code
252+
uses: actions/checkout@v4
253+
with:
254+
ref: ${{ github.ref }}
255+
fetch-depth: 0
256+
257+
- name: Reclaim disk space
258+
uses: AdityaGarg8/remove-unwanted-software@v5
259+
with:
260+
remove-dotnet: true
261+
remove-haskell: true
262+
remove-codeql: true
263+
remove-docker-images: true
264+
265+
- name: Install pnpm
266+
uses: pnpm/action-setup@v2
267+
with:
268+
version: latest
269+
270+
- name: Setup Node.js
271+
uses: actions/setup-node@v4
272+
with:
273+
node-version: '24.10.0'
274+
cache: 'pnpm'
275+
276+
- name: Metro cache
277+
uses: actions/cache@v4
278+
with:
279+
path: apps/playground/node_modules/.cache/rn-harness/metro-cache
280+
key: metro-cache-${{ hashFiles('apps/playground/node_modules/.cache/rn-harness/metro-cache/**/*') }}
281+
restore-keys: |
282+
metro-cache
283+
284+
- name: Install dependencies
285+
run: |
286+
pnpm install
287+
288+
- name: Build packages
289+
run: |
290+
pnpm nx run-many -t build --projects="packages/*"
291+
292+
- name: Set up JDK 17
293+
uses: actions/setup-java@v3
294+
with:
295+
java-version: '17'
296+
distribution: 'temurin'
297+
298+
- name: Restore APK from cache
299+
id: cache-apk-restore
300+
uses: actions/cache/restore@v4
301+
with:
302+
path: apps/playground/android/app/build/outputs/apk/debug/app-debug.apk
303+
key: apk-playground
304+
305+
- name: Build Android app
306+
if: steps.cache-apk-restore.outputs.cache-hit != 'true'
307+
working-directory: apps/playground
308+
run: |
309+
pnpm nx run @react-native-harness/playground:build-android --tasks=assembleDebug
310+
311+
- name: Save APK to cache
312+
if: steps.cache-apk-restore.outputs.cache-hit != 'true' && success()
313+
uses: actions/cache/save@v4
314+
with:
315+
path: apps/playground/android/app/build/outputs/apk/debug/app-debug.apk
316+
key: apk-playground
317+
318+
- name: Run React Native Harness (expect crash)
319+
id: crash-test
320+
continue-on-error: true
321+
uses: ./actions/android
322+
with:
323+
app: android/app/build/outputs/apk/debug/app-debug.apk
324+
runner: android-crash-pre-rn
325+
projectRoot: apps/playground
326+
harnessArgs: --testPathPattern smoke
327+
328+
- name: Verify crash was detected
329+
shell: bash
330+
run: |
331+
if [ "${{ steps.crash-test.outcome }}" != "failure" ]; then
332+
echo "ERROR: Expected harness to fail (crash not detected)"
333+
exit 1
334+
fi
335+
echo "Crash was correctly detected by the harness"
336+
337+
- name: Verify crash artifacts exist
338+
shell: bash
339+
run: |
340+
CRASH_DIR="apps/playground/.harness/crash-reports"
341+
if [ -d "$CRASH_DIR" ] && [ "$(ls -A "$CRASH_DIR" 2>/dev/null)" ]; then
342+
echo "Crash report artifacts found:"
343+
ls -la "$CRASH_DIR"
344+
else
345+
echo "ERROR: No crash report artifacts found in $CRASH_DIR"
346+
exit 1
347+
fi
348+
349+
crash-validate-ios:
350+
name: Crash Validation iOS
351+
runs-on: macos-latest
352+
timeout-minutes: 30
353+
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios') }}
354+
steps:
355+
- name: Checkout code
356+
uses: actions/checkout@v4
357+
with:
358+
ref: ${{ github.ref }}
359+
fetch-depth: 0
360+
361+
- name: Install pnpm
362+
uses: pnpm/action-setup@v2
363+
with:
364+
version: latest
365+
366+
- name: Setup Node.js
367+
uses: actions/setup-node@v4
368+
with:
369+
node-version: '24.10.0'
370+
cache: 'pnpm'
371+
372+
- name: Metro cache
373+
uses: actions/cache@v4
374+
with:
375+
path: apps/playground/node_modules/.cache/rn-harness/metro-cache
376+
key: metro-cache-${{ hashFiles('apps/playground/node_modules/.cache/rn-harness/metro-cache/**/*') }}
377+
restore-keys: |
378+
metro-cache
379+
380+
- name: Install Watchman
381+
run: brew install watchman
382+
383+
- name: Install dependencies
384+
run: |
385+
pnpm install
386+
387+
- name: Build packages
388+
run: |
389+
pnpm nx run-many -t build --projects="packages/*"
390+
391+
- name: Restore app from cache
392+
id: cache-app-restore
393+
uses: actions/cache/restore@v4
394+
with:
395+
path: ./apps/playground/ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
396+
key: ios-app-playground
397+
398+
- name: CocoaPods cache
399+
if: steps.cache-app-restore.outputs.cache-hit != 'true'
400+
uses: actions/cache@v4
401+
with:
402+
path: |
403+
./apps/playground/ios/Pods
404+
~/Library/Caches/CocoaPods
405+
~/.cocoapods
406+
key: playground-${{ runner.os }}-pods-${{ hashFiles('./apps/playground/ios/Podfile.lock') }}
407+
restore-keys: |
408+
playground-${{ runner.os }}-pods-
409+
410+
- name: Install CocoaPods
411+
if: steps.cache-app-restore.outputs.cache-hit != 'true'
412+
working-directory: apps/playground/ios
413+
run: |
414+
pod install
415+
416+
- name: Build iOS app
417+
if: steps.cache-app-restore.outputs.cache-hit != 'true'
418+
working-directory: apps/playground
419+
run: |
420+
pnpm react-native build-ios --buildFolder ./build --verbose
421+
422+
- name: Save app to cache
423+
if: steps.cache-app-restore.outputs.cache-hit != 'true' && success()
424+
uses: actions/cache/save@v4
425+
with:
426+
path: ./apps/playground/ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
427+
key: ios-app-playground
428+
429+
- name: Run React Native Harness (expect crash)
430+
id: crash-test
431+
continue-on-error: true
432+
uses: ./actions/ios
433+
with:
434+
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
435+
runner: ios-crash-pre-rn
436+
projectRoot: apps/playground
437+
harnessArgs: --testPathPattern smoke
438+
439+
- name: Verify crash was detected
440+
shell: bash
441+
run: |
442+
if [ "${{ steps.crash-test.outcome }}" != "failure" ]; then
443+
echo "ERROR: Expected harness to fail (crash not detected)"
444+
exit 1
445+
fi
446+
echo "Crash was correctly detected by the harness"
447+
448+
- name: Verify crash artifacts exist
449+
shell: bash
450+
run: |
451+
CRASH_DIR="apps/playground/.harness/crash-reports"
452+
if [ -d "$CRASH_DIR" ] && [ "$(ls -A "$CRASH_DIR" 2>/dev/null)" ]; then
453+
echo "Crash report artifacts found:"
454+
ls -la "$CRASH_DIR"
455+
else
456+
echo "ERROR: No crash report artifacts found in $CRASH_DIR"
457+
exit 1
458+
fi

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ yarn-error.log
111111

112112
# testing
113113
/coverage
114+
.harness/
114115
vite.config.*.timestamp*
115116
vitest.config.*.timestamp*
116117

@@ -128,4 +129,3 @@ npm-debug.*
128129
*.orig.*
129130
web-build/
130131
cache/
131-

actions/android/action.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ inputs:
1717
required: false
1818
type: boolean
1919
default: 'true'
20+
harnessArgs:
21+
description: Additional arguments to pass to the Harness CLI
22+
required: false
23+
type: string
24+
default: ''
2025
runs:
2126
using: 'composite'
2227
steps:
@@ -136,13 +141,20 @@ runs:
136141
script: |
137142
echo $(pwd)
138143
adb install -r ${{ inputs.app }}
139-
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }}
144+
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
140145
- name: Upload visual test artifacts
141146
if: always() && inputs.uploadVisualTestArtifacts == 'true'
142147
uses: actions/upload-artifact@v4
143148
with:
144-
name: visual-test-diffs-android
149+
name: visual-test-diffs-${{ inputs.runner }}
145150
path: |
146151
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-diff.png
147152
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-actual.png
148153
if-no-files-found: ignore
154+
- name: Upload crash report artifacts
155+
if: always()
156+
uses: actions/upload-artifact@v4
157+
with:
158+
name: harness-crash-reports-${{ inputs.runner }}
159+
path: ${{ inputs.projectRoot }}/.harness/crash-reports/**/*
160+
if-no-files-found: ignore

actions/ios/action.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ inputs:
1717
required: false
1818
type: boolean
1919
default: 'true'
20+
harnessArgs:
21+
description: Additional arguments to pass to the Harness CLI
22+
required: false
23+
type: string
24+
default: ''
2025
runs:
2126
using: 'composite'
2227
steps:
@@ -63,13 +68,20 @@ runs:
6368
- name: Run E2E tests
6469
shell: bash
6570
working-directory: ${{ inputs.projectRoot }}
66-
run: ${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }}
71+
run: ${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
6772
- name: Upload visual test artifacts
6873
if: always() && inputs.uploadVisualTestArtifacts == 'true'
6974
uses: actions/upload-artifact@v4
7075
with:
71-
name: visual-test-diffs-ios
76+
name: visual-test-diffs-${{ inputs.runner }}
7277
path: |
7378
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-diff.png
7479
${{ inputs.projectRoot }}/**/__image_snapshots__/**/*-actual.png
7580
if-no-files-found: ignore
81+
- name: Upload crash report artifacts
82+
if: always()
83+
uses: actions/upload-artifact@v4
84+
with:
85+
name: harness-crash-reports-${{ inputs.runner }}
86+
path: ${{ inputs.projectRoot }}/.harness/crash-reports/**/*
87+
if-no-files-found: ignore

0 commit comments

Comments
 (0)