Skip to content

Commit 93c3e1f

Browse files
authored
feat: add GitHub Action pre- and post-run hooks (#82)
Adds preRunHook and afterRunHook support to the unified GitHub Action, with the same behavior preserved for the deprecated platform-specific actions. Hooks run in bash, expose Harness context env vars, and ensure afterRunHook runs after Harness even when the test run fails.
1 parent 733fe41 commit 93c3e1f

File tree

11 files changed

+647
-58
lines changed

11 files changed

+647
-58
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ jobs:
9797
app: android/app/build/outputs/apk/debug/app-debug.apk
9898
runner: android
9999
projectRoot: apps/playground
100+
preRunHook: |
101+
echo "HARNESS_PROJECT_ROOT=$HARNESS_PROJECT_ROOT"
102+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
103+
afterRunHook: |
104+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
105+
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"
100106
101107
e2e-ios:
102108
name: E2E iOS
@@ -178,6 +184,12 @@ jobs:
178184
app: ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app
179185
runner: ios
180186
projectRoot: apps/playground
187+
preRunHook: |
188+
echo "HARNESS_PROJECT_ROOT=$HARNESS_PROJECT_ROOT"
189+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
190+
afterRunHook: |
191+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
192+
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"
181193
182194
e2e-web:
183195
name: E2E Web
@@ -218,6 +230,12 @@ jobs:
218230
with:
219231
runner: chromium
220232
projectRoot: apps/playground
233+
preRunHook: |
234+
echo "HARNESS_PROJECT_ROOT=$HARNESS_PROJECT_ROOT"
235+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
236+
afterRunHook: |
237+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
238+
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"
221239
222240
crash-validate-android:
223241
name: Crash Validation Android
@@ -297,6 +315,12 @@ jobs:
297315
runner: android-crash-pre-rn
298316
projectRoot: apps/playground
299317
harnessArgs: --testPathPatterns smoke
318+
preRunHook: |
319+
echo "HARNESS_PROJECT_ROOT=$HARNESS_PROJECT_ROOT"
320+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
321+
afterRunHook: |
322+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
323+
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"
300324
301325
- name: Verify crash was detected
302326
shell: bash
@@ -402,6 +426,12 @@ jobs:
402426
runner: ios-crash-pre-rn
403427
projectRoot: apps/playground
404428
harnessArgs: --testPathPatterns smoke
429+
preRunHook: |
430+
echo "HARNESS_PROJECT_ROOT=$HARNESS_PROJECT_ROOT"
431+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
432+
afterRunHook: |
433+
echo "HARNESS_RUNNER=$HARNESS_RUNNER"
434+
echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE"
405435
406436
- name: Verify crash was detected
407437
shell: bash

action.yml

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ inputs:
3333
required: false
3434
type: boolean
3535
default: 'true'
36+
preRunHook:
37+
description: Shell script to run in bash immediately before Harness starts
38+
required: false
39+
type: string
40+
default: ''
41+
afterRunHook:
42+
description: Shell script to run in bash immediately after Harness finishes
43+
required: false
44+
type: string
45+
default: ''
3646
runs:
3747
using: 'composite'
3848
steps:
@@ -76,7 +86,6 @@ runs:
7686
working-directory: ${{ steps.load-config.outputs.projectRoot }}
7787
run: |
7888
xcrun simctl install booted ${{ inputs.app }}
79-
8089
# ── Android ──────────────────────────────────────────────────────────────
8190
- name: Verify Android config
8291
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
@@ -215,14 +224,105 @@ runs:
215224
echo "runner=npx " >> $GITHUB_OUTPUT
216225
fi
217226
- name: Run E2E tests
227+
id: run-tests
218228
if: fromJson(steps.load-config.outputs.config).platformId != 'android'
219229
shell: bash
220230
working-directory: ${{ steps.load-config.outputs.projectRoot }}
221-
run: ${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
231+
env:
232+
PRE_RUN_HOOK: ${{ inputs.preRunHook }}
233+
AFTER_RUN_HOOK: ${{ inputs.afterRunHook }}
234+
HARNESS_RUNNER: ${{ inputs.runner }}
235+
run: |
236+
export HARNESS_PROJECT_ROOT="$PWD"
237+
238+
if [ -n "$PRE_RUN_HOOK" ]; then
239+
pre_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-pre-run.XXXXXX.sh")"
240+
trap 'rm -f "$pre_hook_file"' EXIT
241+
printf '%s\n' "$PRE_RUN_HOOK" > "$pre_hook_file"
242+
chmod +x "$pre_hook_file"
243+
bash "$pre_hook_file"
244+
rm -f "$pre_hook_file"
245+
trap - EXIT
246+
fi
247+
248+
set +e
249+
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
250+
harness_exit_code=$?
251+
set -e
252+
253+
export HARNESS_EXIT_CODE="$harness_exit_code"
254+
after_run_exit_code=0
255+
if [ -n "$AFTER_RUN_HOOK" ]; then
256+
after_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-after-run.XXXXXX.sh")"
257+
trap 'rm -f "$after_hook_file"' EXIT
258+
printf '%s\n' "$AFTER_RUN_HOOK" > "$after_hook_file"
259+
chmod +x "$after_hook_file"
260+
set +e
261+
bash "$after_hook_file"
262+
after_run_exit_code=$?
263+
set -e
264+
rm -f "$after_hook_file"
265+
trap - EXIT
266+
fi
267+
268+
echo "harness_exit_code=$harness_exit_code" >> "$GITHUB_OUTPUT"
269+
270+
if [ "$harness_exit_code" -ne 0 ]; then
271+
exit "$harness_exit_code"
272+
fi
273+
274+
if [ "$after_run_exit_code" -ne 0 ]; then
275+
exit "$after_run_exit_code"
276+
fi
222277
- name: Run E2E tests
223-
id: run-tests
278+
id: run-tests-android
224279
if: fromJson(steps.load-config.outputs.config).platformId == 'android'
225280
uses: reactivecircus/android-emulator-runner@v2
281+
env:
282+
PRE_RUN_HOOK: ${{ inputs.preRunHook }}
283+
AFTER_RUN_HOOK: ${{ inputs.afterRunHook }}
284+
HARNESS_RUNNER: ${{ inputs.runner }}
285+
# android-emulator-runner executes each script line via `sh -c`, so multi-line
286+
# shell control flow must live in a separate bash script instead of `with.script`.
287+
HARNESS_ANDROID_SESSION_SCRIPT: |-
288+
export HARNESS_PROJECT_ROOT="$PWD"
289+
290+
adb install -r "${{ inputs.app }}"
291+
292+
if [ -n "$PRE_RUN_HOOK" ]; then
293+
pre_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-pre-run.XXXXXX.sh")"
294+
printf "%s\n" "$PRE_RUN_HOOK" > "$pre_hook_file"
295+
chmod +x "$pre_hook_file"
296+
bash "$pre_hook_file"
297+
rm -f "$pre_hook_file"
298+
fi
299+
300+
set +e
301+
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner "${{ inputs.runner }}" ${{ inputs.harnessArgs }}
302+
harness_exit_code=$?
303+
echo "harness_exit_code=$harness_exit_code" >> "$GITHUB_OUTPUT"
304+
set -e
305+
306+
export HARNESS_EXIT_CODE="$harness_exit_code"
307+
after_run_exit_code=0
308+
if [ -n "$AFTER_RUN_HOOK" ]; then
309+
after_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-after-run.XXXXXX.sh")"
310+
printf "%s\n" "$AFTER_RUN_HOOK" > "$after_hook_file"
311+
chmod +x "$after_hook_file"
312+
set +e
313+
bash "$after_hook_file"
314+
after_run_exit_code=$?
315+
set -e
316+
rm -f "$after_hook_file"
317+
fi
318+
319+
if [ "$harness_exit_code" -ne 0 ]; then
320+
exit "$harness_exit_code"
321+
fi
322+
323+
if [ "$after_run_exit_code" -ne 0 ]; then
324+
exit "$after_run_exit_code"
325+
fi
226326
with:
227327
working-directory: ${{ steps.load-config.outputs.projectRoot }}
228328
api-level: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.apiLevel }}
@@ -231,10 +331,10 @@ runs:
231331
avd-name: ${{ fromJson(steps.load-config.outputs.config).config.device.name }}
232332
disable-animations: true
233333
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
234-
script: |
235-
echo $(pwd)
236-
adb install -r ${{ inputs.app }}
237-
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
334+
# Keep `script` to a single line so the emulator action does not split our bash
335+
# session apart before the hooks and Harness command run.
336+
script: >-
337+
harness_script_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-android-run.XXXXXX.sh")"; printf '%s\n' "$HARNESS_ANDROID_SESSION_SCRIPT" > "$harness_script_file"; chmod +x "$harness_script_file"; bash "$harness_script_file"; status=$?; rm -f "$harness_script_file"; exit "$status"
238338
- name: Upload visual test artifacts
239339
if: always() && inputs.uploadVisualTestArtifacts == 'true'
240340
uses: actions/upload-artifact@v4

