Skip to content

Commit fdaf330

Browse files
committed
Run the iOS E2E suites on request from a maintainer's comment
A pull_request run from a fork gets no id token, so the E2E test jobs no longer run on that event. Instead, a maintainer comments `@react-native-bot e2e-tests` on the pull request and e2e-ios-on-demand.yml runs the suites from the default branch: the apps come from the artifacts a run of the pull request's head already built, the Maestro flows from the pull request, the workflows and scripts from the default branch. The two reusable E2E workflows take the run to download from and the ref to take the flows from as inputs, and the download-artifact action can read another run. The login label is derived from the calling workflow as `<owner>.<workflow file>`, which is how each repository's workflows are registered on the remote simulator service.
1 parent 75c54e7 commit fdaf330

6 files changed

Lines changed: 196 additions & 2 deletions

File tree

‎.github/actions/download-artifact/action.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ inputs:
88
path:
99
description: Destination path
1010
required: true
11+
run-id:
12+
description: Workflow run to download from; defaults to the current run
13+
required: false
14+
default: ''
15+
github-token:
16+
description: Token with actions:read, needed when run-id is another run
17+
required: false
18+
default: ''
1119

1220
outputs:
1321
download-path:
@@ -25,6 +33,8 @@ runs:
2533
with:
2634
name: ${{ inputs.name }}
2735
path: ${{ inputs.path }}
36+
run-id: ${{ inputs.run-id || github.run_id }}
37+
github-token: ${{ inputs.github-token }}
2838

2939
- name: Wait before attempt 2
3040
if: ${{ always() && steps.download-1.outcome == 'failure' }}
@@ -41,6 +51,8 @@ runs:
4151
with:
4252
name: ${{ inputs.name }}
4353
path: ${{ inputs.path }}
54+
run-id: ${{ inputs.run-id || github.run_id }}
55+
github-token: ${{ inputs.github-token }}
4456

4557
- name: Wait before attempt 3
4658
if: ${{ always() && steps.download-2.outcome == 'failure' }}
@@ -57,3 +69,5 @@ runs:
5769
with:
5870
name: ${{ inputs.name }}
5971
path: ${{ inputs.path }}
72+
run-id: ${{ inputs.run-id || github.run_id }}
73+
github-token: ${{ inputs.github-token }}

