Skip to content

Commit 043a531

Browse files
GijsWeteringsfacebook-github-bot
authored andcommitted
Fail CI retry chains on the job that actually failed
Summary: The E2E and Fantom retry chains reported their final failure through a separate `report` job. The `test` job always succeeded (its test step used `continue-on-error: true`), and `report` read its `status` output and exited 1 on the last attempt. This caused three problems: - The red check was a five-second status job, not the job that ran the tests, so finding the real failure meant opening a green job and looking for the step with a warning icon. - "Re-run failed jobs" re-ran only `report`. It read the previous attempt's cached `status` output and failed again without running any tests. - The Debug and Release matrix legs wrote the same `status` output, and GitHub keeps only the last value written to a given output name. If one flavor failed and the other passed and finished later, `status` came out empty, so no retry ran and the failure was dropped silently. This change: - Removes the `report` jobs. The test step runs as two copies of the same step, gated on `inputs.fail-on-error`: earlier attempts use `continue-on-error: true`, so they stay green and record the failure in the `status` output, and the final attempt runs without it, so the test step itself fails the job. The two copies share their `with:` block through a YAML anchor. (A single step with `continue-on-error: ${{ !inputs.fail-on-error }}` doesn't work: GitHub evaluated it as true on the final attempt, so the failure stayed hidden. Step `if:` conditions read `inputs` correctly.) - Has each matrix leg write a flavor-specific output (`debug_status` / `release_status`). GitHub combines outputs with different names across matrix legs. The reusable workflow's `status` output is `debug_status || release_status`, or `jobs.test.outputs.status` for the single-job Fantom workflow. - Also triggers the retry jobs in `test-all.yml` on `needs.<attempt>.result == 'failure'`. The iOS `report` jobs counted a failed `build` or `test` job as a failure, and this keeps that behavior for every workflow. The flavor is lowercased with `tr` rather than `${FLAVOR,,}` because the macOS runners' default `bash` may be 3.2. A flavor with no matching output fails the step with an error instead of writing an output nothing reads; a red `test` job still triggers the retry through `needs.<attempt>.result`. Changelog: [Internal] Differential Revision: D128043004
1 parent dfed2d9 commit 043a531

6 files changed

Lines changed: 126 additions & 128 deletions

File tree

‎.github/workflows/e2e-android-rntester.yml‎

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ on:
1414
default: 0
1515
outputs:
1616
status:
17-
description: 'The result of the E2E tests (success or failure)'
18-
value: ${{ jobs.report.outputs.status }}
17+
description: "'failure' if the E2E tests failed, empty otherwise"
18+
value: ${{ jobs.test.outputs.debug_status || jobs.test.outputs.release_status }}
1919

2020
jobs:
2121
test:
2222
runs-on: 4-core-ubuntu
23+
# Matrix legs write differently named outputs so they are combined
24+
# instead of the last leg to finish overwriting the others.
2325
outputs:
24-
status: ${{ steps.report-status.outputs.status }}
26+
debug_status: ${{ steps.report-status.outputs.debug_status }}
27+
release_status: ${{ steps.report-status.outputs.release_status }}
2528
strategy:
2629
fail-fast: false
2730
matrix:
@@ -59,11 +62,11 @@ jobs:
5962
echo "should-run=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
6063
- name: Run E2E Tests
6164
id: run-tests
62-
if: steps.test-state.outputs.should-run == 'true'
65+
if: ${{ steps.test-state.outputs.should-run == 'true' && !inputs.fail-on-error }}
6366
continue-on-error: true
6467
uses: ./.github/actions/maestro-android
6568
timeout-minutes: 90
66-
with:
69+
with: &test-inputs
6770
app-path: ./packages/rn-tester/android/app/build/outputs/apk/${{ matrix.flavor }}/app-arm64-v8a-${{ matrix.flavor }}.apk
6871
app-id: com.facebook.react.uiapp
6972
maestro-flow: ./packages/rn-tester/.maestro
@@ -75,6 +78,14 @@ jobs:
7578
# Running the debug ARM64 APK through native translation can delay
7679
# React commits long enough for Maestro to miss taps and assertions.
7780
exclude-tags: ${{ matrix.flavor == 'debug' && 'android-release-only' || '' }}
81+
- name: Run E2E Tests
82+
# Same step without continue-on-error, so the final attempt fails
83+
# here. A step's continue-on-error expression doesn't see inputs.
84+
id: run-tests-final
85+
if: ${{ steps.test-state.outputs.should-run == 'true' && inputs.fail-on-error }}
86+
uses: ./.github/actions/maestro-android
87+
timeout-minutes: 90
88+
with: *test-inputs
7889
- name: Store per-flow test state
7990
if: always()
8091
uses: ./.github/actions/upload-artifact
@@ -85,23 +96,15 @@ jobs:
8596
path: /tmp/maestro-android-state/results.json
8697
- name: Report status
8798
id: report-status
88-
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
89-
run: echo "status=failure" >> $GITHUB_OUTPUT
90-
91-
report:
92-
needs: test
93-
if: always()
94-
runs-on: ubuntu-latest
95-
outputs:
96-
status: ${{ steps.check.outputs.status }}
97-
steps:
98-
- id: check
99+
if: ${{ always() && (steps.run-tests.outcome == 'failure' || steps.run-tests-final.outcome == 'failure') }}
100+
env:
101+
FLAVOR: ${{ matrix.flavor }}
99102
run: |
100-
if [[ "${{ needs.test.outputs.status }}" == "failure" ]]; then
101-
echo "status=failure" >> $GITHUB_OUTPUT
102-
else
103-
echo "status=success" >> $GITHUB_OUTPUT
104-
fi
105-
- name: Fail if needed
106-
if: ${{ inputs.fail-on-error && steps.check.outputs.status == 'failure' }}
107-
run: exit 1
103+
flavor=$(echo "$FLAVOR" | tr "[:upper:]" "[:lower:]")
104+
case "$flavor" in
105+
debug | release) echo "${flavor}_status=failure" >> "$GITHUB_OUTPUT" ;;
106+
*)
107+
echo "::error::No job output for flavor '$FLAVOR'. Add a ${flavor}_status output to this job and to the workflow status output."
108+
exit 1
109+
;;
110+
esac