actions/android/action.yml

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ inputs:
2727
required: false
2828
type: boolean
2929
default: 'true'
30+
preRunHook:
31+
description: Shell script to run in bash immediately before Harness starts
32+
required: false
33+
type: string
34+
default: ''
35+
afterRunHook:
36+
description: Shell script to run in bash immediately after Harness finishes
37+
required: false
38+
type: string
39+
default: ''
3040
runs:
3141
using: 'composite'
3242
steps:
@@ -137,6 +147,50 @@ runs:
137147
- name: Run E2E tests
138148
id: run-tests
139149
uses: reactivecircus/android-emulator-runner@v2
150+
env:
151+
PRE_RUN_HOOK: ${{ inputs.preRunHook }}
152+
AFTER_RUN_HOOK: ${{ inputs.afterRunHook }}
153+
HARNESS_RUNNER: ${{ inputs.runner }}
154+
# android-emulator-runner executes each script line via `sh -c`, so multi-line
155+
# shell control flow must live in a separate bash script instead of `with.script`.
156+
HARNESS_ANDROID_SESSION_SCRIPT: |-
157+
export HARNESS_PROJECT_ROOT="$PWD"
158+
159+
adb install -r "${{ inputs.app }}"
160+
161+
if [ -n "$PRE_RUN_HOOK" ]; then
162+
pre_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-pre-run.XXXXXX.sh")"
163+
printf "%s\n" "$PRE_RUN_HOOK" > "$pre_hook_file"
164+
chmod +x "$pre_hook_file"
165+
bash "$pre_hook_file"
166+
rm -f "$pre_hook_file"
167+
fi
168+
169+
set +e
170+
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner "${{ inputs.runner }}" ${{ inputs.harnessArgs }}
171+
harness_exit_code=$?
172+
set -e
173+
174+
export HARNESS_EXIT_CODE="$harness_exit_code"
175+
after_run_exit_code=0
176+
if [ -n "$AFTER_RUN_HOOK" ]; then
177+
after_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-after-run.XXXXXX.sh")"
178+
printf "%s\n" "$AFTER_RUN_HOOK" > "$after_hook_file"
179+
chmod +x "$after_hook_file"
180+
set +e
181+
bash "$after_hook_file"
182+
after_run_exit_code=$?
183+
set -e
184+
rm -f "$after_hook_file"
185+
fi
186+
187+
if [ "$harness_exit_code" -ne 0 ]; then
188+
exit "$harness_exit_code"
189+
fi
190+
191+
if [ "$after_run_exit_code" -ne 0 ]; then
192+
exit "$after_run_exit_code"
193+
fi
140194
with:
141195
working-directory: ${{ inputs.projectRoot }}
142196
api-level: ${{ fromJson(steps.load-config.outputs.config).config.device.avd.apiLevel }}
@@ -145,10 +199,10 @@ runs:
145199
avd-name: ${{ fromJson(steps.load-config.outputs.config).config.device.name }}
146200
disable-animations: true
147201
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
148-
script: |
149-
echo $(pwd)
150-
adb install -r ${{ inputs.app }}
151-
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
202+
# Keep `script` to a single line so the emulator action does not split our bash
203+
# session apart before the hooks and Harness command run.
204+
script: >-
205+
harness_script_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-android-run.XXXXXX.sh")"; printf '%s\n' "$HARNESS_ANDROID_SESSION_SCRIPT" > "$harness_script_file"; chmod +x "$harness_script_file"; bash "$harness_script_file"; status=$?; rm -f "$harness_script_file"; exit "$status"
152206
- name: Upload visual test artifacts
153207
if: always() && inputs.uploadVisualTestArtifacts == 'true'
154208
uses: actions/upload-artifact@v4

