From 86d040ccc08d2ed52535e6e26a2dc8a17664aa85 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:18:30 -0400 Subject: [PATCH 1/2] test(expo): force-stop Google Play services when the Android Google test fails The Android Google test force-stopped Play services only when it reached the end. When a step failed first, Play services' add-account flow stayed in the app's task and later covered the home in the next test, which made token-cache-persistence/relaunch fail its first attempt. Co-Authored-By: Claude Opus 5.5 --- .changeset/google-test-cleanup-on-failure.md | 2 + .../native-modules/google-sign-in.e2e.ts | 57 ++++++++++--------- 2 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 .changeset/google-test-cleanup-on-failure.md diff --git a/.changeset/google-test-cleanup-on-failure.md b/.changeset/google-test-cleanup-on-failure.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/google-test-cleanup-on-failure.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/expo-native/specs/features/native-modules/google-sign-in.e2e.ts b/integration/expo-native/specs/features/native-modules/google-sign-in.e2e.ts index 43e5e9f2417..088cb188e34 100644 --- a/integration/expo-native/specs/features/native-modules/google-sign-in.e2e.ts +++ b/integration/expo-native/specs/features/native-modules/google-sign-in.e2e.ts @@ -29,32 +29,37 @@ test('useSignInWithGoogle opens the native Google sign-in, and cancelling it end await host.launch(); await host.tap(homeLinks(screen).nativeModules); await host.tap(screen.getByTestId('google-sign-in-button')); - if (platform === 'android') { - await expect - .poll(() => googlePage.count().catch(() => 0), { - timeout: GOOGLE_UI_TIMEOUT_MS, - message: 'a page of Google Play services is on screen', - }) - .toBeGreaterThan(0); - } else { - await expect(screen.getByText(/accounts\.google\.com/)).toBeVisible({ timeout: GOOGLE_UI_TIMEOUT_MS }); - } - await host.screenshot('google-system-prompt'); - let cancelled = true; - if (platform === 'android') { - await device.back(); - cancelled = await googleResult.waitFor({ timeout: BACK_OUT_MS }).then( - () => true, - () => false, - ); + try { + if (platform === 'android') { + await expect + .poll(() => googlePage.count().catch(() => 0), { + timeout: GOOGLE_UI_TIMEOUT_MS, + message: 'a page of Google Play services is on screen', + }) + .toBeGreaterThan(0); + } else { + await expect(screen.getByText(/accounts\.google\.com/)).toBeVisible({ timeout: GOOGLE_UI_TIMEOUT_MS }); + } + await host.screenshot('google-system-prompt'); + let cancelled = true; + if (platform === 'android') { + await device.back(); + cancelled = await googleResult.waitFor({ timeout: BACK_OUT_MS }).then( + () => true, + () => false, + ); + if (!cancelled) await forceStopGooglePlayServices(); + await expect(googlePage).toHaveCount(0, { timeout: 15_000 }); + } else { + await device.alert('dismiss'); + } + if (cancelled) { + await expect(googleResult).toHaveText('Google sign-in was cancelled', { timeout: 15_000 }); + await host.screenshot('google-cancelled'); + } + } catch (error) { // Without a Google account, Google Play services can show a page that back does not close, and its add-account flow outlives the app and covers the next test. - if (!cancelled) await forceStopGooglePlayServices(); - await expect(googlePage).toHaveCount(0, { timeout: 15_000 }); - } else { - await device.alert('dismiss'); - } - if (cancelled) { - await expect(googleResult).toHaveText('Google sign-in was cancelled', { timeout: 15_000 }); - await host.screenshot('google-cancelled'); + if (platform === 'android') await forceStopGooglePlayServices().catch(() => {}); + throw error; } }); From 33aaf3bcdd9b12752b8f903481c241b414f94806 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:24:54 -0400 Subject: [PATCH 2/2] docs(expo): note the Google test's cleanup on failure in the verify skill Co-Authored-By: Claude Opus 5.5 --- .claude/skills/verify-clerk-expo/features/native-modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/verify-clerk-expo/features/native-modules.md b/.claude/skills/verify-clerk-expo/features/native-modules.md index 4cf1946ddf1..3b887ed0587 100644 --- a/.claude/skills/verify-clerk-expo/features/native-modules.md +++ b/.claude/skills/verify-clerk-expo/features/native-modules.md @@ -20,7 +20,7 @@ Preconditions: - `up` has built the test app. The build links `@clerk/expo-google-signin` and `@clerk/expo-biometrics` from the workspace. The test app's `app.json` lists the `@clerk/expo-google-signin` config plugin, beside those of `@clerk/expo` and `@clerk/expo-native-components`, and holds three placeholder values under `extra`: a web client ID, an iOS client ID, and the iOS URL scheme that the plugin registers. They belong to no Google project. - `specs/features/native-modules/biometric-availability.settings.json` turns biometric sign-in on for the instance (`auth_biometric.used_for_sign_in`). The hook reads that as `trusted_device_sign_in_enabled` in the environment's native settings, which the file declares as the expected value. `run` applies it before the test of that spec file. The Google spec file runs on the standard settings, so a run of both has two settings groups. -- **Google sign-in.** Run `integration/expo-native/bin/control-clerk-expo run native-modules/google-sign-in --platform ios`, and again with `--platform android`. The test taps `Native modules` on the home and taps `google-sign-in-button`. On iOS it waits for the system prompt that names `accounts.google.com` and dismisses it with `device.alert('dismiss')`. It then expects `google-result` to say `Google sign-in was cancelled`. On Android it waits for a node whose id begins `com.google.android.gms:id/`, which is a page of Google Play services, and presses back once. When `google-result` appears within 5 seconds, it expects `Google sign-in was cancelled`, as on iOS. When nothing appears, it force-stops Google Play services with `adb shell am force-stop com.google.android.gms` and does not check the result. Either way it then expects no `com.google.android.gms:id/` node on screen. That last check matters: Google Play services keeps its add-account flow running after the app is force-stopped, and its next page would otherwise cover whichever test runs next. Screenshots `google-system-prompt` always, and `google-cancelled` when the app reported the cancel. +- **Google sign-in.** Run `integration/expo-native/bin/control-clerk-expo run native-modules/google-sign-in --platform ios`, and again with `--platform android`. The test taps `Native modules` on the home and taps `google-sign-in-button`. On iOS it waits for the system prompt that names `accounts.google.com` and dismisses it with `device.alert('dismiss')`. It then expects `google-result` to say `Google sign-in was cancelled`. On Android it waits for a node whose id begins `com.google.android.gms:id/`, which is a page of Google Play services, and presses back once. When `google-result` appears within 5 seconds, it expects `Google sign-in was cancelled`, as on iOS. When nothing appears, it force-stops Google Play services with `adb shell am force-stop com.google.android.gms` and does not check the result. Either way it then expects no `com.google.android.gms:id/` node on screen. That last check matters: Google Play services keeps its add-account flow running after the app is force-stopped, and its next page would otherwise cover whichever test runs next. For the same reason, when any step after the tap on `google-sign-in-button` fails on Android, the test force-stops Google Play services before it reports the failure. Screenshots `google-system-prompt` always, and `google-cancelled` when the app reported the cancel. - **Biometric availability.** Run `run native-modules/biometric-availability` on each platform. The test opens the same screen, taps `biometric-availability-button`, and expects `biometric-availability-result` to say `biometric availability: biometric_authentication_unavailable` on iOS and `biometric availability: no_local_credential` on Android. Screenshot `biometric-availability`. - **Proof.** Each screenshot shows the result text under its button. Both tests pass on iOS and Android.