UI-Driven Test Infrastructure: Replace RCTTest with XCUITest/UIAutomator#459
Merged
wmathurin merged 98 commits intoJun 5, 2026
Merged
Conversation
- Add test/testRunner.js: registerSuite, registerTest, testDone, runTest - Add test/TestApp.js: renders test list with run buttons and inline results - Update all test files to use testRunner imports - Update index.js entry points to render TestApp
- Switch from file:.. to link:.. to avoid recursive yarn copy - Add watchFolders to metro config so Metro resolves symlinked test/ - Remove test/ from .npmignore (needed by Metro)
The app target needs a proper RCTAppDelegate that loads the React bundle. The old bare UIResponder AppDelegate showed nothing because it never created a React surface.
Uses SalesforceReactSDKManager, AuthHelper.loginIfRequired, and RCTReactNativeFactory — matching the ReactNativeTemplate. NOTE: Xcode project needs manual update to add AppDelegate.swift and remove AppDelegate.h, AppDelegate.m, main.m references.
iOS: - Remove RCTTest/ directory (custom test runner pod) - Remove React-RCTTest from Podfile - Remove old XCTest files (ReactTestCase, React*Tests.m) - Podfile: all pods in app target, test target inherits Android: - Remove SalesforceTestBridge, ReactNativeTestHost, ReactTestActivity, ReactActivityTestDelegate, TestResult, SalesforceReactTestPackage, DebugSalesforceReactPackage - Remove old instrumentation test classes - Rewrite SalesforceReactTestApp.kt to match template pattern - Add MainActivity.kt (standard SalesforceReactActivity) - Update AndroidManifest for new MainActivity JS: - Remove src/react.force.test.tsx (replaced by test/testRunner.js) - Remove forceTest export from src/index.ts
Copied the template's iOS project structure (AppDelegate.swift, Podfile, xcodeproj, bootconfig, Info.plist) and renamed to SalesforceReactTestApp. Uses use_frameworks! :static for Swift module imports.
…nfig/Info.plist - bundleURL() always loads index.ios.bundle from app bundle - bootconfig.plist and Info.plist checked in with placeholders - Fill in values per docs/TESTING_CREDENTIALS.md for local testing
…cursion) - Switch back from link:.. to file:.. (link caused dep resolution failures) - .npmignore now excludes node_modules/ which prevents recursive copy - Simplified metro configs (no watchFolders needed — test/ is in the copy)
…ctActivityDelegate
Having react and react-native in both dependencies and peerDependencies caused yarn to install a nested copy under node_modules/react-native-force/ node_modules/react, breaking React hooks (useState returns null).
- New resetSyncManager in react.force.mobilesync.ts - Added to TurboModule spec (NativeSFMobileSyncReactBridge.ts) - iOS bridge: calls [SFMobileSyncSyncManager removeSharedInstances] - Android bridge: calls SyncManager.reset() - Removed test/alltests.js (no longer used, TestApp imports directly)
iOS: - BaseReactNativeTest.swift: launches app, waits for testList, runTest() - ReactHarnessTests, ReactOAuthTests, ReactNetTests, ReactSmartStoreTests, ReactMobileSyncTests Android: - BaseReactNativeTest.kt: launches activity, waits for testList, runTest() - ReactHarnessTest, ReactOAuthTest, ReactNetTest, ReactSmartStoreTest, ReactMobileSyncTest
…testID TouchableOpacity with testID doesn't map to XCUIElement Button type. Use generic element query with identifier matching instead.
iOS: - BaseReactNativeTest reads test_credentials.json from test bundle - Passes as -creds launch argument (SDK instant login in DEBUG builds) Android: - BaseReactNativeTest launches TestAuthenticationActivity with creds - Reads test_credentials.json from assets - TestAuthenticationActivity registered in AndroidManifest Both platforms authenticate without login screen before running tests.
- prepareandroid.js/prepareios.js: clean sibling node_modules before install, remove nested react-native-force/node_modules after install (prevents yarn v1 file: protocol from creating duplicate React) - iOS: BaseReactNativeTest reads test_credentials.json from bundle, passes as -creds launch argument - Android: BaseReactNativeTest launches TestAuthenticationActivity with credentials from assets, waits for test list - Removed TestAuthenticationActivity from manifest (already in SDK)
prepareios.js now copies to both ios/ (app) and ios/SalesforceReactTests/ (UI test bundle resources). Both locations gitignored.
Moves permissionRule and activityRule from @rule to @ClassRule in the companion object. This ensures the activity launches only once for the entire test class, not once per test method. Since we're doing batch execution with cached results, we don't need the activity to restart for each individual test method.
Tests now default to individual execution (ideal for Android Studio/Xcode), with opt-in batch execution for CI via system property/environment variable. Individual mode (default): - Single test runs in isolation - Fast iteration during development - No activity flashing between tests (Android @ClassRule) Batch mode (opt-in via useBatchExecution=true): - Entire suite runs via "Run All" button - Results cached and reused - Much faster for CI Android: -DuseBatchExecution=true iOS: useBatchExecution=true environment variable Added README.md files documenting both modes.
Reverted @ClassRule back to @rule - activity launches per test. Removed batch execution logic - all tests run individually now. Android tests are fast enough with ActivityScenarioRule that batch execution optimization isn't needed. Simplified BaseReactNativeTest: - Single testTimeoutMs property (no suite vs individual distinction) - runTest() directly executes the test (no caching) - Removed suiteName and testNames requirements from subclasses iOS still supports both batch and individual modes via useBatchExecution flag.
Removed batch execution logic - all tests run individually now. Simplified BaseReactNativeTest: - Single testTimeoutSeconds property - runTest() directly executes the test (no caching) - Removed suiteName and testNames requirements from subclasses Both iOS and Android now use straightforward individual test execution.
- Check if button exists before attempting scroll - Set maxSearchSwipes to 20 to allow scrolling further - Log warnings when scroll fails - Better error message when button not found This should fix testGetSyncStatusDeleteSync not being reachable.
Added contentContainerStyle with paddingBottom: 100 to ensure tests at the bottom of the list (like testGetSyncStatusDeleteSync) are reachable by scrolling on Android.
- iOS: select correct Xcode before pod install to avoid SwiftBridging redefinition error when two Xcode versions are present on the runner - Android: double test timeouts (base: 15s→30s, MobileSync: 60s→120s) for Firebase Test Lab ARM devices which are slower than local emulators
iOS: remove testLogout and testAuthenticate which don't exist in oauth.test.js Android: add testGetAuthCredentials which was missing from ReactOAuthTest
Android (reusable-android-workflow.yaml): - Add --use-orchestrator + clearPackageData=true to prevent cascade failures when one test crashes the instrumentation process - Add --num-flaky-test-attempts=1 for one automatic retry on transient failures - Increase --timeout from 10m to 20m (MobileSync needs ~10m alone) - Add explicit --project mobile-apps-firebase-test - Route github context vars through env: per security best practice - Fix results copy to use test_result_1.xml (not wildcard) iOS (reusable-workflow.yaml): - Switch from mxcl/xcodebuild test run to action:none + direct xcodebuild test enabling -retry-tests-on-failure (mxcl does not support it) - Add explicit simulator resolver via xcrun simctl list with diagnostic output - Add -retry-tests-on-failure for one automatic retry on flaky tests - Add xcresult bundle verify step for clear failure when no tests ran - Set job_summary:true unconditionally (was failure-only, suppressed pass summaries) Validated: YAML parse OK, actionlint clean, zizmor 0 findings
Contributor
Author
|
Locally all the tests are passing. |
…ition on Xcode 26
…Sync 180s, Firebase timeout 30m, scroll any scrollable
brandonpage
reviewed
Jun 4, 2026
| </activity> | ||
| </application> | ||
|
|
||
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
brandonpage
approved these changes
Jun 4, 2026
brandonpage
left a comment
Contributor
There was a problem hiding this comment.
Incredible work. This is awesome.
…t 90s, Firebase 40m
… starts is too slow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the fragile RCTTest-based test infrastructure with a UI-driven approach using standard native UI testing frameworks (XCUITest on iOS, UIAutomator on Android). Tests are now driven by tapping buttons in a standard React Native app that displays test results inline.
Motivation
The current test infrastructure relies on platform-specific test bridges (
RCTTestModuleon iOS,SalesforceTestBridgeon Android) that hook into React Native internals. React Native 0.84 introduced precompiled binaries which broke our iOS test infrastructure — thepodspec_sourcesmethod used to extract RCTTest source code from the React Native pod no longer works with precompiled frameworks. This blocks us from upgrading beyond RN 0.83.Previous RN upgrades also required test infrastructure fixes (e.g., PlatformConstants changes in 0.83), but RN 0.84 represents a fundamental breaking point: we cannot extract test infrastructure source from precompiled binaries.
Solution
New Architecture
Key Benefits:
What Changed
Removed (Old Infrastructure)
src/react.force.test.ts- Test bridge module (no longer needed)iosTests/RCTTest/- Custom RCTTestRunner extraction (incompatible with precompiled binaries)ReactNativeTestHost.javaReactTestActivity.javaSalesforceTestBridge.javaTestResult.javaAdded (New Infrastructure)
test/testRunner.js- Pure JS test runner (no native bridge)test/TestApp.js- Standard RN app with test list UIiosTests/ios/SalesforceReactTests/- XCUITest suite (5 test classes, 35 tests)androidTests/android/app/src/androidTest/- UIAutomator suite (5 test classes, 35 tests)react.force.mobilesync.resetSyncManager()- Enables JS-side test cleanupTest Execution Flow
testID/accessibilityLabel(e.g.,run_testGetApiVersion)result_testGetApiVersion_passorresult_testGetApiVersion_fail)error_testGetApiVersion) and assertsTest Results
iOS: All 35 tests passing via XCUITest on both iOS 18 and iOS 26
Android: All 35 tests passing via UIAutomator locally. CI integration in progress (Firebase Test Lab).
Both platforms use instant login (credentials via launch arguments) to bypass OAuth UI.
Test Infrastructure Details
iOS:
BaseReactNativeTestprovides common test infrastructuretestTimeoutSecondsfor longer tests (Net: 30s, MobileSync: 60s, default: 15s)class func setUp()-creds <json>)Android:
BaseReactNativeTestuses@RulewithActivityScenarioRulepattern (followsAuthFlowTestin Android SDK)TestAuthenticationActivitywith credentials intent extratestTimeoutMsfor longer tests (Net: 90_000ms, MobileSync: 180_000ms, default: 60_000ms)Dependencies
This PR depends on instant login support in the native SDKs:
Files Changed
Core Changes (~80 commits):
See commit history for detailed breakdown.
Testing
test_template.sh)Before Merging
iosTests/package.json: Updated togit+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#devandroidTests/package.json: Updated togit+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#devImpact
This PR unblocks React Native 0.84+ upgrades by eliminating our dependency on RCTTest source extraction. Future RN upgrades will only require version bumps in package.json, not test infrastructure rewrites.
Related Work
specs/W-22797324-rn-ui-driven-test-infrastructure/spec.mdspecs/W-22797324-rn-ui-driven-test-infrastructure/progress.md