actions/ios/action.yml

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ inputs:
2222
required: false
2323
type: string
2424
default: ''
25+
preRunHook:
26+
description: Shell script to run in bash immediately before Harness starts
27+
required: false
28+
type: string
29+
default: ''
30+
afterRunHook:
31+
description: Shell script to run in bash immediately after Harness finishes
32+
required: false
33+
type: string
34+
default: ''
2535
runs:
2636
using: 'composite'
2737
steps:
@@ -66,9 +76,55 @@ runs:
6676
echo "runner=npx " >> $GITHUB_OUTPUT
6777
fi
6878
- name: Run E2E tests
79+
id: run-tests
6980
shell: bash
7081
working-directory: ${{ inputs.projectRoot }}
71-
run: ${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
82+
env:
83+
PRE_RUN_HOOK: ${{ inputs.preRunHook }}
84+
AFTER_RUN_HOOK: ${{ inputs.afterRunHook }}
85+
HARNESS_RUNNER: ${{ inputs.runner }}
86+
run: |
87+
export HARNESS_PROJECT_ROOT="$PWD"
88+
89+
if [ -n "$PRE_RUN_HOOK" ]; then
90+
pre_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-pre-run.XXXXXX.sh")"
91+
trap 'rm -f "$pre_hook_file"' EXIT
92+
printf '%s\n' "$PRE_RUN_HOOK" > "$pre_hook_file"
93+
chmod +x "$pre_hook_file"
94+
bash "$pre_hook_file"
95+
rm -f "$pre_hook_file"
96+
trap - EXIT
97+
fi
98+
99+
set +e
100+
${{ steps.detect-pm.outputs.runner }}react-native-harness --harnessRunner ${{ inputs.runner }} ${{ inputs.harnessArgs }}
101+
harness_exit_code=$?
102+
set -e
103+
104+
export HARNESS_EXIT_CODE="$harness_exit_code"
105+
after_run_exit_code=0
106+
if [ -n "$AFTER_RUN_HOOK" ]; then
107+
after_hook_file="$(mktemp "${RUNNER_TEMP:-/tmp}/harness-after-run.XXXXXX.sh")"
108+
trap 'rm -f "$after_hook_file"' EXIT
109+
printf '%s\n' "$AFTER_RUN_HOOK" > "$after_hook_file"
110+
chmod +x "$after_hook_file"
111+
set +e
112+
bash "$after_hook_file"
113+
after_run_exit_code=$?
114+
set -e
115+
rm -f "$after_hook_file"
116+
trap - EXIT
117+
fi
118+
119+
echo "harness_exit_code=$harness_exit_code" >> "$GITHUB_OUTPUT"
120+
121+
if [ "$harness_exit_code" -ne 0 ]; then
122+
exit "$harness_exit_code"
123+
fi
124+
125+
if [ "$after_run_exit_code" -ne 0 ]; then
126+
exit "$after_run_exit_code"
127+
fi
72128
- name: Upload visual test artifacts
73129
if: always() && inputs.uploadVisualTestArtifacts == 'true'
74130
uses: actions/upload-artifact@v4

0 commit comments

Comments
 (0)