Skip to content

Mobile e2e (@clerk/expo) #42

Mobile e2e (@clerk/expo)

Mobile e2e (@clerk/expo) #42

Workflow file for this run

# Manual mobile e2e for @clerk/expo native components.
# Clones clerk-expo-quickstart, builds the NativeComponentQuickstart app,
# and runs Maestro flows on iOS simulator and Android emulator.
#
# Secrets:
# INTEGRATION_STAGING_INSTANCE_KEYS — JSON map of named staging test instances
# ({ "<name>": { "pk": "pk_test_...", "sk": "sk_test_..." } }).
# Same secret used by /integration (Playwright) staging jobs. We read the
# entry named EXPO_INSTANCE_NAME (set in env: below).
#
# Test users are provisioned per-run via Clerk Backend API and deleted at
# teardown — same pattern as /integration's createBapiUser.
name: "Mobile e2e (@clerk/expo)"
on:
workflow_dispatch:
inputs:
quickstart_ref:
description: "clerk-expo-quickstart git ref (branch, tag, or SHA)"
required: false
default: "main"
exclude_tags:
description: "Maestro tags to exclude (comma-separated)"
required: false
default: "manual,skip"
clerk_android_ref:
description: "clerk-android git ref to publish as a mavenLocal snapshot and test @clerk/expo against. Leave empty to test the versions already pinned in packages/expo/android/build.gradle."
required: false
default: ""
clerk_android_snapshot_suffix:
description: "Suffix appended to the clerk-android version when clerk_android_ref is set (e.g. '-compat-12345678'). Required when clerk_android_ref is set."
required: false
default: ""
# Auto-run on Renovate PRs that bump the clerkAndroid* pins. The dep bump is
# already applied at the PR's merge ref, so we just need to build + Maestro
# against it — no clerk-android checkout/snapshot publish is needed in this path.
pull_request:
paths:
- "packages/expo/android/build.gradle"
env:
EXPO_INSTANCE_NAME: clerkstage-with-native-components
concurrency:
group: mobile-e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
android:
name: Android
# Restrict pull_request runs to Renovate to avoid burning emulator time on
# human PRs that touch the same path. Manual dispatches always run.
if: github.event_name != 'pull_request' || github.event.pull_request.user.login == 'renovate[bot]'
runs-on: 'blacksmith-8vcpu-ubuntu-2204'
timeout-minutes: 45
defaults:
run:
working-directory: .
steps:
- name: Checkout @clerk/javascript
uses: actions/checkout@v4
- name: Checkout clerk-expo-quickstart
uses: actions/checkout@v4
with:
repository: clerk/clerk-expo-quickstart
ref: ${{ inputs.quickstart_ref || 'main' }}
path: clerk-expo-quickstart
- name: Checkout clerk-android
if: inputs.clerk_android_ref != ''
uses: actions/checkout@v4
with:
repository: clerk/clerk-android
ref: ${{ inputs.clerk_android_ref }}
path: clerk-android
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install monorepo deps
run: pnpm install --frozen-lockfile
- name: Build @clerk/expo
run: pnpm turbo build --filter=@clerk/expo...
- name: Install quickstart deps
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
run: pnpm install
- name: Resolve Clerk instance keys
id: keys
env:
INTEGRATION_STAGING_INSTANCE_KEYS: ${{ secrets.INTEGRATION_STAGING_INSTANCE_KEYS }}
run: node scripts/resolve-instance-keys.mjs INTEGRATION_STAGING_INSTANCE_KEYS "$EXPO_INSTANCE_NAME"
- name: Write quickstart .env
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
run: |
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
- name: Provision test user via BAPI
id: user
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
run: |
email="ci-${GITHUB_RUN_ID}-${RANDOM}+clerk_test@clerkcookie.com"
password="ClerkCI!$(openssl rand -hex 8)Aa1"
response=$(curl -fsS -X POST https://api.clerk.com/v1/users \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d "{\"email_address\":[\"$email\"],\"password\":\"$password\"}")
user_id=$(echo "$response" | jq -er '.id')
echo "::add-mask::$password"
echo "email=$email" >> "$GITHUB_OUTPUT"
echo "password=$password" >> "$GITHUB_OUTPUT"
echo "user_id=$user_id" >> "$GITHUB_OUTPUT"
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
# 21 (not 17) because clerk-android requires it to build. Expo's
# quickstart Android build works with 21 as well.
java-version: 21
- name: Validate clerk-android snapshot inputs
if: inputs.clerk_android_ref != '' && inputs.clerk_android_snapshot_suffix == ''
run: |
echo "::error::clerk_android_ref is set but clerk_android_snapshot_suffix is empty. Provide a suffix (e.g. '-compat-${{ github.run_id }}')."
exit 1
- name: Compute clerk-android snapshot versions
id: android_versions
if: inputs.clerk_android_ref != ''
working-directory: clerk-android
run: |
suffix="${{ inputs.clerk_android_snapshot_suffix }}"
api="$(awk -F= '/^CLERK_API_VERSION=/{print $2}' gradle.properties | tr -d '[:space:]')${suffix}"
ui="$(awk -F= '/^CLERK_UI_VERSION=/{print $2}' gradle.properties | tr -d '[:space:]')${suffix}"
telemetry="$(awk -F= '/^CLERK_TELEMETRY_VERSION=/{print $2}' gradle.properties | tr -d '[:space:]')${suffix}"
echo "api=$api" >> "$GITHUB_OUTPUT"
echo "ui=$ui" >> "$GITHUB_OUTPUT"
echo "telemetry=$telemetry" >> "$GITHUB_OUTPUT"
- name: Publish clerk-android snapshot to mavenLocal
if: inputs.clerk_android_ref != ''
working-directory: clerk-android
run: |
sed -i "s/^CLERK_API_VERSION=.*/CLERK_API_VERSION=${{ steps.android_versions.outputs.api }}/" gradle.properties
sed -i "s/^CLERK_UI_VERSION=.*/CLERK_UI_VERSION=${{ steps.android_versions.outputs.ui }}/" gradle.properties
sed -i "s/^CLERK_TELEMETRY_VERSION=.*/CLERK_TELEMETRY_VERSION=${{ steps.android_versions.outputs.telemetry }}/" gradle.properties
chmod +x gradlew
./gradlew :source:api:publishToMavenLocal :source:ui:publishToMavenLocal :source:telemetry:publishToMavenLocal -x signMavenPublication
- name: Patch @clerk/expo build.gradle to use snapshot
if: inputs.clerk_android_ref != ''
run: |
sed -i "s/clerkAndroidApiVersion = \".*\"/clerkAndroidApiVersion = \"${{ steps.android_versions.outputs.api }}\"/" packages/expo/android/build.gradle
sed -i "s/clerkAndroidUiVersion = \".*\"/clerkAndroidUiVersion = \"${{ steps.android_versions.outputs.ui }}\"/" packages/expo/android/build.gradle
# Expo autolinks via the root project's repositories; adding mavenLocal()
# to the expo module's build.gradle is sufficient for gradle resolution.
if ! grep -q "mavenLocal()" packages/expo/android/build.gradle; then
sed -i '/^repositories\s*{/a \ mavenLocal()' packages/expo/android/build.gradle || true
fi
echo "=== Patched @clerk/expo build.gradle ==="
grep -A 2 -B 2 "clerkAndroid\|mavenLocal" packages/expo/android/build.gradle | head -20
- name: Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Run Android e2e
uses: reactivecircus/android-emulator-runner@v2
env:
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
# inputs.exclude_tags is empty on pull_request triggers; fall back to the same default
EXCLUDE_TAGS: ${{ inputs.exclude_tags || 'manual,skip' }}
with:
api-level: 34
target: google_apis
arch: x86_64
script: |
cd clerk-expo-quickstart/NativeComponentQuickstart
npx expo prebuild --clean
npx expo run:android --variant release --no-bundler
cd ../../integration-mobile
# Maestro doesn't auto-recurse into subdirectories; pass each flow explicitly.
find flows -type f -name "*.yaml" ! -path "*/common/*" -print0 | \
xargs -0 maestro test --exclude-tags "$EXCLUDE_TAGS"
- name: Upload Maestro artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-android
path: ~/.maestro/tests
- name: Cleanup test user
if: always() && steps.user.outputs.user_id != ''
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
USER_ID: ${{ steps.user.outputs.user_id }}
run: |
curl -fsS -X DELETE "https://api.clerk.com/v1/users/$USER_ID" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" || true
ios:
name: iOS
# iOS isn't affected by the Android pin bump path filter; only run on manual dispatch.
if: github.event_name == 'workflow_dispatch'
runs-on: macos-15
timeout-minutes: 60
steps:
- name: Checkout @clerk/javascript
uses: actions/checkout@v4
- name: Checkout clerk-expo-quickstart
uses: actions/checkout@v4
with:
repository: clerk/clerk-expo-quickstart
ref: ${{ inputs.quickstart_ref }}
path: clerk-expo-quickstart
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install monorepo deps
run: pnpm install --frozen-lockfile
- name: Build @clerk/expo
run: pnpm turbo build --filter=@clerk/expo...
- name: Install quickstart deps
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
run: pnpm install
- name: Resolve Clerk instance keys
id: keys
env:
INTEGRATION_STAGING_INSTANCE_KEYS: ${{ secrets.INTEGRATION_STAGING_INSTANCE_KEYS }}
run: node scripts/resolve-instance-keys.mjs INTEGRATION_STAGING_INSTANCE_KEYS "$EXPO_INSTANCE_NAME"
- name: Write quickstart .env
working-directory: clerk-expo-quickstart/NativeComponentQuickstart
run: |
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
- name: Provision test user via BAPI
id: user
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
run: |
email="ci-${GITHUB_RUN_ID}-${RANDOM}+clerk_test@clerkcookie.com"
password="ClerkCI!$(openssl rand -hex 8)Aa1"
response=$(curl -fsS -X POST https://api.clerk.com/v1/users \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d "{\"email_address\":[\"$email\"],\"password\":\"$password\"}")
user_id=$(echo "$response" | jq -er '.id')
echo "::add-mask::$password"
echo "email=$email" >> "$GITHUB_OUTPUT"
echo "password=$password" >> "$GITHUB_OUTPUT"
echo "user_id=$user_id" >> "$GITHUB_OUTPUT"
- name: Cache SPM
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData
key: spm-${{ hashFiles('packages/expo/package.json') }}
- name: Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Build and run iOS e2e
env:
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
EXCLUDE_TAGS: ${{ inputs.exclude_tags }}
run: |
cd clerk-expo-quickstart/NativeComponentQuickstart
npx expo prebuild --clean
npx expo run:ios --configuration Release --no-bundler
cd ../../integration-mobile
# Maestro doesn't auto-recurse into subdirectories; pass each flow explicitly.
find flows -type f -name "*.yaml" ! -path "*/common/*" -print0 | \
xargs -0 maestro test --exclude-tags "$EXCLUDE_TAGS,androidOnly"
- name: Upload Maestro artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: maestro-ios
path: ~/.maestro/tests
- name: Cleanup test user
if: always() && steps.user.outputs.user_id != ''
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
USER_ID: ${{ steps.user.outputs.user_id }}
run: |
curl -fsS -X DELETE "https://api.clerk.com/v1/users/$USER_ID" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" || true