Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
53dc694
test(expo): add comprehensive test coverage for native components
chriscanin Apr 15, 2026
fe9e3fe
test(integration-mobile): fix Maestro flows after real-device validation
chriscanin Apr 16, 2026
532dcdf
test(integration-mobile): iOS UserProfile selectors + skip unready flows
chriscanin Apr 16, 2026
034c3f3
test(integration-mobile): fix cold-launch flow for post-signin state
chriscanin Apr 16, 2026
6250faf
test(integration-mobile): Android fixes + dark-mode-applied skip
chriscanin Apr 16, 2026
4b7c966
ci(mobile-e2e): wire INTEGRATION_INSTANCE_KEYS + per-run BAPI test user
chriscanin Apr 30, 2026
3848bcd
Merge branch 'main' into chris/native-component-tests
chriscanin May 6, 2026
808601a
Merge remote-tracking branch 'origin/main' into chris/native-componen…
chriscanin May 7, 2026
7887add
test(expo): fix unit tests and lint after merge with main
chriscanin May 7, 2026
989630d
Merge branch 'main' into chris/native-component-tests
chriscanin May 7, 2026
e4ec3ef
ci(e2e): list available keys when resolve-instance-keys can't find th…
chriscanin May 7, 2026
0af5318
ci(e2e): use staging BAPI for mobile-e2e BAPI calls
chriscanin May 8, 2026
555daa6
ci(e2e): print BAPI response body when user creation fails
chriscanin May 8, 2026
b2f4fcc
ci(e2e): include username in BAPI user creation payload
chriscanin May 8, 2026
e562c92
ci(e2e): override quickstart's verdaccio .npmrc with public registry
chriscanin May 8, 2026
7531501
ci(e2e): swap @clerk/expo to locally-built package before install
chriscanin May 8, 2026
b9d7360
ci(e2e): pnpm install --ignore-workspace so quickstart actually installs
chriscanin May 8, 2026
230b55b
ci(e2e): pnpm pack @clerk/expo so workspace deps resolve outside work…
chriscanin May 8, 2026
73fe7f6
ci(e2e): stub missing splash/adaptive-icon assets before prebuild
chriscanin May 8, 2026
4c086e2
ci(e2e): target an iOS simulator for expo run:ios so CI doesn't need …
chriscanin May 8, 2026
2998ac4
ci(e2e): disable Apple Sign-In to skip iOS signing + chain Android sc…
chriscanin May 11, 2026
20cb509
ci(e2e): set bundle id to com.clerk.clerkexpoquickstart so Maestro ca…
chriscanin May 11, 2026
bfe46fa
ci(e2e): cache gradle/pods/deriveddata/avd/node_modules + strip expo-…
chriscanin May 11, 2026
ee71629
ci(e2e): add flows_filter input + diagnose Clerk user visibility
chriscanin May 11, 2026
bcf09a1
ci(e2e): screenshot each sign-in step + retry gradle on wrapper flake
chriscanin May 12, 2026
b7559fa
ci(e2e): collapse Android retry loop onto one line
chriscanin May 12, 2026
7caffcb
ci(e2e): make Maestro install fail loud + retry on curl flake
chriscanin May 12, 2026
c787226
ci(e2e): include debug screenshots in maestro artifacts upload
chriscanin May 12, 2026
508c136
ci(e2e): pass CLERK_TEST_EMAIL/PASSWORD to maestro via --env
chriscanin May 12, 2026
f60c318
test(e2e): handle post-password email-code verification step
chriscanin May 12, 2026
5d658a6
ci(e2e): binary cache APK / .app + shrink diagnose to BAPI sanity check
chriscanin May 12, 2026
a66a391
test(e2e): wait for AuthView email field after sign-out
chriscanin May 12, 2026
d2a26dc
ci(e2e): set up JDK 17 unconditionally (Maestro needs Java to run)
chriscanin May 12, 2026
5262cb4
test(e2e): wait for AuthView email field on sign-in subflow
chriscanin May 12, 2026
71991ed
test(e2e): clear email field via long-press + Select all before typing
chriscanin May 12, 2026
3d95c38
chore(e2e): move integration-mobile to integration/mobile + case-inse…
chriscanin May 12, 2026
04e03e1
test(e2e): skip 2 flaky flows until underlying SDK/Maestro bugs are f…
chriscanin May 12, 2026
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
6 changes: 6 additions & 0 deletions .changeset/expo-native-component-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/expo': patch
---