‎.github/workflows/e2e-android-templateapp.yml‎

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ on:
1414
default: 0
1515
outputs:
1616
status:
17-
description: 'The result of the E2E tests (success or failure)'
18-
value: ${{ jobs.report.outputs.status }}
17+
description: "'failure' if the E2E tests failed, empty otherwise"
18+
value: ${{ jobs.test.outputs.debug_status || jobs.test.outputs.release_status }}
1919

2020
jobs:
2121
test:
2222
runs-on: 4-core-ubuntu
23+
# Matrix legs write differently named outputs so they are combined
24+
# instead of the last leg to finish overwriting the others.
2325
outputs:
24-
status: ${{ steps.report-status.outputs.status }}
26+
debug_status: ${{ steps.report-status.outputs.debug_status }}
27+
release_status: ${{ steps.report-status.outputs.release_status }}
2528
strategy:
2629
fail-fast: false
2730
matrix:
@@ -81,11 +84,11 @@ jobs:
8184
echo "should-run=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
8285
- name: Run E2E Tests
8386
id: run-tests
84-
if: steps.test-state.outputs.should-run == 'true'
87+
if: ${{ steps.test-state.outputs.should-run == 'true' && !inputs.fail-on-error }}
8588
continue-on-error: true
8689
uses: ./.github/actions/maestro-android
8790
timeout-minutes: 90
88-
with:
91+
with: &test-inputs
8992
app-path: /tmp/android-templateapp/app-${{ matrix.flavor }}.apk
9093
app-id: com.rntestproject
9194
maestro-flow: ./scripts/e2e/.maestro/
@@ -94,6 +97,14 @@ jobs:
9497
emulator-api-level: '35'
9598
emulator-target: google_apis
9699
emulator-arch: x86_64
100+
- name: Run E2E Tests
101+
# Same step without continue-on-error, so the final attempt fails
102+
# here. A step's continue-on-error expression doesn't see inputs.
103+
id: run-tests-final
104+
if: ${{ steps.test-state.outputs.should-run == 'true' && inputs.fail-on-error }}
105+
uses: ./.github/actions/maestro-android
106+
timeout-minutes: 90
107+
with: *test-inputs
97108
- name: Store per-flow test state
98109
if: always()
99110
uses: ./.github/actions/upload-artifact
@@ -104,23 +115,15 @@ jobs:
104115
path: /tmp/maestro-android-state/results.json
105116
- name: Report status
106117
id: report-status
107-
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
108-
run: echo "status=failure" >> $GITHUB_OUTPUT
109-
110-
report:
111-
needs: test
112-
if: always()
113-
runs-on: ubuntu-latest
114-
outputs:
115-
status: ${{ steps.check.outputs.status }}
116-
steps:
117-
- id: check
118+
if: ${{ always() && (steps.run-tests.outcome == 'failure' || steps.run-tests-final.outcome == 'failure') }}
119+
env:
120+
FLAVOR: ${{ matrix.flavor }}
118121
run: |
119-
if [[ "${{ needs.test.outputs.status }}" == "failure" ]]; then
120-
echo "status=failure" >> $GITHUB_OUTPUT
121-
else
122-
echo "status=success" >> $GITHUB_OUTPUT
123-
fi
124-
- name: Fail if needed
125-
if: ${{ inputs.fail-on-error && steps.check.outputs.status == 'failure' }}
126-
run: exit 1
122+
flavor=$(echo "$FLAVOR" | tr "[:upper:]" "[:lower:]")
123+
case "$flavor" in
124+
debug | release) echo "${flavor}_status=failure" >> "$GITHUB_OUTPUT" ;;
125+
*)
126+
echo "::error::No job output for flavor '$FLAVOR'. Add a ${flavor}_status output to this job and to the workflow status output."
127+
exit 1
128+
;;
129+
esac