‎.github/actions/maestro-ios/action.yml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ runs:
3535
run: export MAESTRO_VERSION=2.6.1; curl -Ls "https://get.maestro.mobile.dev" | bash
3636
- name: Install sim-remote
3737
uses: software-mansion-labs/argent-cloud-actions/install@v2
38+
- name: Resolve login label
39+
# <owner>.<calling workflow file>, the name this job's workflow is registered under.
40+
id: login
41+
shell: bash
42+
run: echo "label=${GITHUB_REPOSITORY_OWNER}.$(basename "${GITHUB_WORKFLOW_REF%%@*}" .yml)" >> "$GITHUB_OUTPUT"
3843
- name: Acquire remote simulator
3944
# Logs in with the job's OIDC id token; released in the post step.
4045
uses: software-mansion-labs/argent-cloud-actions/acquire@v2
4146
with:
42-
label: react-native-e2e
47+
label: ${{ steps.login.outputs.label }}
4348
- name: Set up JDK 17
4449
uses: actions/setup-java@v5
4550
with:
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: E2E iOS on demand
2+
3+
# Runs the iOS Maestro E2E suites against a pull request when a maintainer
4+
# comments `@react-native-bot e2e-tests` on it. The apps come from the
5+
# artifacts a run of the pull request's head already built, the Maestro flows
6+
# from the pull request, and the workflows and scripts from the default branch.
7+
8+
on:
9+
issue_comment:
10+
types: [created]
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
group: e2e-ios-on-demand-${{ github.event.issue.number }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
request:
21+
if: >-
22+
github.event.issue.pull_request != null &&
23+
startsWith(github.event.comment.body, '@react-native-bot e2e-tests') &&
24+
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
25+
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
28+
actions: read
29+
pull-requests: write
30+
outputs:
31+
head-sha: ${{ steps.find.outputs.head-sha }}
32+
run-id: ${{ steps.find.outputs.run-id }}
33+
steps:
34+
- name: Find the pull request's build
35+
id: find
36+
env:
37+
GH_TOKEN: ${{ github.token }}
38+
GH_REPO: ${{ github.repository }}
39+
PR: ${{ github.event.issue.number }}
40+
run: |
41+
set -euo pipefail
42+
sha=$(gh api "repos/$GH_REPO/pulls/$PR" --jq .head.sha)
43+
needed="RNTesterApp-NewArch-Debug RNTesterApp-NewArch-Release RNTestProject-Debug RNTestProject-Release react-native-package"
44+
run_id=
45+
for id in $(gh api "repos/$GH_REPO/actions/runs?head_sha=$sha&per_page=30" --jq '.workflow_runs[].id'); do
46+
have=$(gh api "repos/$GH_REPO/actions/runs/$id/artifacts?per_page=100" --jq '.artifacts[].name')
47+
missing=
48+
for name in $needed; do
49+
grep -qx "$name" <<<"$have" || missing="$missing $name"
50+
done
51+
if [ -z "$missing" ]; then
52+
run_id=$id
53+
break
54+
fi
55+
done
56+
if [ -z "$run_id" ]; then
57+
echo "::error::no run of $sha has built all of: $needed"
58+
exit 1
59+
fi
60+
echo "head-sha=$sha" >> "$GITHUB_OUTPUT"
61+
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
62+
- name: Acknowledge the request
63+
env:
64+
GH_TOKEN: ${{ github.token }}
65+
GH_REPO: ${{ github.repository }}
66+
COMMENT: ${{ github.event.comment.id }}
67+
run: gh api -X POST "repos/$GH_REPO/issues/comments/$COMMENT/reactions" -f content=rocket > /dev/null
68+
69+
rntester:
70+
needs: request
71+
uses: ./.github/workflows/e2e-ios-rntester.yml
72+
permissions:
73+
contents: read
74+
actions: read
75+
id-token: write
76+
with:
77+
artifact-run-id: ${{ needs.request.outputs.run-id }}
78+
flows-ref: ${{ needs.request.outputs.head-sha }}
79+
fail-on-error: true
80+
81+
templateapp:
82+
needs: request
83+
uses: ./.github/workflows/e2e-ios-templateapp.yml
84+
permissions:
85+
contents: read
86+
actions: read
87+
id-token: write
88+
with:
89+
artifact-run-id: ${{ needs.request.outputs.run-id }}
90+
flows-ref: ${{ needs.request.outputs.head-sha }}
91+
fail-on-error: true
92+
93+
report:
94+
needs: [request, rntester, templateapp]
95+
if: always()
96+
runs-on: ubuntu-latest
97+
permissions:
98+
pull-requests: write
99+
steps:
100+
- name: Comment the results
101+
env:
102+
GH_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN || github.token }}
103+
GH_REPO: ${{ github.repository }}
104+
PR: ${{ github.event.issue.number }}
105+
REQUEST: ${{ needs.request.result }}
106+
HEAD_SHA: ${{ needs.request.outputs.head-sha }}
107+
RNTESTER: ${{ needs.rntester.outputs.status }}
108+
TEMPLATEAPP: ${{ needs.templateapp.outputs.status }}
109+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
110+
run: |
111+
set -euo pipefail
112+
if [ "$REQUEST" != "success" ]; then
113+
body="iOS E2E: no run of the pull request's head has built the apps yet, see [the request]($RUN_URL)."
114+
else
115+
body="iOS E2E on ${HEAD_SHA:0:7} ([run]($RUN_URL)): RNTester **${RNTESTER:-skipped}**, template app **${TEMPLATEAPP:-skipped}**."
116+
fi
117+
gh api -X POST "repos/$GH_REPO/issues/$PR/comments" -f body="$body" > /dev/null

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: E2E iOS RNTester
22

33
permissions:
44
contents: read
5+
actions: read
56
id-token: write
67

78
on:
@@ -10,13 +11,24 @@ on:
1011
fail-on-error:
1112
type: boolean
1213
default: false
14+
artifact-run-id:
15+
description: Run to take the app artifacts from; defaults to this run
16+
type: string
17+
default: ''
18+
flows-ref:
19+
description: Ref to take the Maestro flows from; defaults to the checked-out tree
20+
type: string
21+
default: ''
1322
outputs:
1423
status:
1524
description: 'The result of the E2E tests (success or failure)'
1625
value: ${{ jobs.report.outputs.status }}
1726