- Export `NativeSessionSync` and `app.plugin.js` sub-plugins to enable unit testing (internal, no public API change).
- Add JUnit/Robolectric/MockK test dependencies to the Android module for native unit tests.
464 changes: 435 additions & 29 deletions .github/workflows/mobile-e2e.yml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion .typedoc/custom-plugin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ const LINK_REPLACEMENTS = [
['enterprise-account-connection', '/docs/reference/backend/types/backend-enterprise-account-connection'],
['enterprise-connection', '/docs/reference/backend/types/backend-enterprise-connection'],
['enterprise-connection-oauth-config', '/docs/reference/backend/types/backend-enterprise-connection-oauth-config'],
['enterprise-connection-saml-connection', '/docs/reference/backend/types/backend-enterprise-connection-saml-connection'],
[
'enterprise-connection-saml-connection',
'/docs/reference/backend/types/backend-enterprise-connection-saml-connection',
],
['external-account', '/docs/reference/backend/types/backend-external-account'],
['phone-number', '/docs/reference/backend/types/backend-phone-number'],
['saml-account', '/docs/reference/backend/types/backend-saml-account'],
Expand Down
7 changes: 7 additions & 0 deletions integration/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Local env file — never commit. Use config/.env.example as the template.
config/.env

# Maestro artifacts
*.png
*.mp4
maestro-output/
18 changes: 18 additions & 0 deletions integration/mobile/config/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copy to .env and fill in values from your Clerk dev instance.
# .env is gitignored.

# Clerk publishable key for the test app (development instance)
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here

# Google Sign-In (iOS): the reversed-client-id URL scheme from GoogleService-Info.plist
EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.your-ios-client-id

# Google Sign-In (Android + iOS): the web client ID
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com

# Test user (must use Clerk's testmode +clerk_test pattern for high-rate-limit access)
CLERK_TEST_EMAIL=tester+clerk_test@example.com
CLERK_TEST_PASSWORD=ClerkTest!2024

# Optional: which simulator/emulator to target by default (Maestro will auto-pick if unset)
# MAESTRO_DEVICE=iPhone 16 Pro
27 changes: 27 additions & 0 deletions integration/mobile/fixtures/test-users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "Test user metadata for the Maestro flows. Real credentials live in config/.env, never in this file.",
"users": [
{
"id": "primary",
"description": "Primary test user. Pre-existing in the Clerk dev instance.",
"emailEnv": "CLERK_TEST_EMAIL",
"passwordEnv": "CLERK_TEST_PASSWORD"
},
{
"id": "secondary",
"description": "Used by sign-out-then-sign-in-different-user flow. Provision separately.",
"emailEnv": "CLERK_TEST_EMAIL_SECONDARY",
"passwordEnv": "CLERK_TEST_PASSWORD_SECONDARY"
},
{
"id": "signup",
"description": "Generated per run with the +clerk_test pattern so verification codes auto-resolve.",
"emailTemplate": "tester+clerk_test_{timestamp}@example.com",
"passwordEnv": "CLERK_TEST_PASSWORD"
}
],
"notes": [
"Use +clerk_test addresses to bypass captcha and get higher rate limits.",
"Document any new test users you add here so future devs know what they're for."
]
}
9 changes: 9 additions & 0 deletions integration/mobile/flows/common/assert-signed-in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Subflow: assert the user is on the signed-in home screen.
appId: com.clerk.clerkexpoquickstart
---
- assertVisible:
text: "Welcome"
- assertVisible:
text: "Manage Profile"
- assertVisible:
text: "Sign Out"
5 changes: 5 additions & 0 deletions integration/mobile/flows/common/assert-signed-out.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Subflow: assert the user is on the signed-out screen with the AuthView visible.
appId: com.clerk.clerkexpoquickstart
---
- assertVisible:
text: 'Welcome! Sign in to continue\.?'
66 changes: 66 additions & 0 deletions integration/mobile/flows/common/open-app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Subflow: launch the NativeComponentQuickstart app from a clean state.
# This is a dev build, so we must handle the Expo dev launcher (iOS uses
# http://localhost:8081; Android uses http://10.0.2.2:8081) and the
# Expo developer menu overlay that appears on first launch.
appId: com.clerk.clerkexpoquickstart
---
- launchApp:
clearState: true
- waitForAnimationToEnd:
timeout: 5000
# Android Google Password Manager may linger from a previous run.
# Dismiss it before anything else.
- runFlow:
when:
visible: ".*Google Password Manager.*"
commands:
- tapOn:
text: "Not now|Never"
- waitForAnimationToEnd:
timeout: 2000
# Dev launcher: tap whichever dev-server URL is shown (port 8081).
# Maestro's text field is regex-matched, so ".*:8081" matches both
# "http://10.0.2.2:8081" (Android) and "http://localhost:8081" (iOS).
- runFlow:
when:
visible: "Development Build"
commands:
- tapOn:
text: ".*:8081"
- waitForAnimationToEnd:
timeout: 10000
# Dismiss the Expo developer menu if it pops up. Tap the "Close" (X)
# accessibility element at the top-right of the sheet. On iOS the
# accessibility text is "Close" (not the resource-id "xmark"); on Android
# it's "Close" on the view's accessibilityText.
- runFlow:
when:
visible: ".*developer menu.*"
commands:
- tapOn:
text: "Close"
optional: true
- runFlow:
when:
visible: ".*developer menu.*"
commands:
- tapOn:
point: "50%,20%"
- waitForAnimationToEnd:
timeout: 2000
- waitForAnimationToEnd:
timeout: 3000
# If a previous flow left the user signed in (session persists in
# Keychain/SecureStore across clearState), sign out so subsequent flows
# start from the AuthView.
- runFlow:
when:
visible: "Sign Out"
commands:
- tapOn:
text: "Sign Out"
- waitForAnimationToEnd:
timeout: 3000
# Assert the AuthView is visible (signed-out state)
- assertVisible:
text: 'Welcome! Sign in to continue\.?'
66 changes: 66 additions & 0 deletions integration/mobile/flows/common/sign-in-email-password.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Subflow: enter email + password into the native AuthView and submit.
# Requires CLERK_TEST_EMAIL and CLERK_TEST_PASSWORD env vars.
appId: com.clerk.clerkexpoquickstart
---
- assertVisible:
text: 'Welcome! Sign in to continue\.?'
# On Android the AuthView sometimes renders its welcome text a beat before
# the email field. Don't tap until the field is actually present so we
# don't race the form's second render pass. If this times out, the failure
# mode is a clear timeout that points at a real render-timing bug.
- extendedWaitUntil:
visible: "Enter your email or username"
timeout: 10000
- takeScreenshot: debug-01-welcome
# Tap the field, then long-press to bring up the selection menu and pick
# "Select all" so we replace any pre-populated value (Clerk persists the
# last-used identifier in secure-store, which survives launchApp/clearState).
# After Select-all, inputText REPLACES the selection rather than appending.
- tapOn:
text: "Enter your email or username"
- longPressOn:
text: "Enter your email or username"
# iOS shows "Select All", Android shows "Select all" — match both.
- runFlow:
when:
visible: "Select [Aa]ll"
commands:
- tapOn:
text: "Select [Aa]ll"
- inputText: ${CLERK_TEST_EMAIL}
- takeScreenshot: debug-02-email-typed
- tapOn:
text: "Continue"
index: 0
- waitForAnimationToEnd:
timeout: 3000
- takeScreenshot: debug-03-after-continue
- tapOn:
text: "Enter your password"
- eraseText: 50
- inputText: ${CLERK_TEST_PASSWORD}
- tapOn:
text: "Continue"
index: 0
- waitForAnimationToEnd:
timeout: 5000
# Some instances require email-code verification AFTER a successful
# password submit. For +clerk_test@ emails Clerk's documented test
# verification code is "424242" — see https://clerk.com/docs/testing/test-emails-and-phones
- runFlow:
when:
visible: "Check your email"
commands:
- inputText: "424242"
- waitForAnimationToEnd:
timeout: 5000
# Android Google Password Manager may prompt to save the password after
# sign-in. Dismiss it so assertions on the home screen work.
- runFlow:
when:
visible: ".*Google Password Manager.*"
commands:
- tapOn:
text: "Not now|Never"
- waitForAnimationToEnd:
timeout: 2000
16 changes: 16 additions & 0 deletions integration/mobile/flows/common/sign-out-via-button.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Subflow: tap the Sign Out button on the home screen and wait for AuthView.
appId: com.clerk.clerkexpoquickstart
---
- tapOn:
text: "Sign Out"
- waitForAnimationToEnd:
timeout: 8000
- assertVisible:
text: 'Welcome! Sign in to continue\.?'
# After sign-out the AuthView sometimes renders its welcome text a beat
# before the email field. Don't return from this subflow until the
# field is actually present, so cycle flows don't race a half-rendered
# AuthView. If this assertion times out the test surfaces a real bug.
- extendedWaitUntil:
visible: "Enter your email or username"
timeout: 10000
16 changes: 16 additions & 0 deletions integration/mobile/flows/common/sign-out-via-profile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Subflow: open the UserProfile via Manage Profile, tap Log out, assert signed out.
appId: com.clerk.clerkexpoquickstart
---
- tapOn:
text: "Manage Profile"
- waitForAnimationToEnd:
timeout: 3000
- assertVisible:
text: "Account"
# iOS renders "Sign out", Android renders "Log out"
- tapOn:
text: "Log out|Sign out"
- waitForAnimationToEnd:
timeout: 3000
- assertVisible:
text: 'Welcome! Sign in to continue\.?'
29 changes: 29 additions & 0 deletions integration/mobile/flows/cycles/sign-in-sign-out-sign-in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# REGRESSION: After sign-in -> sign-out -> sign-in, the second sign-in
# completed natively but the JS SDK never picked it up. This flow signs in
# twice in a row to verify the cycle works correctly.
#
# TODO: re-enable once two underlying issues are fixed:
# - Android: clerk-android AuthStartView reads Clerk.enabledFirstFactorAttributes
# and Clerk.socialProviders via non-observable getters; the body of a re-mounted
# AuthView can be left empty when its first composition wins the race against
# environment population. A MutableStateFlow + observe in AuthStartView fixes
# it (see drafted patch in clerk-android workspace).
# - iOS: Maestro's field-clearing on the second sign-in leaves a leading
# character (saw ".ci-..." in failing screenshots), which Clerk rejects as
# "Identifier is invalid".
# Both bugs are real; both manifest only on slower hardware (CI) and both pass
# reliably on local Pixel 9 Pro / iPhone 17 Pro simulators.
appId: com.clerk.clerkexpoquickstart
tags:
- regression
- skip
---
- runFlow: ../common/open-app.yaml
# First sign-in
- runFlow: ../common/sign-in-email-password.yaml
- runFlow: ../common/assert-signed-in.yaml
# Sign out via the Sign Out button
- runFlow: ../common/sign-out-via-button.yaml
# Second sign-in -- must work without the bug
- runFlow: ../common/sign-in-email-password.yaml
- runFlow: ../common/assert-signed-in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Happy path: sign in as one user, sign out, sign in as a different user.
# Requires CLERK_TEST_EMAIL_SECONDARY and CLERK_TEST_PASSWORD_SECONDARY env vars.
appId: com.clerk.clerkexpoquickstart
# TODO: requires CLERK_TEST_EMAIL_SECONDARY / CLERK_TEST_PASSWORD_SECONDARY
# env vars and a second test user in the Clerk dev instance. Skip until
# a secondary user is provisioned.
tags:
- happy-path
- skip
---
- runFlow: ../common/open-app.yaml
- runFlow: ../common/sign-in-email-password.yaml
- runFlow: ../common/assert-signed-in.yaml
- runFlow: ../common/sign-out-via-button.yaml
- runFlow: ../common/assert-signed-out.yaml
# Sign in as a different user
- tapOn:
text: "Enter your email or username"
- inputText: ${CLERK_TEST_EMAIL_SECONDARY}
- tapOn:
text: "Continue"
index: 0
- waitForAnimationToEnd:
timeout: 3000
- tapOn:
text: "Enter your password"
- inputText: ${CLERK_TEST_PASSWORD_SECONDARY}
- tapOn:
text: "Continue"
index: 0
- waitForAnimationToEnd:
timeout: 5000
- runFlow: ../common/assert-signed-in.yaml
38 changes: 38 additions & 0 deletions integration/mobile/flows/profile/edit-first-name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Happy path: open profile, edit first name, save, dismiss, assert still signed in.
appId: com.clerk.clerkexpoquickstart
tags:
- happy-path
---
- runFlow: ../common/open-app.yaml
- runFlow: ../common/sign-in-email-password.yaml
- runFlow: ../common/assert-signed-in.yaml
# Open UserProfile via Manage Profile
- tapOn:
text: "Manage Profile"
- waitForAnimationToEnd:
timeout: 3000
- assertVisible:
text: "Account"
# Tap profile editor (iOS: "Update profile", Android: "Edit profile")
- tapOn:
text: "(Edit|Update) profile"
- waitForAnimationToEnd:
timeout: 2000
# Clear and type new first name
- eraseText: 50
- inputText: "TestUser"
- tapOn: "Save"
- waitForAnimationToEnd:
timeout: 3000
# Dismiss profile (iOS: Close X, Android: back)
- tapOn:
text: "Close"
optional: true
- runFlow:
when:
visible: "Account"
commands:
- back
- waitForAnimationToEnd:
timeout: 2000
- runFlow: ../common/assert-signed-in.yaml
8 changes: 8 additions & 0 deletions integration/mobile/flows/profile/open-inline-profile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SKIP: The NativeComponentQuickstart app does not have an inline profile screen.
# This flow is not applicable and is retained as a placeholder only.
appId: com.clerk.clerkexpoquickstart
tags:
- skip
---
# No-op: inline profile is not available in the quickstart app.
- runFlow: ../common/open-app.yaml
28 changes: 28 additions & 0 deletions integration/mobile/flows/profile/open-profile-modal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Happy path: sign in, tap Manage Profile, assert UserProfile opens, dismiss,
# assert still signed in.
appId: com.clerk.clerkexpoquickstart
tags:
- happy-path
---
- runFlow: ../common/open-app.yaml
- runFlow: ../common/sign-in-email-password.yaml
- runFlow: ../common/assert-signed-in.yaml
# Open UserProfile via Manage Profile button
- tapOn:
text: "Manage Profile"
- waitForAnimationToEnd:
timeout: 3000
- assertVisible:
text: "Account"
# Dismiss the profile (iOS: tap Close X, Android: back button)
- tapOn:
text: "Close"
optional: true
- runFlow:
when:
visible: "Account"
commands:
- back
- waitForAnimationToEnd:
timeout: 2000
- runFlow: ../common/assert-signed-in.yaml
Loading
Loading