Skip to content

Commit 28fb6bc

Browse files
committed
ci(mobile-e2e): add manual workflow_dispatch for @clerk/expo native components
Lands the workflow file ahead of #8334 so that 'workflow_dispatch' shows up in the Actions UI and can be dispatched against the feature branch for end-to-end validation before the larger PR is merged. The workflow itself does nothing on push or PR — it is manual-only. Test infrastructure (Maestro flows, integration-mobile/, packages/expo unit tests) lives in #8334 and will be merged separately.
1 parent d7317c1 commit 28fb6bc

1 file changed

Lines changed: 267 additions & 0 deletions

File tree

.github/workflows/mobile-e2e.yml

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Manual mobile e2e for @clerk/expo native components.
2+
# Clones clerk-expo-quickstart, builds the NativeComponentQuickstart app,
3+
# and runs Maestro flows on iOS simulator and Android emulator.
4+
#
5+
# Secrets:
6+
# INTEGRATION_INSTANCE_KEYS — JSON map of named test instances
7+
# ({ "<name>": { "pk": "pk_test_...", "sk": "sk_test_..." } }).
8+
# Same secret used by /integration (Playwright). We read the entry named
9+
# EXPO_INSTANCE_NAME (set in env: below).
10+
#
11+
# Test users are provisioned per-run via Clerk Backend API and deleted at
12+
# teardown — same pattern as /integration's createBapiUser.
13+
#
14+
# TODO(SDK team): confirm the instance-name slot to add inside
15+
# INTEGRATION_INSTANCE_KEYS for this workflow (placeholder: "expo-native").
16+
name: "Mobile e2e (@clerk/expo)"
17+
18+
on:
19+
workflow_dispatch:
20+
inputs:
21+
quickstart_ref:
22+
description: "clerk-expo-quickstart git ref (branch, tag, or SHA)"
23+
required: false
24+
default: "main"
25+
exclude_tags:
26+
description: "Maestro tags to exclude (comma-separated)"
27+
required: false
28+
default: "manual,skip"
29+
30+
env:
31+
# TODO(SDK team): replace with the canonical mobile-e2e instance name once confirmed.
32+
EXPO_INSTANCE_NAME: expo-native
33+
34+
concurrency:
35+
group: mobile-e2e-${{ github.ref }}
36+
cancel-in-progress: true
37+
38+
jobs:
39+
android:
40+
name: Android
41+
runs-on: ubuntu-latest
42+
timeout-minutes: 45
43+
defaults:
44+
run:
45+
working-directory: .
46+
steps:
47+
- name: Checkout @clerk/javascript
48+
uses: actions/checkout@v4
49+
50+
- name: Checkout clerk-expo-quickstart
51+
uses: actions/checkout@v4
52+
with:
53+
repository: clerk/clerk-expo-quickstart
54+
ref: ${{ inputs.quickstart_ref }}
55+
path: clerk-expo-quickstart
56+
57+
- uses: pnpm/action-setup@v4
58+
- uses: actions/setup-node@v4
59+
with:
60+
node-version: 20
61+
cache: pnpm
62+
63+
- name: Install monorepo deps
64+
run: pnpm install --frozen-lockfile
65+
66+
- name: Build @clerk/expo
67+
run: pnpm turbo build --filter=@clerk/expo...
68+
69+
- name: Install quickstart deps
70+
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
71+
run: pnpm install
72+
73+
- name: Resolve Clerk instance keys
74+
id: keys
75+
env:
76+
INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }}
77+
run: |
78+
if [ -z "$INTEGRATION_INSTANCE_KEYS" ]; then
79+
echo "::error::INTEGRATION_INSTANCE_KEYS secret is not set"
80+
exit 1
81+
fi
82+
pk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].pk") || {
83+
echo "::error::No entry '$EXPO_INSTANCE_NAME' found in INTEGRATION_INSTANCE_KEYS"
84+
exit 1
85+
}
86+
sk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].sk")
87+
echo "::add-mask::$sk"
88+
echo "pk=$pk" >> "$GITHUB_OUTPUT"
89+
echo "sk=$sk" >> "$GITHUB_OUTPUT"
90+
91+
- name: Write quickstart .env
92+
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
93+
run: |
94+
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
95+
96+
- name: Provision test user via BAPI
97+
id: user
98+
env:
99+
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
100+
run: |
101+
email="ci-${GITHUB_RUN_ID}-${RANDOM}+clerk_test@clerkcookie.com"
102+
password="ClerkCI!$(openssl rand -hex 8)Aa1"
103+
response=$(curl -fsS -X POST https://api.clerk.com/v1/users \
104+
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
105+
-H "Content-Type: application/json" \
106+
-d "{\"email_address\":[\"$email\"],\"password\":\"$password\"}")
107+
user_id=$(echo "$response" | jq -er '.id')
108+
echo "::add-mask::$password"
109+
echo "email=$email" >> "$GITHUB_OUTPUT"
110+
echo "password=$password" >> "$GITHUB_OUTPUT"
111+
echo "user_id=$user_id" >> "$GITHUB_OUTPUT"
112+
113+
- name: Set up JDK 17
114+
uses: actions/setup-java@v4
115+
with:
116+
distribution: temurin
117+
java-version: 17
118+
119+
- name: Install Maestro
120+
run: |
121+
curl -Ls "https://get.maestro.mobile.dev" | bash
122+
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
123+
124+
- name: Run Android e2e
125+
uses: reactivecircus/android-emulator-runner@v2
126+
env:
127+
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
128+
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
129+
with:
130+
api-level: 34
131+
target: google_apis
132+
arch: x86_64
133+
script: |
134+
cd clerk-expo-quickstart/NativeComponentQuickstart
135+
npx expo prebuild --clean
136+
npx expo run:android --variant release --no-bundler
137+
cd ../../integration-mobile
138+
# Maestro doesn't auto-recurse into subdirectories; pass each flow explicitly.
139+
find flows -type f -name "*.yaml" ! -path "*/common/*" -print0 | \
140+
xargs -0 maestro test --exclude-tags "${{ inputs.exclude_tags }}"
141+
142+
- name: Upload Maestro artifacts on failure
143+
if: failure()
144+
uses: actions/upload-artifact@v4
145+
with:
146+
name: maestro-android
147+
path: ~/.maestro/tests
148+
149+
- name: Cleanup test user
150+
if: always() && steps.user.outputs.user_id != ''
151+
env:
152+
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
153+
USER_ID: ${{ steps.user.outputs.user_id }}
154+
run: |
155+
curl -fsS -X DELETE "https://api.clerk.com/v1/users/$USER_ID" \
156+
-H "Authorization: Bearer $CLERK_SECRET_KEY" || true
157+
158+
ios:
159+
name: iOS
160+
runs-on: macos-15
161+
timeout-minutes: 60
162+
steps:
163+
- name: Checkout @clerk/javascript
164+
uses: actions/checkout@v4
165+
166+
- name: Checkout clerk-expo-quickstart
167+
uses: actions/checkout@v4
168+
with:
169+
repository: clerk/clerk-expo-quickstart
170+
ref: ${{ inputs.quickstart_ref }}
171+
path: clerk-expo-quickstart
172+
173+
- uses: pnpm/action-setup@v4
174+
- uses: actions/setup-node@v4
175+
with:
176+
node-version: 20
177+
cache: pnpm
178+
179+
- name: Install monorepo deps
180+
run: pnpm install --frozen-lockfile
181+
182+
- name: Build @clerk/expo
183+
run: pnpm turbo build --filter=@clerk/expo...
184+
185+
- name: Install quickstart deps
186+
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
187+
run: pnpm install
188+
189+
- name: Resolve Clerk instance keys
190+
id: keys
191+
env:
192+
INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }}
193+
run: |
194+
if [ -z "$INTEGRATION_INSTANCE_KEYS" ]; then
195+
echo "::error::INTEGRATION_INSTANCE_KEYS secret is not set"
196+
exit 1
197+
fi
198+
pk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].pk") || {
199+
echo "::error::No entry '$EXPO_INSTANCE_NAME' found in INTEGRATION_INSTANCE_KEYS"
200+
exit 1
201+
}
202+
sk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].sk")
203+
echo "::add-mask::$sk"
204+
echo "pk=$pk" >> "$GITHUB_OUTPUT"
205+
echo "sk=$sk" >> "$GITHUB_OUTPUT"
206+
207+
- name: Write quickstart .env
208+
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
209+
run: |
210+
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
211+
212+
- name: Provision test user via BAPI
213+
id: user
214+
env:
215+
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
216+
run: |
217+
email="ci-${GITHUB_RUN_ID}-${RANDOM}+clerk_test@clerkcookie.com"
218+
password="ClerkCI!$(openssl rand -hex 8)Aa1"
219+
response=$(curl -fsS -X POST https://api.clerk.com/v1/users \
220+
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
221+
-H "Content-Type: application/json" \
222+
-d "{\"email_address\":[\"$email\"],\"password\":\"$password\"}")
223+
user_id=$(echo "$response" | jq -er '.id')
224+
echo "::add-mask::$password"
225+
echo "email=$email" >> "$GITHUB_OUTPUT"
226+
echo "password=$password" >> "$GITHUB_OUTPUT"
227+
echo "user_id=$user_id" >> "$GITHUB_OUTPUT"
228+
229+
- name: Cache SPM
230+
uses: actions/cache@v4
231+
with:
232+
path: ~/Library/Developer/Xcode/DerivedData
233+
key: spm-${{ hashFiles('packages/expo/package.json') }}
234+
235+
- name: Install Maestro
236+
run: |
237+
curl -Ls "https://get.maestro.mobile.dev" | bash
238+
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
239+
240+
- name: Build and run iOS e2e
241+
env:
242+
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
243+
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
244+
run: |
245+
cd clerk-expo-quickstart/NativeComponentQuickstart
246+
npx expo prebuild --clean
247+
npx expo run:ios --configuration Release --no-bundler
248+
cd ../../integration-mobile
249+
# Maestro doesn't auto-recurse into subdirectories; pass each flow explicitly.
250+
find flows -type f -name "*.yaml" ! -path "*/common/*" -print0 | \
251+
xargs -0 maestro test --exclude-tags "${{ inputs.exclude_tags }},androidOnly"
252+
253+
- name: Upload Maestro artifacts on failure
254+
if: failure()
255+
uses: actions/upload-artifact@v4
256+
with:
257+
name: maestro-ios
258+
path: ~/.maestro/tests
259+
260+
- name: Cleanup test user
261+
if: always() && steps.user.outputs.user_id != ''
262+
env:
263+
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
264+
USER_ID: ${{ steps.user.outputs.user_id }}
265+
run: |
266+
curl -fsS -X DELETE "https://api.clerk.com/v1/users/$USER_ID" \
267+
-H "Authorization: Bearer $CLERK_SECRET_KEY" || true

0 commit comments

Comments
 (0)