Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

- name: Build for testing
working-directory: example/ios
run: |
set -o pipefail
xcodebuild \
build-for-testing \
-workspace ReactNativeExample.xcworkspace \
-scheme ReactNativeExample \
-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
xcodebuild \
test-without-building \
-workspace ReactNativeExample.xcworkspace \
-scheme ReactNativeExample \
-only-testing:ReactNativeExampleUITests \
-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