‎.github/workflows/e2e-ios-rntester.yml‎

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ on:
1111
default: false
1212
outputs:
1313
status:
14-
description: 'The result of the E2E tests (success or failure)'
15-
value: ${{ jobs.report.outputs.status }}
14+
description: "'failure' if the E2E tests failed, empty otherwise"
15+
value: ${{ jobs.test.outputs.debug_status || jobs.test.outputs.release_status }}
1616

1717
jobs:
1818
test:
1919
runs-on: macos-26-large
20+
# Matrix legs write differently named outputs so they are combined
21+
# instead of the last leg to finish overwriting the others.
2022
outputs:
21-
status: ${{ steps.report-status.outputs.status }}
23+
debug_status: ${{ steps.report-status.outputs.debug_status }}
24+
release_status: ${{ steps.report-status.outputs.release_status }}
2225
strategy:
2326
fail-fast: false
2427
matrix:
@@ -37,35 +40,34 @@ jobs:
3740
run: ls -lR /tmp/RNTesterBuild
3841
- name: Run E2E Tests
3942
id: run-tests
43+
if: ${{ !inputs.fail-on-error }}
4044
continue-on-error: true
4145
uses: ./.github/actions/maestro-ios
42-
with:
46+
with: &test-inputs
4347
app-path: '/tmp/RNTesterBuild/RNTester.app'
4448
app-id: com.meta.RNTester.localDevelopment
4549
maestro-flow: ./packages/rn-tester/.maestro/
4650
flavor: ${{ matrix.flavor }}
4751
device-model: iPhone-17-Pro
4852
device-os: iOS-26-2
53+
- name: Run E2E Tests
54+
# Same step without continue-on-error, so the final attempt fails
55+
# here. A step's continue-on-error expression doesn't see inputs.
56+
id: run-tests-final
57+
if: ${{ inputs.fail-on-error }}
58+
uses: ./.github/actions/maestro-ios
59+
with: *test-inputs
4960
- name: Report status
5061
id: report-status
51-
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
52-
run: echo "status=failure" >> $GITHUB_OUTPUT
53-
54-
report:
55-
needs: test
56-
if: always()
57-
runs-on: ubuntu-latest
58-
outputs:
59-
status: ${{ steps.check.outputs.status }}
60-
steps:
61-
- id: check
62+
if: ${{ always() && (steps.run-tests.outcome == 'failure' || steps.run-tests-final.outcome == 'failure') }}
63+
env:
64+
FLAVOR: ${{ matrix.flavor }}
6265
run: |
63-
if [[ "${{ needs.test.result }}" != "success" ||
64-
"${{ needs.test.outputs.status }}" == "failure" ]]; then
65-
echo "status=failure" >> $GITHUB_OUTPUT
66-
else
67-
echo "status=success" >> $GITHUB_OUTPUT
68-
fi
69-
- name: Fail if needed
70-
if: ${{ inputs.fail-on-error && steps.check.outputs.status == 'failure' }}
71-
run: exit 1
66+
flavor=$(echo "$FLAVOR" | tr "[:upper:]" "[:lower:]")
67+
case "$flavor" in
68+
debug | release) echo "${flavor}_status=failure" >> "$GITHUB_OUTPUT" ;;
69+
*)
70+
echo "::error::No job output for flavor '$FLAVOR'. Add a ${flavor}_status output to this job and to the workflow status output."
71+
exit 1
72+
;;
73+
esac

‎.github/workflows/e2e-ios-templateapp.yml‎

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ on:
1111
default: false
1212
outputs:
1313
status:
14-
description: 'The result of the E2E tests (success or failure)'
15-
value: ${{ jobs.report.outputs.status }}
14+
description: "'failure' if the E2E tests failed, empty otherwise"
15+
value: ${{ jobs.test.outputs.debug_status || jobs.test.outputs.release_status }}
1616

