From 87b7e3b9b156cc5840363c4de7d8a5851e854c5f Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 08:22:42 -0700 Subject: [PATCH 01/12] testing --- .changeset/expo-native-debug-logging.md | 8 ++++++++ .github/workflows/expo-native-build.yml | 3 +++ .../java/expo/modules/clerk/ClerkAuthViewModule.kt | 2 +- .../main/java/expo/modules/clerk/ClerkExpoDebug.kt | 10 ++++++++++ .../main/java/expo/modules/clerk/ClerkExpoModule.kt | 4 ++-- .../modules/clerk/ClerkUserProfileViewModule.kt | 2 +- packages/expo/src/provider/nativeClientSync.tsx | 13 +++++++++++-- 7 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 .changeset/expo-native-debug-logging.md create mode 100644 packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt diff --git a/.changeset/expo-native-debug-logging.md b/.changeset/expo-native-debug-logging.md new file mode 100644 index 00000000000..b8a09dd2b8c --- /dev/null +++ b/.changeset/expo-native-debug-logging.md @@ -0,0 +1,8 @@ +--- +'@clerk/expo': patch +--- + +Improve diagnosability of native module initialization on Android. + +- A failed native Clerk configuration is now logged as a warning in release builds instead of being silently swallowed. When this happens the JS SDK keeps working but the native components (`AuthView`, `UserProfileView`, `UserButton`) cannot render. +- Running `adb shell setprop log.tag.ClerkExpo DEBUG` now enables the Expo module's debug logs and clerk-android SDK debug logging at runtime, including in release builds. diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 118c3e8613c..6baa5880704 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -365,7 +365,10 @@ jobs: disable-animations: true # The action runs each script line in a separate sh -c; the folded # scalar (>-) plus && keeps everything in one shell invocation. + # The setprop turns on @clerk/expo + clerk-android debug logging in the + # release build so the logcat artifact shows native SDK activity. script: >- + adb shell setprop log.tag.ClerkExpo DEBUG && cd integration/tests/expo-native && MAESTRO_DEBUG_OUTPUT="$RUNNER_TEMP/maestro-debug" ./run-android-flows.sh "$GITHUB_WORKSPACE/$FIXTURE_DIR/android/app/build/outputs/apk/release/app-release.apk" diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt index 40d39f1119a..e75920e3735 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt @@ -31,7 +31,7 @@ import expo.modules.kotlin.viewevent.EventDispatcher private const val TAG = "ClerkAuthViewModule" private fun debugLog(tag: String, message: String) { - if (BuildConfig.DEBUG) { + if (clerkExpoDebugEnabled()) { Log.d(tag, message) } } diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt new file mode 100644 index 00000000000..73e0407415a --- /dev/null +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt @@ -0,0 +1,10 @@ +package expo.modules.clerk + +import android.util.Log + +// Release builds are silent by default; `adb shell setprop log.tag.ClerkExpo DEBUG` +// enables module debug logs and clerk-android SDK debug logging without a rebuild. +internal const val CLERK_EXPO_DEBUG_TAG = "ClerkExpo" + +internal fun clerkExpoDebugEnabled(): Boolean = + BuildConfig.DEBUG || Log.isLoggable(CLERK_EXPO_DEBUG_TAG, Log.DEBUG) diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt index c5718ade6ef..f9173716358 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt @@ -31,7 +31,7 @@ private const val HOST_SDK_VERSION_HEADER = "x-clerk-host-sdk-version" private const val HOST_SDK = "expo" private fun debugLog(tag: String, message: String) { - if (BuildConfig.DEBUG) { + if (clerkExpoDebugEnabled()) { Log.d(tag, message) } } @@ -121,7 +121,7 @@ class ClerkExpoModule : Module() { } } - return ClerkConfigurationOptions().withCustomHeaders(customHeaders) + return ClerkConfigurationOptions(enableDebugMode = clerkExpoDebugEnabled()).withCustomHeaders(customHeaders) } private fun startClientStateObserver() { diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt index 51826181aff..b65aee0b88d 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt @@ -19,7 +19,7 @@ import expo.modules.kotlin.viewevent.EventDispatcher private const val TAG = "ClerkUserProfileViewModule" private fun debugLog(tag: String, message: String) { - if (BuildConfig.DEBUG) { + if (clerkExpoDebugEnabled()) { Log.d(tag, message) } } diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 5c564d92f32..0b7a5cfff42 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -1022,8 +1022,17 @@ export function useNativeClientBootstrap({ `To enable native features, add "@clerk/expo" to your app.json plugins array.`, ); } - } else if (__DEV__) { - console.error(`[ClerkProvider] Failed to configure Clerk ${Platform.OS}:`, error); + } else { + // Also logged in release builds: a failed native configure silently + // disables the native components, which is undiagnosable from device + // logs otherwise. + const message = error instanceof Error ? error.message : String(error); + console.warn( + `[ClerkProvider] Failed to configure native Clerk (${Platform.OS}); native components will not work: ${message}`, + ); + if (__DEV__) { + console.error(`[ClerkProvider] Failed to configure Clerk ${Platform.OS}:`, error); + } } } finally { if (didAttemptConfigure && isCurrentConfiguration()) { From c0f648cd4fe6b215f2032cff2b6b8c3869539a62 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 09:10:08 -0700 Subject: [PATCH 02/12] fix(e2e): align OkHttp versions in the Android fixture and branch the embedded flow per platform --- integration/templates/expo-native/app.json | 2 +- .../plugins/withClerkOkHttpAlignment.js | 26 +++++++++ .../flows/embedded-profile-host-back.yaml | 53 +++++++++++++------ 3 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js diff --git a/integration/templates/expo-native/app.json b/integration/templates/expo-native/app.json index 58e258ea523..9c378558907 100644 --- a/integration/templates/expo-native/app.json +++ b/integration/templates/expo-native/app.json @@ -12,6 +12,6 @@ "android": { "package": "com.clerk.exponativebuildfixture" }, - "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser"] + "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser", "./plugins/withClerkOkHttpAlignment.js"] } } diff --git a/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js b/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js new file mode 100644 index 00000000000..52337e8c960 --- /dev/null +++ b/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js @@ -0,0 +1,26 @@ +const { withAppBuildGradle } = require('expo/config-plugins'); + +// clerk-android is compiled against OkHttp 5 but React Native pins OkHttp 4, +// leaving a mixed classpath: clerk networking breaks on the 4.x core +// (NoClassDefFoundError: okhttp3.internal.UnreadableResponseBody), while RN's +// cookie jar breaks on the 5.x core (okhttp3.internal.Util). Force the 5.x +// core plus the urlconnection shim that still ships a JavaNetCookieJar class +// compatible with it, so both stacks run on one coherent OkHttp. +const GRADLE_BLOCK = ` +configurations.all { + resolutionStrategy { + force 'com.squareup.okhttp3:okhttp:5.4.0' + force 'com.squareup.okhttp3:logging-interceptor:5.4.0' + force 'com.squareup.okhttp3:okhttp-urlconnection:5.0.0-alpha.16' + } +} +`; + +module.exports = function withClerkOkHttpAlignment(config) { + return withAppBuildGradle(config, cfg => { + if (!cfg.modResults.contents.includes('com.squareup.okhttp3:okhttp:5.4.0')) { + cfg.modResults.contents += GRADLE_BLOCK; + } + return cfg; + }); +}; diff --git a/integration/tests/expo-native/flows/embedded-profile-host-back.yaml b/integration/tests/expo-native/flows/embedded-profile-host-back.yaml index 19d495bcbd3..360a99e8ed7 100644 --- a/integration/tests/expo-native/flows/embedded-profile-host-back.yaml +++ b/integration/tests/expo-native/flows/embedded-profile-host-back.yaml @@ -10,21 +10,44 @@ name: Embedded UserProfileView host back round trip - runFlow: subflows/assert-signed-in.yaml - tapOn: id: 'open-embedded-profile-button' -- extendedWaitUntil: - visible: 'Security' - timeout: 20000 -- tapOn: - text: 'Security' -- extendedWaitUntil: - visible: 'Password|Passkeys|Two-step verification|Active devices' - timeout: 15000 -# Both back buttons read 'Back' (internal id 'BackButton', host root id -# 'chevron.backward') but only one exists at a time: this tap pops Clerk's -# internal stack, the next one is the host chevron firing onHostBack. -- tapOn: 'Back' -- extendedWaitUntil: - visible: 'Security' - timeout: 15000 +# The internal push differs per platform: clerk-ios pushes a Security screen +# from the profile root, clerk-android uses tabs at the root and pushes the +# Manage account screen instead. The double-Back contract below is the same: +# both back buttons read 'Back' but only one exists at a time, so the first +# tap pops Clerk's internal stack and the second is the host chevron firing +# onHostBack. +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: 'Security' + timeout: 20000 + - tapOn: + text: 'Security' + - extendedWaitUntil: + visible: 'Password|Passkeys|Two-step verification|Active devices' + timeout: 15000 + - tapOn: 'Back' + - extendedWaitUntil: + visible: 'Security' + timeout: 15000 +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: 'Edit profile' + timeout: 20000 + - tapOn: + text: 'Manage account' + - extendedWaitUntil: + visible: 'EMAIL ADDRESSES|Add email address' + timeout: 15000 + - tapOn: 'Back' + - extendedWaitUntil: + visible: 'Edit profile' + timeout: 15000 - tapOn: 'Back' - extendedWaitUntil: visible: From df3b7be8e9644d3de94fe0747aa28a39ef17dc08 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 09:40:20 -0700 Subject: [PATCH 03/12] fix(expo): hold Android native views until the Clerk SDK is initialized --- .changeset/expo-native-debug-logging.md | 3 ++- .../expo/modules/clerk/ClerkAuthViewModule.kt | 16 ++++++++++++++++ .../clerk/ClerkUserProfileViewModule.kt | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.changeset/expo-native-debug-logging.md b/.changeset/expo-native-debug-logging.md index b8a09dd2b8c..c2aa789de99 100644 --- a/.changeset/expo-native-debug-logging.md +++ b/.changeset/expo-native-debug-logging.md @@ -2,7 +2,8 @@ '@clerk/expo': patch --- -Improve diagnosability of native module initialization on Android. +Fix Android native components rendering an empty screen when opened before the native SDK finishes initializing, and improve diagnosability of native module initialization on Android. +- `AuthView` and `UserProfileView` on Android now show a loading indicator until the native Clerk SDK has loaded its configuration, instead of permanently rendering an incomplete screen when opened too early (for example on a slow network). - A failed native Clerk configuration is now logged as a warning in release builds instead of being silently swallowed. When this happens the JS SDK keeps working but the native components (`AuthView`, `UserProfileView`, `UserButton`) cannot render. - Running `adb shell setprop log.tag.ClerkExpo DEBUG` now enables the Expo module's debug logs and clerk-android SDK debug logging at runtime, including in release builds. diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt index e75920e3735..f48c00237f9 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt @@ -6,10 +6,15 @@ import android.content.Context import android.util.Log import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -91,6 +96,17 @@ class ClerkAuthNativeView(context: Context, appContext: AppContext) : ClerkCompo override fun Content() { debugLog(TAG, "setupView - mode: $mode, isDismissible: $isDismissible, hostBackButton: $hostBackButton, activity: $activity") + // clerk-android views compose from a snapshot of the environment and do not + // recompose when it loads afterwards, so a view composed too early stays + // empty forever; hold rendering until the SDK is initialized. + val isInitialized by Clerk.isInitialized.collectAsState() + if (!isInitialized) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + return + } + if (hostBackButton) { ClerkHostBackActionProvider(onHostBack = { onHostBack(mapOf()) }) { AuthContent() } } else { diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt index b65aee0b88d..6b2511cb075 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt @@ -4,7 +4,14 @@ package expo.modules.clerk import android.content.Context import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import com.clerk.api.Clerk @@ -46,6 +53,17 @@ class ClerkUserProfileNativeView(context: Context, appContext: AppContext) : Cle override fun Content() { debugLog(TAG, "setupView - isDismissible: $isDismissible, hostBackButton: $hostBackButton") + // clerk-android views compose from a snapshot of the environment and do not + // recompose when it loads afterwards, so a view composed too early stays + // empty forever; hold rendering until the SDK is initialized. + val isInitialized by Clerk.isInitialized.collectAsState() + if (!isInitialized) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + return + } + if (hostBackButton) { ClerkHostBackActionProvider(onHostBack = { onHostBack(mapOf()) }) { ProfileView() } } else { From abfa03ab3c388d53899261ef4893c70303cd28b8 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 10:04:12 -0700 Subject: [PATCH 04/12] fix(e2e): drop the devicelab maestro driver on Android to stop text-input corruption --- .github/workflows/expo-native-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 6baa5880704..955e1667bf7 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -354,7 +354,9 @@ jobs: env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }} - MAESTRO_DRIVER: devicelab + # The devicelab driver's text input mangles special characters + # (dropped underscores, stray trailing characters), which corrupts + # the test email/password; the default driver types reliably. MAESTRO_PLATFORM: android with: api-level: 34 From 129ea148a4594cef7604819984cabac551a63efe Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 10:49:11 -0700 Subject: [PATCH 05/12] fix(e2e): run Android flows through the maestro CLI and harden the sign-in subflow --- .github/workflows/expo-native-build.yml | 35 +++++++++++-- .../subflows/sign-in-email-password.yaml | 49 ++++++++++++++----- integration/tests/expo-native/run-flows.sh | 42 ++++++++++++---- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 955e1667bf7..02f8fde69ca 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -28,6 +28,7 @@ env: E2E_INSTANCE_NAME: clerkstage-with-native-components BAPI_URL: https://api.clerkstage.dev MAESTRO_RUNNER_VERSION: '1.1.21' + MAESTRO_VERSION: '2.6.1' jobs: native-build: @@ -242,6 +243,35 @@ jobs: echo "$HOME/.maestro-runner/bin" >> "$GITHUB_PATH" "$HOME/.maestro-runner/bin/maestro-runner" --version + # Android flows run through the official maestro CLI (MAESTRO_ENGINE=cli): + # the runner's Android driver mis-targets text taps (see run-flows.sh). + - name: Cache maestro CLI + if: matrix.platform == 'android' && steps.keys.outputs.pk != '' + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.maestro + key: maestro-${{ runner.os }}-${{ env.MAESTRO_VERSION }} + + - name: Install maestro CLI + if: matrix.platform == 'android' && steps.keys.outputs.pk != '' + run: | + set -o pipefail + if [ -x "$HOME/.maestro/bin/maestro" ]; then + echo "Using cached Maestro" + else + installed=0 + for i in 1 2 3; do + if curl -fLs --retry 3 --retry-delay 5 "https://get.maestro.mobile.dev" | MAESTRO_VERSION="$MAESTRO_VERSION" bash; then + if [ -x "$HOME/.maestro/bin/maestro" ]; then installed=1; break; fi + fi + echo "Maestro install attempt $i failed (or binary missing); retrying" + sleep 5 + done + [ "$installed" = 1 ] || { echo "::error::Maestro install failed after 3 attempts"; exit 1; } + fi + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + "$HOME/.maestro/bin/maestro" --version + - name: Boot iOS simulator id: sim if: matrix.platform == 'ios' && steps.keys.outputs.pk != '' @@ -354,10 +384,7 @@ jobs: env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }} - # The devicelab driver's text input mangles special characters - # (dropped underscores, stray trailing characters), which corrupts - # the test email/password; the default driver types reliably. - MAESTRO_PLATFORM: android + MAESTRO_ENGINE: cli with: api-level: 34 target: google_apis diff --git a/integration/tests/expo-native/flows/subflows/sign-in-email-password.yaml b/integration/tests/expo-native/flows/subflows/sign-in-email-password.yaml index 469d946b65f..69df6b2991c 100644 --- a/integration/tests/expo-native/flows/subflows/sign-in-email-password.yaml +++ b/integration/tests/expo-native/flows/subflows/sign-in-email-password.yaml @@ -12,10 +12,12 @@ appId: com.clerk.exponativebuildfixture visible: 'Enter your email( or username)?' timeout: 25000 # Clear any pre-populated identifier (Clerk persists the last-used one in -# secure-store, surviving clearState) via Select-all when the edit menu -# shows, falling back to eraseText when it doesn't (the menu is unreliable -# on a remounted AuthView on iOS). The retry gates on reaching the password -# step, so a mangled identifier gets a clean second attempt. +# secure-store, surviving clearState). On iOS eraseText is cursor-dependent, +# so Select-all via the edit menu when it shows (the menu is unreliable on a +# remounted AuthView). On Android eraseText clears the whole field, and the +# long-press can open Gboard's features panel over the screen instead of the +# edit menu, so skip the menu dance there. The retry gates on reaching the +# password step, so a mangled identifier gets a clean second attempt. - retry: maxRetries: 2 commands: @@ -23,17 +25,36 @@ appId: com.clerk.exponativebuildfixture text: 'Enter your email( or username)?' - waitForAnimationToEnd: timeout: 2000 - - longPressOn: - text: 'Enter your email( or username)?' - # iOS shows "Select All", Android shows "Select all". + # A remounted AuthView prefills the last-used identifier, which is the + # same email this flow signs in with, and clearing a prefilled field is + # cursor-position dependent on Android. Only clear and retype when the + # field does not already hold the right value. - runFlow: when: - visible: 'Select [Aa]ll' + notVisible: ${CLERK_TEST_EMAIL} commands: - - tapOn: - text: 'Select [Aa]ll' - - eraseText: 100 - - inputText: ${CLERK_TEST_EMAIL} + - runFlow: + when: + platform: iOS + commands: + - longPressOn: + text: 'Enter your email( or username)?' + - runFlow: + when: + visible: 'Select All' + commands: + - tapOn: + text: 'Select All' + - eraseText: 100 + - inputText: ${CLERK_TEST_EMAIL} + # Wait for the typed value to land before tapping: the tap can race the + # recomposition that enables the button, and this also catches a mangled + # identifier inside the retry instead of at the next screen's timeout. + - extendedWaitUntil: + visible: ${CLERK_TEST_EMAIL} + timeout: 5000 + - waitForAnimationToEnd: + timeout: 2000 - tapOn: text: 'Continue' index: 0 @@ -60,6 +81,10 @@ appId: com.clerk.exponativebuildfixture text: 'Enter your password' - eraseText: 50 - inputText: ${CLERK_TEST_PASSWORD} + # The password is masked so its value can't be asserted; settle for the + # screen going stable so the tap doesn't race the enabling recomposition. + - waitForAnimationToEnd: + timeout: 2000 - tapOn: text: 'Continue' index: 0 diff --git a/integration/tests/expo-native/run-flows.sh b/integration/tests/expo-native/run-flows.sh index e5a83f4bd4a..1559514f32a 100755 --- a/integration/tests/expo-native/run-flows.sh +++ b/integration/tests/expo-native/run-flows.sh @@ -15,10 +15,22 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" -command -v maestro-runner >/dev/null 2>&1 || { - echo "maestro-runner is required: https://devicelab.dev/open-source/maestro-runner/docs/getting-started" - exit 1 -} +# MAESTRO_ENGINE=cli runs flows with the official maestro CLI instead of +# maestro-runner. The runner's Android driver approximates Maestro's anchored +# regex text matching with UiSelector textContains, which mis-targets taps +# (e.g. 'Continue' resolves to the 'Continue to ' title instead of the +# button), so Android CI uses the CLI; iOS stays on the runner's wda driver. +if [ "${MAESTRO_ENGINE:-runner}" = "cli" ]; then + command -v maestro >/dev/null 2>&1 || { + echo "maestro is required: https://docs.maestro.dev" + exit 1 + } +else + command -v maestro-runner >/dev/null 2>&1 || { + echo "maestro-runner is required: https://devicelab.dev/open-source/maestro-runner/docs/getting-started" + exit 1 + } +fi : "${CLERK_TEST_EMAIL:?CLERK_TEST_EMAIL is required}" : "${CLERK_TEST_PASSWORD:?CLERK_TEST_PASSWORD is required}" @@ -30,11 +42,23 @@ run_flow() { shift local output_root=${MAESTRO_DEBUG_OUTPUT:-${TMPDIR:-/tmp}/clerk-expo-maestro-runner} - maestro-runner test \ - --output "$output_root/$output_name" \ - --flatten \ - --artifacts on-failure \ - "$@" + if [ "${MAESTRO_ENGINE:-runner}" = "cli" ]; then + # The CLI's env flag is -e; translate the shared --env arguments. + local args=() a + for a in "$@"; do + if [ "$a" = "--env" ]; then args+=("-e"); else args+=("$a"); fi + done + maestro test \ + --debug-output "$output_root/$output_name" \ + --flatten-debug-output \ + "${args[@]}" + else + maestro-runner test \ + --output "$output_root/$output_name" \ + --flatten \ + --artifacts on-failure \ + "$@" + fi } record_result() { From 1a6d98ac9be6f0f7fa6ae8457e61fce3d4c17c71 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 11:02:04 -0700 Subject: [PATCH 06/12] ci(e2e): make maestro e2e failures fail the expo native jobs --- .github/workflows/expo-native-build.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 02f8fde69ca..1a39e86fc33 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -311,9 +311,6 @@ jobs: - name: Run iOS e2e (Maestro) id: run_e2e_ios if: matrix.platform == 'ios' && steps.user.outputs.user_id != '' - # Burn-in: e2e cannot fail the check yet. Flip to hard-fail in a - # follow-up once the suite has proven quiet. - continue-on-error: true working-directory: ${{ env.FIXTURE_DIR }} env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} @@ -379,7 +376,6 @@ jobs: - name: Run Android e2e (Maestro) id: run_e2e_android if: matrix.platform == 'android' && steps.user.outputs.user_id != '' - continue-on-error: true uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} @@ -445,5 +441,5 @@ jobs: [ "$outcome" = "skipped" ] && outcome="$ANDROID_OUTCOME" echo "## Maestro e2e (${{ matrix.platform }}): $outcome" >> "$GITHUB_STEP_SUMMARY" if [ "$outcome" = "failure" ]; then - echo "::warning::Maestro e2e failed (burn-in mode: not failing the job). See the maestro-${{ matrix.platform }} artifact." + echo "::error::Maestro e2e failed. See the maestro-${{ matrix.platform }} artifact." fi From 3805ae1ac09f440674f7b16f4907f38df1359733 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 11:31:50 -0700 Subject: [PATCH 07/12] ci(e2e): revert both platforms to the official maestro CLI --- .github/workflows/expo-native-build.yml | 43 ++----------------- integration/tests/expo-native/run-flows.sh | 50 +++++++--------------- 2 files changed, 19 insertions(+), 74 deletions(-) diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 1a39e86fc33..a0d1349f6f5 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -27,7 +27,6 @@ env: SDK_PACK_DIR: /tmp/clerk-expo-pack E2E_INSTANCE_NAME: clerkstage-with-native-components BAPI_URL: https://api.clerkstage.dev - MAESTRO_RUNNER_VERSION: '1.1.21' MAESTRO_VERSION: '2.6.1' jobs: @@ -215,45 +214,15 @@ jobs: path: ${{ steps.native-build-key.outputs.artifact }} key: ${{ steps.native-build-cache.outputs.cache-primary-key }} - - name: Cache maestro-runner - if: steps.keys.outputs.pk != '' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.maestro-runner - key: maestro-runner-${{ runner.os }}-${{ runner.arch }}-${{ env.MAESTRO_RUNNER_VERSION }} - - - name: Install maestro-runner - if: steps.keys.outputs.pk != '' - run: | - set -o pipefail - if [ -x "$HOME/.maestro-runner/bin/maestro-runner" ]; then - echo "Using cached maestro-runner" - else - installed=0 - for i in 1 2 3; do - if curl -fsSL --retry 3 --retry-delay 5 "https://open.devicelab.dev/install/maestro-runner" | - bash -s -- --version "$MAESTRO_RUNNER_VERSION"; then - if [ -x "$HOME/.maestro-runner/bin/maestro-runner" ]; then installed=1; break; fi - fi - echo "maestro-runner install attempt $i failed (or binary missing); retrying" - sleep 5 - done - [ "$installed" = 1 ] || { echo "::error::maestro-runner install failed after 3 attempts"; exit 1; } - fi - echo "$HOME/.maestro-runner/bin" >> "$GITHUB_PATH" - "$HOME/.maestro-runner/bin/maestro-runner" --version - - # Android flows run through the official maestro CLI (MAESTRO_ENGINE=cli): - # the runner's Android driver mis-targets text taps (see run-flows.sh). - name: Cache maestro CLI - if: matrix.platform == 'android' && steps.keys.outputs.pk != '' + if: steps.keys.outputs.pk != '' uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.maestro key: maestro-${{ runner.os }}-${{ env.MAESTRO_VERSION }} - name: Install maestro CLI - if: matrix.platform == 'android' && steps.keys.outputs.pk != '' + if: steps.keys.outputs.pk != '' run: | set -o pipefail if [ -x "$HOME/.maestro/bin/maestro" ]; then @@ -315,9 +284,6 @@ jobs: env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }} - MAESTRO_DEVICE: ${{ steps.sim.outputs.udid }} - MAESTRO_DRIVER: wda - MAESTRO_PLATFORM: ios SIM_UDID: ${{ steps.sim.outputs.udid }} run: | echo "Using simulator $SIM_UDID" @@ -380,7 +346,6 @@ jobs: env: CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }} CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }} - MAESTRO_ENGINE: cli with: api-level: 34 target: google_apis @@ -411,7 +376,7 @@ jobs: # Test reports record flow env (and typed input) in plaintext; # add-mask only covers step logs, not artifact contents. - - name: Scrub test credentials from maestro-runner reports + - name: Scrub test credentials from Maestro debug output if: always() && (steps.run_e2e_ios.outcome == 'failure' || steps.run_e2e_android.outcome == 'failure') env: CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }} @@ -423,7 +388,7 @@ jobs: \( -name '*.html' -o -name '*.json' -o -name '*.log' -o -name '*.txt' -o -name '*.xml' -o -name '*.yaml' \) \ -exec perl -pi -e 's/\Q$ENV{CLERK_TEST_PASSWORD}\E/[REDACTED]/g' {} + - - name: Upload maestro-runner artifacts on e2e failure + - name: Upload Maestro artifacts on e2e failure if: always() && (steps.run_e2e_ios.outcome == 'failure' || steps.run_e2e_android.outcome == 'failure') uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: diff --git a/integration/tests/expo-native/run-flows.sh b/integration/tests/expo-native/run-flows.sh index 1559514f32a..43239fe1b0f 100755 --- a/integration/tests/expo-native/run-flows.sh +++ b/integration/tests/expo-native/run-flows.sh @@ -15,22 +15,15 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" -# MAESTRO_ENGINE=cli runs flows with the official maestro CLI instead of -# maestro-runner. The runner's Android driver approximates Maestro's anchored -# regex text matching with UiSelector textContains, which mis-targets taps -# (e.g. 'Continue' resolves to the 'Continue to ' title instead of the -# button), so Android CI uses the CLI; iOS stays on the runner's wda driver. -if [ "${MAESTRO_ENGINE:-runner}" = "cli" ]; then - command -v maestro >/dev/null 2>&1 || { - echo "maestro is required: https://docs.maestro.dev" - exit 1 - } -else - command -v maestro-runner >/dev/null 2>&1 || { - echo "maestro-runner is required: https://devicelab.dev/open-source/maestro-runner/docs/getting-started" - exit 1 - } -fi +# Flows run through the official maestro CLI. maestro-runner was tried and +# reverted: its devicelab driver mangles typed text and its default Android +# driver approximates Maestro's anchored regex text matching with UiSelector +# textContains, which mis-targets taps (e.g. 'Continue' resolves to the +# 'Continue to ' title instead of the button). +command -v maestro >/dev/null 2>&1 || { + echo "maestro is required: https://docs.maestro.dev" + exit 1 +} : "${CLERK_TEST_EMAIL:?CLERK_TEST_EMAIL is required}" : "${CLERK_TEST_PASSWORD:?CLERK_TEST_PASSWORD is required}" @@ -42,23 +35,10 @@ run_flow() { shift local output_root=${MAESTRO_DEBUG_OUTPUT:-${TMPDIR:-/tmp}/clerk-expo-maestro-runner} - if [ "${MAESTRO_ENGINE:-runner}" = "cli" ]; then - # The CLI's env flag is -e; translate the shared --env arguments. - local args=() a - for a in "$@"; do - if [ "$a" = "--env" ]; then args+=("-e"); else args+=("$a"); fi - done - maestro test \ - --debug-output "$output_root/$output_name" \ - --flatten-debug-output \ - "${args[@]}" - else - maestro-runner test \ - --output "$output_root/$output_name" \ - --flatten \ - --artifacts on-failure \ - "$@" - fi + maestro test \ + --debug-output "$output_root/$output_name" \ + --flatten-debug-output \ + "$@" } record_result() { @@ -114,8 +94,8 @@ for flow in flows/*.yaml; do flow_result=failed for attempt in 1 2; do if run_flow "${flow##*/}-attempt-$attempt" \ - --env CLERK_TEST_EMAIL="$CLERK_TEST_EMAIL" \ - --env CLERK_TEST_PASSWORD="$CLERK_TEST_PASSWORD" \ + -e CLERK_TEST_EMAIL="$CLERK_TEST_EMAIL" \ + -e CLERK_TEST_PASSWORD="$CLERK_TEST_PASSWORD" \ "$flow"; then flow_result=passed break From 4380654117b301e4b85e357f2c076c9b7520fa16 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 12:16:11 -0700 Subject: [PATCH 08/12] test(e2e): drop the okhttp force and the native init gate to validate against maestro --- .changeset/expo-native-debug-logging.md | 3 +-- integration/templates/expo-native/app.json | 2 +- .../plugins/withClerkOkHttpAlignment.js | 26 ------------------- .../expo/modules/clerk/ClerkAuthViewModule.kt | 16 ------------ .../clerk/ClerkUserProfileViewModule.kt | 18 ------------- 5 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js diff --git a/.changeset/expo-native-debug-logging.md b/.changeset/expo-native-debug-logging.md index c2aa789de99..b8a09dd2b8c 100644 --- a/.changeset/expo-native-debug-logging.md +++ b/.changeset/expo-native-debug-logging.md @@ -2,8 +2,7 @@ '@clerk/expo': patch --- -Fix Android native components rendering an empty screen when opened before the native SDK finishes initializing, and improve diagnosability of native module initialization on Android. +Improve diagnosability of native module initialization on Android. -- `AuthView` and `UserProfileView` on Android now show a loading indicator until the native Clerk SDK has loaded its configuration, instead of permanently rendering an incomplete screen when opened too early (for example on a slow network). - A failed native Clerk configuration is now logged as a warning in release builds instead of being silently swallowed. When this happens the JS SDK keeps working but the native components (`AuthView`, `UserProfileView`, `UserButton`) cannot render. - Running `adb shell setprop log.tag.ClerkExpo DEBUG` now enables the Expo module's debug logs and clerk-android SDK debug logging at runtime, including in release builds. diff --git a/integration/templates/expo-native/app.json b/integration/templates/expo-native/app.json index 9c378558907..58e258ea523 100644 --- a/integration/templates/expo-native/app.json +++ b/integration/templates/expo-native/app.json @@ -12,6 +12,6 @@ "android": { "package": "com.clerk.exponativebuildfixture" }, - "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser", "./plugins/withClerkOkHttpAlignment.js"] + "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser"] } } diff --git a/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js b/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js deleted file mode 100644 index 52337e8c960..00000000000 --- a/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js +++ /dev/null @@ -1,26 +0,0 @@ -const { withAppBuildGradle } = require('expo/config-plugins'); - -// clerk-android is compiled against OkHttp 5 but React Native pins OkHttp 4, -// leaving a mixed classpath: clerk networking breaks on the 4.x core -// (NoClassDefFoundError: okhttp3.internal.UnreadableResponseBody), while RN's -// cookie jar breaks on the 5.x core (okhttp3.internal.Util). Force the 5.x -// core plus the urlconnection shim that still ships a JavaNetCookieJar class -// compatible with it, so both stacks run on one coherent OkHttp. -const GRADLE_BLOCK = ` -configurations.all { - resolutionStrategy { - force 'com.squareup.okhttp3:okhttp:5.4.0' - force 'com.squareup.okhttp3:logging-interceptor:5.4.0' - force 'com.squareup.okhttp3:okhttp-urlconnection:5.0.0-alpha.16' - } -} -`; - -module.exports = function withClerkOkHttpAlignment(config) { - return withAppBuildGradle(config, cfg => { - if (!cfg.modResults.contents.includes('com.squareup.okhttp3:okhttp:5.4.0')) { - cfg.modResults.contents += GRADLE_BLOCK; - } - return cfg; - }); -}; diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt index f48c00237f9..e75920e3735 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt @@ -6,15 +6,10 @@ import android.content.Context import android.util.Log import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -96,17 +91,6 @@ class ClerkAuthNativeView(context: Context, appContext: AppContext) : ClerkCompo override fun Content() { debugLog(TAG, "setupView - mode: $mode, isDismissible: $isDismissible, hostBackButton: $hostBackButton, activity: $activity") - // clerk-android views compose from a snapshot of the environment and do not - // recompose when it loads afterwards, so a view composed too early stays - // empty forever; hold rendering until the SDK is initialized. - val isInitialized by Clerk.isInitialized.collectAsState() - if (!isInitialized) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - return - } - if (hostBackButton) { ClerkHostBackActionProvider(onHostBack = { onHostBack(mapOf()) }) { AuthContent() } } else { diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt index 6b2511cb075..b65aee0b88d 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt @@ -4,14 +4,7 @@ package expo.modules.clerk import android.content.Context import android.util.Log -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import com.clerk.api.Clerk @@ -53,17 +46,6 @@ class ClerkUserProfileNativeView(context: Context, appContext: AppContext) : Cle override fun Content() { debugLog(TAG, "setupView - isDismissible: $isDismissible, hostBackButton: $hostBackButton") - // clerk-android views compose from a snapshot of the environment and do not - // recompose when it loads afterwards, so a view composed too early stays - // empty forever; hold rendering until the SDK is initialized. - val isInitialized by Clerk.isInitialized.collectAsState() - if (!isInitialized) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - return - } - if (hostBackButton) { ClerkHostBackActionProvider(onHostBack = { onHostBack(mapOf()) }) { ProfileView() } } else { From 69bc4b343fc44bab42eeb7ea7caebff9ba22f820 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 12:34:46 -0700 Subject: [PATCH 09/12] fix(e2e): restore the fixture okhttp alignment (required independent of test runner) --- integration/templates/expo-native/app.json | 2 +- .../plugins/withClerkOkHttpAlignment.js | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js diff --git a/integration/templates/expo-native/app.json b/integration/templates/expo-native/app.json index 58e258ea523..9c378558907 100644 --- a/integration/templates/expo-native/app.json +++ b/integration/templates/expo-native/app.json @@ -12,6 +12,6 @@ "android": { "package": "com.clerk.exponativebuildfixture" }, - "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser"] + "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser", "./plugins/withClerkOkHttpAlignment.js"] } } diff --git a/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js b/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js new file mode 100644 index 00000000000..52337e8c960 --- /dev/null +++ b/integration/templates/expo-native/plugins/withClerkOkHttpAlignment.js @@ -0,0 +1,26 @@ +const { withAppBuildGradle } = require('expo/config-plugins'); + +// clerk-android is compiled against OkHttp 5 but React Native pins OkHttp 4, +// leaving a mixed classpath: clerk networking breaks on the 4.x core +// (NoClassDefFoundError: okhttp3.internal.UnreadableResponseBody), while RN's +// cookie jar breaks on the 5.x core (okhttp3.internal.Util). Force the 5.x +// core plus the urlconnection shim that still ships a JavaNetCookieJar class +// compatible with it, so both stacks run on one coherent OkHttp. +const GRADLE_BLOCK = ` +configurations.all { + resolutionStrategy { + force 'com.squareup.okhttp3:okhttp:5.4.0' + force 'com.squareup.okhttp3:logging-interceptor:5.4.0' + force 'com.squareup.okhttp3:okhttp-urlconnection:5.0.0-alpha.16' + } +} +`; + +module.exports = function withClerkOkHttpAlignment(config) { + return withAppBuildGradle(config, cfg => { + if (!cfg.modResults.contents.includes('com.squareup.okhttp3:okhttp:5.4.0')) { + cfg.modResults.contents += GRADLE_BLOCK; + } + return cfg; + }); +}; From 24e9236499215b26b2d1479afd9957612b0f1965 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 12:55:17 -0700 Subject: [PATCH 10/12] chore(e2e): tighten comments --- integration/tests/expo-native/run-flows.sh | 8 +++----- packages/expo/src/provider/nativeClientSync.tsx | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/integration/tests/expo-native/run-flows.sh b/integration/tests/expo-native/run-flows.sh index 43239fe1b0f..b3eb7b7f64c 100755 --- a/integration/tests/expo-native/run-flows.sh +++ b/integration/tests/expo-native/run-flows.sh @@ -15,11 +15,9 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" -# Flows run through the official maestro CLI. maestro-runner was tried and -# reverted: its devicelab driver mangles typed text and its default Android -# driver approximates Maestro's anchored regex text matching with UiSelector -# textContains, which mis-targets taps (e.g. 'Continue' resolves to the -# 'Continue to ' title instead of the button). +# Runs the official maestro CLI. maestro-runner was tried and reverted: its +# drivers mangle typed text and resolve text selectors by substring, so +# tapOn 'Continue' hits the 'Continue to ' title instead of the button. command -v maestro >/dev/null 2>&1 || { echo "maestro is required: https://docs.maestro.dev" exit 1 diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 0b7a5cfff42..c5f35c2bce9 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -1023,9 +1023,7 @@ export function useNativeClientBootstrap({ ); } } else { - // Also logged in release builds: a failed native configure silently - // disables the native components, which is undiagnosable from device - // logs otherwise. + // Warned in release too: a failed native configure silently disables the native components. const message = error instanceof Error ? error.message : String(error); console.warn( `[ClerkProvider] Failed to configure native Clerk (${Platform.OS}); native components will not work: ${message}`, From 4fbba9da9e85a9d257e5ef6616c9649942556e34 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 12:57:06 -0700 Subject: [PATCH 11/12] ci(e2e): drop the expo debug-logging changes to keep the branch test-only --- .changeset/expo-native-debug-logging.md | 8 -------- .changeset/expo-native-e2e-fixes.md | 2 ++ .github/workflows/expo-native-build.yml | 3 --- .../java/expo/modules/clerk/ClerkAuthViewModule.kt | 2 +- .../main/java/expo/modules/clerk/ClerkExpoDebug.kt | 10 ---------- .../main/java/expo/modules/clerk/ClerkExpoModule.kt | 4 ++-- .../expo/modules/clerk/ClerkUserProfileViewModule.kt | 2 +- packages/expo/src/provider/nativeClientSync.tsx | 11 ++--------- 8 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 .changeset/expo-native-debug-logging.md create mode 100644 .changeset/expo-native-e2e-fixes.md delete mode 100644 packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt diff --git a/.changeset/expo-native-debug-logging.md b/.changeset/expo-native-debug-logging.md deleted file mode 100644 index b8a09dd2b8c..00000000000 --- a/.changeset/expo-native-debug-logging.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@clerk/expo': patch ---- - -Improve diagnosability of native module initialization on Android. - -- A failed native Clerk configuration is now logged as a warning in release builds instead of being silently swallowed. When this happens the JS SDK keeps working but the native components (`AuthView`, `UserProfileView`, `UserButton`) cannot render. -- Running `adb shell setprop log.tag.ClerkExpo DEBUG` now enables the Expo module's debug logs and clerk-android SDK debug logging at runtime, including in release builds. diff --git a/.changeset/expo-native-e2e-fixes.md b/.changeset/expo-native-e2e-fixes.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/expo-native-e2e-fixes.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index a0d1349f6f5..2e1adb1f41c 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -355,10 +355,7 @@ jobs: disable-animations: true # The action runs each script line in a separate sh -c; the folded # scalar (>-) plus && keeps everything in one shell invocation. - # The setprop turns on @clerk/expo + clerk-android debug logging in the - # release build so the logcat artifact shows native SDK activity. script: >- - adb shell setprop log.tag.ClerkExpo DEBUG && cd integration/tests/expo-native && MAESTRO_DEBUG_OUTPUT="$RUNNER_TEMP/maestro-debug" ./run-android-flows.sh "$GITHUB_WORKSPACE/$FIXTURE_DIR/android/app/build/outputs/apk/release/app-release.apk" diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt index e75920e3735..40d39f1119a 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt @@ -31,7 +31,7 @@ import expo.modules.kotlin.viewevent.EventDispatcher private const val TAG = "ClerkAuthViewModule" private fun debugLog(tag: String, message: String) { - if (clerkExpoDebugEnabled()) { + if (BuildConfig.DEBUG) { Log.d(tag, message) } } diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt deleted file mode 100644 index 73e0407415a..00000000000 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoDebug.kt +++ /dev/null @@ -1,10 +0,0 @@ -package expo.modules.clerk - -import android.util.Log - -// Release builds are silent by default; `adb shell setprop log.tag.ClerkExpo DEBUG` -// enables module debug logs and clerk-android SDK debug logging without a rebuild. -internal const val CLERK_EXPO_DEBUG_TAG = "ClerkExpo" - -internal fun clerkExpoDebugEnabled(): Boolean = - BuildConfig.DEBUG || Log.isLoggable(CLERK_EXPO_DEBUG_TAG, Log.DEBUG) diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt index f9173716358..c5718ade6ef 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt @@ -31,7 +31,7 @@ private const val HOST_SDK_VERSION_HEADER = "x-clerk-host-sdk-version" private const val HOST_SDK = "expo" private fun debugLog(tag: String, message: String) { - if (clerkExpoDebugEnabled()) { + if (BuildConfig.DEBUG) { Log.d(tag, message) } } @@ -121,7 +121,7 @@ class ClerkExpoModule : Module() { } } - return ClerkConfigurationOptions(enableDebugMode = clerkExpoDebugEnabled()).withCustomHeaders(customHeaders) + return ClerkConfigurationOptions().withCustomHeaders(customHeaders) } private fun startClientStateObserver() { diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt index b65aee0b88d..51826181aff 100644 --- a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt +++ b/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt @@ -19,7 +19,7 @@ import expo.modules.kotlin.viewevent.EventDispatcher private const val TAG = "ClerkUserProfileViewModule" private fun debugLog(tag: String, message: String) { - if (clerkExpoDebugEnabled()) { + if (BuildConfig.DEBUG) { Log.d(tag, message) } } diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index c5f35c2bce9..5c564d92f32 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -1022,15 +1022,8 @@ export function useNativeClientBootstrap({ `To enable native features, add "@clerk/expo" to your app.json plugins array.`, ); } - } else { - // Warned in release too: a failed native configure silently disables the native components. - const message = error instanceof Error ? error.message : String(error); - console.warn( - `[ClerkProvider] Failed to configure native Clerk (${Platform.OS}); native components will not work: ${message}`, - ); - if (__DEV__) { - console.error(`[ClerkProvider] Failed to configure Clerk ${Platform.OS}:`, error); - } + } else if (__DEV__) { + console.error(`[ClerkProvider] Failed to configure Clerk ${Platform.OS}:`, error); } } finally { if (didAttemptConfigure && isCurrentConfiguration()) { From f5c01244fdbf57c8875cde66a908100153a4a4a2 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 31 Jul 2026 13:14:04 -0700 Subject: [PATCH 12/12] ci(e2e): bump maestro to 2.8.0 --- .github/workflows/expo-native-build.yml | 2 +- integration/tests/expo-native/config.yaml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 2e1adb1f41c..9702cf7d505 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -27,7 +27,7 @@ env: SDK_PACK_DIR: /tmp/clerk-expo-pack E2E_INSTANCE_NAME: clerkstage-with-native-components BAPI_URL: https://api.clerkstage.dev - MAESTRO_VERSION: '2.6.1' + MAESTRO_VERSION: '2.8.0' jobs: native-build: diff --git a/integration/tests/expo-native/config.yaml b/integration/tests/expo-native/config.yaml index e307bf2630b..94e3659b694 100644 --- a/integration/tests/expo-native/config.yaml +++ b/integration/tests/expo-native/config.yaml @@ -1,8 +1,10 @@ # Maestro workspace config: https://docs.maestro.dev/reference/workspace-configuration # Top-level files under flows/ are cross-platform tests; flows/subflows/ holds # runFlow-only pieces that discovery never runs standalone. Selectors use -# English text/labels: clerk-android ships no testTag/resource-ids, and both -# native SDKs localize all strings, so devices must run the en locale. +# English text/labels: clerk-android ships no testTag/resource-ids (clerk-ios +# does expose accessibility identifiers like clerk.auth.start.identifier, but +# shared flows can't rely on iOS-only ids), and both native SDKs localize all +# strings, so devices must run the en locale. appId: com.clerk.exponativebuildfixture flows: - 'flows/*.yaml'