Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/wicked-regions-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
246 changes: 84 additions & 162 deletions .github/workflows/mobile-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,101 +1,80 @@
# 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)"
name: 'Mobile e2e smoke (@clerk/expo)'

on:
workflow_dispatch:
inputs:
quickstart_ref:
description: "clerk-expo-quickstart git ref (branch, tag, or SHA)"
platform:
description: 'Platform to run'
required: false
default: "main"
exclude_tags:
description: "Maestro tags to exclude (comma-separated)"
default: 'android'
type: choice
options:
- android
- ios
- all
expo_sdk:
description: 'Expo SDK fixture version'
required: false
default: "manual,skip"
default: '56'
type: choice
options:
- '54'
- '55'
- '56'

env:
EXPO_INSTANCE_NAME: clerkstage-with-native-components
permissions:
contents: read

concurrency:
group: mobile-e2e-${{ github.ref }}
group: mobile-e2e-smoke-${{ github.ref }}-${{ inputs.platform }}-${{ inputs.expo_sdk }}
cancel-in-progress: true

env:
FIXTURE_DIR: integration/templates/expo-native
FIXTURE_PUBLISHABLE_KEY: pk_test_ZHVtbXkuY2xlcmsuYWNjb3VudHMuZGV2JA
SDK_PACK_DIR: /tmp/clerk-expo-pack

jobs:
android:
name: Android
runs-on: 'blacksmith-8vcpu-ubuntu-2204'
if: ${{ inputs.platform == 'android' || inputs.platform == 'all' }}
name: Android / Expo SDK ${{ inputs.expo_sdk }}
runs-on: blacksmith-8vcpu-ubuntu-2204
timeout-minutes: 45
defaults:
run:
working-directory: .
steps:
- name: Checkout @clerk/javascript
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Checkout clerk-expo-quickstart
- name: Checkout repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
repository: clerk/clerk-expo-quickstart
ref: ${{ inputs.quickstart_ref }}
path: clerk-expo-quickstart

- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
node-version: 24.15.0
cache: pnpm

- name: Install monorepo deps
- name: Install monorepo dependencies
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
- name: Build and pack @clerk/expo
run: |
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
pnpm --filter @clerk/expo... build
mkdir -p "$SDK_PACK_DIR"
pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR"

- name: Provision test user via BAPI
id: user
- name: Install fixture dependencies
working-directory: ${{ env.FIXTURE_DIR }}
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
EXPO_SDK: ${{ inputs.expo_sdk }}
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"
cp "package.sdk-$EXPO_SDK.json" package.json
pnpm install --no-frozen-lockfile
SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-*.tgz)"
pnpm add "$SDK_TARBALL" -w
pnpm expo install expo-auth-session expo-constants expo-crypto expo-dev-client expo-secure-store expo-web-browser

- name: Write fixture .env
working-directory: ${{ env.FIXTURE_DIR }}
run: echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=$FIXTURE_PUBLISHABLE_KEY" > .env

- name: Set up JDK 17
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
Expand All @@ -107,141 +86,84 @@ jobs:
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
"$HOME/.maestro/bin/maestro" --version

- name: Run Android e2e
- name: Run Android smoke test
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2
env:
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
EXCLUDE_TAGS: ${{ inputs.exclude_tags }}
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"
cd "$FIXTURE_DIR"
pnpm expo prebuild --clean --platform android
pnpm expo run:android --variant release --no-bundler
maestro test --test-output-dir artifacts/maestro .maestro/smoke.yaml

- name: Upload Maestro artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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
name: maestro-android-sdk-${{ inputs.expo_sdk }}
path: ${{ env.FIXTURE_DIR }}/artifacts/maestro

ios:
name: iOS
if: ${{ inputs.platform == 'ios' || inputs.platform == 'all' }}
name: iOS / Expo SDK ${{ inputs.expo_sdk }}
runs-on: macos-15
timeout-minutes: 60
steps:
- name: Checkout @clerk/javascript
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Checkout clerk-expo-quickstart
- name: Checkout repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
repository: clerk/clerk-expo-quickstart
ref: ${{ inputs.quickstart_ref }}
path: clerk-expo-quickstart

- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
node-version: 24.15.0
cache: pnpm

- name: Install monorepo deps
- name: Install monorepo dependencies
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
- name: Build and pack @clerk/expo
run: |
echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=${{ steps.keys.outputs.pk }}" > .env
pnpm --filter @clerk/expo... build
mkdir -p "$SDK_PACK_DIR"
pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR"

- name: Provision test user via BAPI
id: user
- name: Install fixture dependencies
working-directory: ${{ env.FIXTURE_DIR }}
env:
CLERK_SECRET_KEY: ${{ steps.keys.outputs.sk }}
EXPO_SDK: ${{ inputs.expo_sdk }}
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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/Library/Developer/Xcode/DerivedData
key: spm-${{ hashFiles('packages/expo/package.json') }}
cp "package.sdk-$EXPO_SDK.json" package.json
pnpm install --no-frozen-lockfile
SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-*.tgz)"
pnpm add "$SDK_TARBALL" -w
pnpm expo install expo-auth-session expo-constants expo-crypto expo-dev-client expo-secure-store expo-web-browser

- name: Write fixture .env
working-directory: ${{ env.FIXTURE_DIR }}
run: echo "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=$FIXTURE_PUBLISHABLE_KEY" > .env

- name: Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
"$HOME/.maestro/bin/maestro" --version

- 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 }}
- name: Build and run iOS smoke test
working-directory: ${{ env.FIXTURE_DIR }}
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"
pnpm expo prebuild --clean --platform ios
pnpm expo run:ios --configuration Release --no-bundler
maestro test --test-output-dir artifacts/maestro .maestro/smoke.yaml

- name: Upload Maestro artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 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
name: maestro-ios-sdk-${{ inputs.expo_sdk }}
path: ${{ env.FIXTURE_DIR }}/artifacts/maestro
7 changes: 7 additions & 0 deletions integration/templates/expo-native/.maestro/smoke.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
appId: com.clerk.exponativebuildfixture
---
- launchApp
- assertVisible: 'Clerk Expo Native Fixture'
- assertVisible: 'Open native AuthView'
- tapOn: 'Open native AuthView'
- assertVisible: 'Native AuthView mounted'
8 changes: 7 additions & 1 deletion integration/templates/expo-native/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ function NativeBuildFixture() {
presentationStyle='pageSheet'
onRequestClose={() => setIsAuthOpen(false)}
>
<AuthView onDismiss={() => setIsAuthOpen(false)} />
<View style={styles.modalContent}>
<Text testID='auth-view-mounted'>Native AuthView mounted</Text>
<AuthView onDismiss={() => setIsAuthOpen(false)} />
</View>
</Modal>
</View>
);
Expand Down Expand Up @@ -70,4 +73,7 @@ const styles = StyleSheet.create({
fontSize: 20,
fontWeight: '600',
},
modalContent: {
flex: 1,
},
});
Loading