Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1559ef1
Add local E2E test suites for the example app
dianaKhortiuk-frontegg Apr 8, 2026
55ad424
Fix react-native-sdk-e2e workflow ref
dianaKhortiuk-frontegg Apr 8, 2026
44c5a0a
Merge remote-tracking branch 'origin/master' into chore/example-app-f…
dianaKhortiuk-frontegg Apr 8, 2026
cff4b4c
Drop stale credential refs from E2E_TESTS.md
dianaKhortiuk-frontegg Apr 8, 2026
1336257
Replace broken cross-repo e2e workflow with local Android emulator suite
dianaKhortiuk-frontegg Apr 9, 2026
3df9647
Add iOS E2E job and register XCUITest target in Xcode project
dianaKhortiuk-frontegg Apr 9, 2026
93f749a
Fix pbxproj for UI test target: add productType and fix product refs
dianaKhortiuk-frontegg Apr 9, 2026
2a24d2c
Fix iOS UI test target: add PRODUCT_NAME, scheme entry, deployment ta…
dianaKhortiuk-frontegg Apr 9, 2026
1654076
Wire iOS E2E tests to LocalMockAuthServer
dianaKhortiuk-frontegg Apr 14, 2026
9133b0a
Verify E2E tests locally: fix Android Kotlin plugin, iOS mock server,…
dianaKhortiuk-frontegg Apr 15, 2026
c89165d
Merge master into #67: resolve FronteggButton.tsx (keep both testID +…
dianaKhortiuk-frontegg Jun 26, 2026
de0826e
fix(e2e): pin example Gradle to 8.0.1 (RN 0.72) + drop stale Podfile.…
dianaKhortiuk-frontegg Jun 26, 2026
51ebaf1
fix(e2e): single-line the android gradle command + pod install --repo…
dianaKhortiuk-frontegg Jun 26, 2026
284ad35
Merge remote-tracking branch 'origin/master' into HEAD
dianaKhortiuk-frontegg Jun 26, 2026
9ac758f
fix(ios/spm): link FronteggSwift into every target that links FronteggRN
dianaKhortiuk-frontegg Jun 26, 2026
49a3106
test(e2e/ios): bind the mock auth server once instead of per test class
dianaKhortiuk-frontegg Jun 29, 2026
11c66e2
ci(e2e/ios): build + run UI tests in Release so the app embeds the JS…
dianaKhortiuk-frontegg Jun 29, 2026
72a9823
ci(e2e/ios): Debug build with embedded JS + mock web-auth transport f…
dianaKhortiuk-frontegg Jun 29, 2026
7c33f91
ci(e2e/ios): exclude real social-login test from CI (mirror SDK hoste…
dianaKhortiuk-frontegg Jun 29, 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
229 changes: 219 additions & 10 deletions .github/workflows/react-native-sdk-e2e.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,227 @@
# Runs React Native SDK E2E tests from e2e-system-tests on every PR.
# The check runs in the e2e-system-tests repo and reports back here; merge is blocked until it passes.
# Require this check in branch protection: Settings → Branches → rule → "React Native SDK E2E".
# Local E2E tests for the React Native example app (Android + iOS).
# Mirrors frontegg-android-kotlin's demo-e2e.yml and frontegg-ios-swift's
# demo-e2e.yml: builds the example app, boots a device/simulator, runs the
# instrumented tests, and reports results as PR checks.
#
# The previous cross-repo call to e2e-system-tests was never functional
# (0-second failure on every run since the workflow was created) due to
# GitHub's private-repo reusable-workflow access restrictions. This replaces
# it with self-contained local suites that actually run.

name: React Native SDK E2E

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:

permissions:
contents: read
checks: write

jobs:
react-native-sdk-e2e:
name: React Native SDK E2E
uses: frontegg/e2e-system-tests/.github/workflows/react-native-sdk-e2e-reusable.yml@main
with:
repo_ref: ${{ github.event.pull_request.head.sha }}
test_suite: hosted:no-social
secrets: inherit
# ──────────────────────────────────────────────────────────────────────
# Android — UiAutomator on an API 34 emulator
# ──────────────────────────────────────────────────────────────────────
android-e2e:
name: Android E2E
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install JS dependencies
run: |
yarn install --frozen-lockfile
cd example && yarn install --frozen-lockfile

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('example/android/**/*.gradle*', 'example/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-

- name: Build debug APK + androidTest APK
working-directory: example/android
run: |
chmod +x gradlew
./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-api34-google_apis-x86_64-pixel6-v1

- name: Create AVD snapshot if cache missing
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
target: google_apis
profile: pixel_6
cores: 4
ram-size: 4096M
heap-size: 1024M
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: echo "AVD snapshot generated"

- name: Run E2E tests on emulator
uses: reactivecircus/android-emulator-runner@v2
env:
LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }}
LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }}
LOGIN_WRONG_PASSWORD: ${{ secrets.E2E_LOGIN_WRONG_PASSWORD }}
TENANT_NAME_1: ${{ secrets.E2E_TENANT_NAME_1 }}
TENANT_NAME_2: ${{ secrets.E2E_TENANT_NAME_2 }}
GOOGLE_EMAIL: ${{ secrets.E2E_GOOGLE_EMAIL }}
GOOGLE_PASSWORD: ${{ secrets.E2E_GOOGLE_PASSWORD }}
with:
api-level: 34
arch: x86_64
target: google_apis
profile: pixel_6
cores: 4
ram-size: 4096M
heap-size: 1024M
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
working-directory: example/android
script: |
adb shell input keyevent 82
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
sleep 5
./gradlew :app:connectedDebugAndroidTest --no-daemon -Pandroid.testInstrumentationRunnerArguments.LOGIN_EMAIL="$LOGIN_EMAIL" -Pandroid.testInstrumentationRunnerArguments.LOGIN_PASSWORD="$LOGIN_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.LOGIN_WRONG_PASSWORD="$LOGIN_WRONG_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_1="$TENANT_NAME_1" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_2="$TENANT_NAME_2" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_EMAIL="$GOOGLE_EMAIL" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_PASSWORD="$GOOGLE_PASSWORD"

