diff --git a/.github/workflows/mobile-e2e.yml b/.github/workflows/mobile-e2e.yml new file mode 100644 index 00000000000..29ae2e74c9c --- /dev/null +++ b/.github/workflows/mobile-e2e.yml @@ -0,0 +1,269 @@ +# 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_INSTANCE_KEYS — JSON map of named test instances +# ({ "": { "pk": "pk_test_...", "sk": "sk_test_..." } }). +# Same secret used by /integration (Playwright). 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. +# +# TODO(SDK team): confirm the instance-name slot to add inside +# INTEGRATION_INSTANCE_KEYS for this workflow (placeholder: "expo-native"). +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" + +env: + # TODO(SDK team): replace with the canonical mobile-e2e instance name once confirmed. + EXPO_INSTANCE_NAME: expo-native + +concurrency: + group: mobile-e2e-${{ github.ref }} + cancel-in-progress: true + +jobs: + android: + name: Android + 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 }} + 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_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} + run: | + if [ -z "$INTEGRATION_INSTANCE_KEYS" ]; then + echo "::error::INTEGRATION_INSTANCE_KEYS secret is not set" + exit 1 + fi + pk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].pk") || { + echo "::error::No entry '$EXPO_INSTANCE_NAME' found in INTEGRATION_INSTANCE_KEYS" + exit 1 + } + sk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].sk") + echo "::add-mask::$sk" + echo "pk=$pk" >> "$GITHUB_OUTPUT" + echo "sk=$sk" >> "$GITHUB_OUTPUT" + + - 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 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - 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 }} + 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" + + - 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 + 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_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} + run: | + if [ -z "$INTEGRATION_INSTANCE_KEYS" ]; then + echo "::error::INTEGRATION_INSTANCE_KEYS secret is not set" + exit 1 + fi + pk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].pk") || { + echo "::error::No entry '$EXPO_INSTANCE_NAME' found in INTEGRATION_INSTANCE_KEYS" + exit 1 + } + sk=$(echo "$INTEGRATION_INSTANCE_KEYS" | jq -er ".[\"$EXPO_INSTANCE_NAME\"].sk") + echo "::add-mask::$sk" + echo "pk=$pk" >> "$GITHUB_OUTPUT" + echo "sk=$sk" >> "$GITHUB_OUTPUT" + + - 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