1827
jobs:
1928
test:
29+
# A pull_request run has no id token to log into the remote simulators
30+
# with; the suite runs on request from e2e-ios-on-demand.yml instead.
31+
if: github.event_name != 'pull_request'
2032
runs-on: ubuntu-latest
2133
outputs:
2234
status: ${{ steps.report-status.outputs.status }}
@@ -30,10 +42,24 @@ jobs:
3042
- name: Setup Node.js
3143
uses: ./.github/actions/setup-node
3244
- name: Download App
33-
uses: actions/download-artifact@v7
45+
uses: ./.github/actions/download-artifact
3446
with:
3547
name: RNTesterApp-NewArch-${{ matrix.flavor }}
3648
path: /tmp/RNTesterBuild/RNTester.app
49+
run-id: ${{ inputs.artifact-run-id }}
50+
github-token: ${{ github.token }}
51+
- name: Checkout the Maestro flows under test
52+
if: ${{ inputs.flows-ref != '' }}
53+
uses: actions/checkout@v6
54+
with:
55+
ref: ${{ inputs.flows-ref }}
56+
path: .flows-under-test
57+
sparse-checkout: packages/rn-tester/.maestro
58+
- name: Use the Maestro flows under test
59+
if: ${{ inputs.flows-ref != '' }}
60+
run: |
61+
rm -rf packages/rn-tester/.maestro
62+
mv .flows-under-test/packages/rn-tester/.maestro packages/rn-tester/.maestro
3763
- name: Check downloaded folder content
3864
run: ls -lR /tmp/RNTesterBuild
3965
- name: Run E2E Tests

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: E2E iOS Template App
22

33
permissions:
44
contents: read
5+
actions: read
56
id-token: write
67

78
on:
@@ -10,6 +11,14 @@ on:
1011
fail-on-error:
1112
type: boolean
1213
default: false
14+
artifact-run-id:
15+
description: Run to take the app and package artifacts from; defaults to this run, which then builds them
16+
type: string
17+
default: ''
18+
flows-ref:
19+
description: Ref to take the Maestro flows from; defaults to the checked-out tree
20+
type: string
21+
default: ''
1322
outputs:
1423
status:
1524
description: 'The result of the E2E tests (success or failure)'
@@ -18,6 +27,7 @@ on:
1827
jobs:
1928
# Building needs Xcode; the tests drive a remote simulator from Linux.
2029
build:
30+
if: inputs.artifact-run-id == ''
2131
runs-on: macos-26-large
2232
strategy:
2333
fail-fast: false
@@ -101,6 +111,11 @@ jobs:
101111

102112
test:
103113
needs: build
114+
# A pull_request run has no id token to log into the remote simulators
115+
# with; the suite runs on request from e2e-ios-on-demand.yml instead.
116+
if: >-
117+
always() && github.event_name != 'pull_request' &&
118+
(needs.build.result == 'success' || needs.build.result == 'skipped')
104119
runs-on: ubuntu-latest
105120
outputs:
106121
status: ${{ steps.report-status.outputs.status }}
@@ -120,6 +135,8 @@ jobs:
120135
with:
121136
name: RNTestProject-${{ matrix.flavor }}
122137
path: /tmp/RNTestProjectBuild/RNTestProject.app
138+
run-id: ${{ inputs.artifact-run-id }}
139+
github-token: ${{ github.token }}
123140
- name: Check downloaded folder content
124141
run: ls -l /tmp/RNTestProjectBuild/RNTestProject.app
125142
- name: Download React Native Package
@@ -128,6 +145,20 @@ jobs:
128145
with:
129146
name: react-native-package
130147
path: /tmp/react-native-tmp
148+
run-id: ${{ inputs.artifact-run-id }}
149+
github-token: ${{ github.token }}
150+
- name: Checkout the Maestro flows under test
151+
if: ${{ inputs.flows-ref != '' }}
152+
uses: actions/checkout@v6
153+
with:
154+
ref: ${{ inputs.flows-ref }}
155+
path: .flows-under-test
156+
sparse-checkout: scripts/e2e/.maestro
157+
- name: Use the Maestro flows under test
158+
if: ${{ inputs.flows-ref != '' }}
159+
run: |
160+
rm -rf scripts/e2e/.maestro
161+
mv .flows-under-test/scripts/e2e/.maestro scripts/e2e/.maestro
131162
- name: Configure git
132163
if: ${{ matrix.flavor == 'Debug' }}
133164
shell: bash

‎.github/workflows/test-all.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010

1111
permissions:
1212
contents: read
13+
actions: read
1314
id-token: write
1415

1516
jobs:

0 commit comments

Comments
 (0)