- name: Collect JUnit reports
if: always()
run: |
mkdir -p e2e-artifacts
find example/android -path "*/androidTest-results/*" -name "*.xml" -exec cp -f {} e2e-artifacts/ \; 2>/dev/null || true
find example/android -path "*/build/outputs/*" -name "TEST-*.xml" -exec cp -f {} e2e-artifacts/ \; 2>/dev/null || true

- name: Publish JUnit report
uses: mikepenz/action-junit-report@v5
if: always()
with:
report_paths: e2e-artifacts/*.xml
check_name: Android E2E

- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: android-e2e-results
path: e2e-artifacts/
if-no-files-found: ignore

# ──────────────────────────────────────────────────────────────────────
# iOS — XCUITest on an iPhone simulator (macOS runner)
# Mirrors frontegg-ios-swift's demo-e2e.yml.
# ──────────────────────────────────────────────────────────────────────
ios-e2e:
name: iOS E2E
runs-on: macos-15
timeout-minutes: 60
env:
LOGIN_EMAIL: ${{ secrets.E2E_LOGIN_EMAIL }}
LOGIN_PASSWORD: ${{ secrets.E2E_LOGIN_PASSWORD }}
LOGIN_WRONG_PASSWORD: ${{ secrets.E2E_LOGIN_WRONG_PASSWORD }}
TENANT_NAME_1: ${{ secrets.E2E_TENANT_NAME_1 }}
TENANT_NAME_2: ${{ secrets.E2E_TENANT_NAME_2 }}
GOOGLE_EMAIL: ${{ secrets.E2E_GOOGLE_EMAIL }}
GOOGLE_PASSWORD: ${{ secrets.E2E_GOOGLE_PASSWORD }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install JS dependencies
run: |
yarn install --frozen-lockfile
cd example && yarn install --frozen-lockfile

- name: Install CocoaPods dependencies
working-directory: example/ios
run: pod install --repo-update

- name: Build for testing
working-directory: example/ios
run: |
set -o pipefail
xcodebuild \
build-for-testing \
-workspace ReactNativeExample.xcworkspace \
-scheme ReactNativeExample \
-configuration Release \
-destination "platform=iOS Simulator,name=iPhone 16" \
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
CODE_SIGNING_ALLOWED=NO \
2>&1 | tee "$RUNNER_TEMP/build.log"

- name: Run UI tests
working-directory: example/ios
run: |
set -o pipefail
# Exclude the real social-login test from CI: it drives a real
# ASWebAuthenticationSession that can't complete headlessly on a simulator.
# This mirrors the frontegg-ios-swift SDK E2E, which runs the
# "hosted:no-social" suite and excludes real system-web-auth social login.
xcodebuild \
test-without-building \
-workspace ReactNativeExample.xcworkspace \
-scheme ReactNativeExample \
-configuration Release \
-only-testing:ReactNativeExampleUITests \
-skip-testing:ReactNativeExampleUITests/LoginViaGoogleTest \
-destination "platform=iOS Simulator,name=iPhone 16" \
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
-resultBundlePath "$RUNNER_TEMP/ios-e2e.xcresult" \
-parallel-testing-enabled NO \
2>&1 | tee "$RUNNER_TEMP/test.log"

- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: ios-e2e-results
path: |
${{ runner.temp }}/build.log
${{ runner.temp }}/test.log
${{ runner.temp }}/ios-e2e.xcresult
if-no-files-found: ignore
15 changes: 10 additions & 5 deletions docs/E2E_REACT_NATIVE_TESTS_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,15 @@ React Native’s default accessibility behavior (button `title` → accessibilit
| User profile (email/name on screen) | Yes — `react-native-sdk-user-profile-test.ts` | Asserts user info displayed. |
| Tenant switching | Yes — `react-native-sdk-tenant-switching-test.ts` | Asserts "Active Tenant" text; handles case where tenant UI is absent. |
| Social login (e.g. Google) | Yes — `react-native-sdk-social-login-test.ts` | — |
| Passkeys (Register / Login with Passkeys) | No | Buttons exist in example app; no dedicated e2e yet. |
| Refresh Token button | No | Selector exists; no test that explicitly uses it. |
| Passkeys (Register / Login with Passkeys) | Partial | External suite: no. Local suite (`example/android/app/src/androidTest/.../PasskeysRegisterTest.kt`, `PasskeysLoginTest.kt` + iOS equivalents) adds smoke coverage that verifies the buttons are reachable and the app survives the system credential-manager sheet. Full biometric-backed enrolment still TODO. |
| Refresh Token button | Partial | External suite: no. Local suite (`RefreshTokenTest.kt` / `RefreshTokenTest.swift`) now asserts that tapping the button rotates the access-token suffix while the user stays authenticated. |

So: core auth, MFA, step-up, tenant, and social flows are covered; Passkeys and “Refresh Token” are not.
So: core auth, MFA, step-up, tenant, and social flows are covered by the
external Nightwatch suite. Passkeys and "Refresh Token" now have
developer-runnable coverage in `example/` — see
[`example/E2E_TESTS.md`](../example/E2E_TESTS.md) for the full local suite
(Android UiAutomator + iOS XCUITest) that mirrors the patterns used by
`frontegg-android-kotlin` and `frontegg-ios-swift`.

---

Expand All @@ -97,12 +102,12 @@ So: core auth, MFA, step-up, tenant, and social flows are covered; Passkeys and
### 5.3 Optional: more stable selectors

- Selectors are text-based (button title / static text). They are correct for the current example app but can break if copy or i18n changes.
- **Recommendation (optional):** In the example app, add `testID` (and optionally `accessibilityLabel`) to key elements (e.g. `Login`, `Logout`, `Request Authorization`), and in e2e use `testID`-based or accessibility selectors where the framework supports it, to reduce fragility.
- **Status:** Addressed. `example/src/HomeScreen.tsx` now sets `testID` on every actionable element (`loginButton`, `logoutButton`, `loginWithGoogleButton`, `requestAuthorizeButton`, `refreshTokenButton`, `registerPasskeysButton`, `loginWithPasskeysButton`, `tenantSwitchButton-$tenantId`) and on the diagnostic text nodes (`accessTokenValue`, `userEmailValue`, `activeTenantValue`, …). The new local suite under `example/` uses these; the Nightwatch suite can migrate off text-based selectors at its own pace.

### 5.4 Unused selector

- `ReactNativeSDKUserPageSelectors.REFRESH_TOKEN_BUTTON_*` is defined but the page object has no `clickRefreshToken()`. No test uses it.
- **Recommendation:** Either add a small test that taps “Refresh Token” and asserts token/state (if useful), or remove the selector to avoid dead code.
- **Status:** Addressed locally. `example/android/app/src/androidTest/.../RefreshTokenTest.kt` and `example/ios/ReactNativeExampleUITests/RefreshTokenTest.swift` exercise the button and assert the access-token value changes. The Nightwatch selector can now either be wired up to a similar test or removed.

### 5.5 Step-up test assertion

Expand Down
Loading
Loading