1717
jobs:
1818
build:
@@ -100,8 +100,11 @@ jobs:
100100
test:
101101
needs: build
102102
runs-on: macos-26-large
103+
# Matrix legs write differently named outputs so they are combined
104+
# instead of the last leg to finish overwriting the others.
103105
outputs:
104-
status: ${{ steps.report-status.outputs.status }}
106+
debug_status: ${{ steps.report-status.outputs.debug_status }}
107+
release_status: ${{ steps.report-status.outputs.release_status }}
105108
strategy:
106109
fail-fast: false
107110
matrix:
@@ -149,35 +152,33 @@ jobs:
149152
node ./scripts/e2e/init-project-e2e.js --projectName RNTestProject --currentBranch $BRANCH --directory /tmp/RNTestProject --pathToLocalReactNative $REACT_NATIVE_PKG
150153
- name: Run E2E Tests
151154
id: run-tests
155+
if: ${{ !inputs.fail-on-error }}
152156
continue-on-error: true
153157
uses: ./.github/actions/maestro-ios
154-
with:
158+
with: &test-inputs
155159
app-path: '/tmp/RNTestProjectBuild/RNTestProject.app'
156160
app-id: org.reactjs.native.example.RNTestProject
157161
maestro-flow: ./scripts/e2e/.maestro/
158162
flavor: ${{ matrix.flavor }}
159163
working-directory: /tmp/RNTestProject
164+
- name: Run E2E Tests
165+
# Same step without continue-on-error, so the final attempt fails
166+
# here. A step's continue-on-error expression doesn't see inputs.
167+
id: run-tests-final
168+
if: ${{ inputs.fail-on-error }}
169+
uses: ./.github/actions/maestro-ios
170+
with: *test-inputs
160171
- name: Report status
161172
id: report-status
162-
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
163-
run: echo "status=failure" >> $GITHUB_OUTPUT
164-
165-
report:
166-
needs: [build, test]
167-
if: always()
168-
runs-on: ubuntu-latest
169-
outputs:
170-
status: ${{ steps.check.outputs.status }}
171-
steps:
172-
- id: check
173+
if: ${{ always() && (steps.run-tests.outcome == 'failure' || steps.run-tests-final.outcome == 'failure') }}
174+
env:
175+
FLAVOR: ${{ matrix.flavor }}
173176
run: |
174-
if [[ "${{ needs.build.result }}" != "success" ||
175-
"${{ needs.test.result }}" != "success" ||
176-
"${{ needs.test.outputs.status }}" == "failure" ]]; then
177-
echo "status=failure" >> $GITHUB_OUTPUT
178-
else
179-
echo "status=success" >> $GITHUB_OUTPUT
180-
fi
181-
- name: Fail if needed
182-
if: ${{ inputs.fail-on-error && steps.check.outputs.status == 'failure' }}
183-
run: exit 1
177+
flavor=$(echo "$FLAVOR" | tr "[:upper:]" "[:lower:]")
178+
case "$flavor" in
179+
debug | release) echo "${flavor}_status=failure" >> "$GITHUB_OUTPUT" ;;
180+
*)
181+
echo "::error::No job output for flavor '$FLAVOR'. Add a ${flavor}_status output to this job and to the workflow status output."
182+
exit 1
183+
;;
184+
esac

‎.github/workflows/fantom-tests.yml‎

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ on:
1111
default: false
1212
outputs:
1313
status:
14-
description: 'The result of the Fantom tests (success or failure)'
15-
value: ${{ jobs.report.outputs.status }}
14+
description: "'failure' if the Fantom tests failed, empty otherwise"
15+
value: ${{ jobs.test.outputs.status }}
1616

1717
jobs:
1818
test:
@@ -28,27 +28,16 @@ jobs:
2828
uses: actions/checkout@v6
2929
- name: Run Fantom Tests
3030
id: run-tests
31+
if: ${{ !inputs.fail-on-error }}
3132
continue-on-error: true
3233
uses: ./.github/actions/run-fantom-tests
34+
- name: Run Fantom Tests
35+
# Same step without continue-on-error, so the final attempt fails
36+
# here. A step's continue-on-error expression doesn't see inputs.
37+
id: run-tests-final
38+
if: ${{ inputs.fail-on-error }}
39+
uses: ./.github/actions/run-fantom-tests
3340
- name: Report status
3441
id: report-status
35-
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
42+
if: ${{ always() && (steps.run-tests.outcome == 'failure' || steps.run-tests-final.outcome == 'failure') }}
3643
run: echo "status=failure" >> $GITHUB_OUTPUT
37-
38-
report:
39-
needs: test
40-
if: always()
41-
runs-on: ubuntu-latest
42-
outputs:
43-
status: ${{ steps.check.outputs.status }}
44-
steps:
45-
- id: check
46-
run: |
47-
if [[ "${{ needs.test.outputs.status }}" == "failure" ]]; then
48-
echo "status=failure" >> $GITHUB_OUTPUT
49-
else
50-
echo "status=success" >> $GITHUB_OUTPUT
51-
fi
52-
- name: Fail if needed
53-
if: ${{ inputs.fail-on-error && steps.check.outputs.status == 'failure' }}
54-
run: exit 1

0 commit comments

Comments
 (0)