How to Test Onboarding Flow on React Native (Complete Guide)
Testing the onboarding flow in‑based is a critical quality gate. onboarding flow in a React Native app is the first impression users get, and any friction here directly impacts activation, retention,
How to Test Onboarding Flow on React Native (Complete Guide)
Testing the onboarding flow in‑based is a critical quality gate. onboarding flow in a React Native app is the first impression users get, and any friction here directly impacts activation, retention, and downstream metrics. A broken onboarding screen can cause users to abandon the app before they even see core value, leading to measurable drops in DAU and increased CAC. This guide walks you through why onboarding matters, what typically fails in production, how to build a exhaustive test matrix, execute it manually, automate it with React‑native‑specific tools, and finally how autonomous, persona‑driven exploration (such as what SUSA offers) surfaces bugs that scripted tests never anticipate.
How to Test Onboarding Flow on React Native (Complete Guide)
Why Onboarding Flow Testing Matters in React Native Apps
Onboarding is not a disposable tutorial; it is often the gatekeeper to account creation, permission granting, and initial data sync. In React Native, the bridge between JavaScript and native modules introduces unique failure points: asynchronous native calls, race conditions between the JS thread and UI thread, and platform‑specific UI components that behave differently on iOS vs Android. If any of these stall, the user sees a blank screen, a spinner that never stops, or a modal that blocks progress. Empirical data from A/B tests shows that a 2‑second delay in onboarding completion can cut conversion by up to 15 %. Therefore, treating onboarding as a first‑class test target is not optional; it is a revenue‑protecting activity.
Common Onboarding Flow Breakages in Production (React Native Specific)
- Async storage initialization race – The app reads from AsyncStorage before the native module finishes loading, causing undefined user‑preference values and skipping welcome screens.
- Improper handling of
react-native-splash-screen– The splash screen hides before the root navigator mounts, leaving a flash of unstyled content. - Permission dialog timing – On Android, requesting camera or location permissions too early triggers the system to deny them automatically if the app is not in the foreground.
- Navigator state corruption – Deep linking into an onboarding screen while a modal is open can push duplicate routes, breaking the back‑stack.
- Accessibility label loss – Custom components built with
create-react-native-componentsometimes dropaccessibilityLabelprops when bundled with Hermes, causing TalkBack/VoiceOver to announce “button” instead of the intended text. - Privacy leakage – Logging user‑entered email or phone number to a remote analytics endpoint before user consent is given, violating GDPR/CCPA.
- Orientation lock conflict – The onboarding screen forces portrait, but the device is in landscape and the app does not handle orientation change, resulting in clipped UI.
Each of these issues surfaces only under specific device, OS version, or network conditions, making them elusive to scripted tests that follow a single happy path.
How to Test Onboarding Flow on React Native (Complete Guide)
Building a Comprehensive Test Matrix for React Native Onboarding
A test matrix captures every dimension you need to verify. Below is a master table that you can copy into a spreadsheet or test‑management tool. Each row includes a unique ID, a concise description, the expected outcome, priority (P0‑blocker, P1‑high, P2‑medium), and the layer(s) where the test can be applied (unit, integration, e2e, manual).
| TC‑ID | Description | Expected Result | Priority | Test Layer |
|---|---|---|---|---|
| ONB‑001 | Launch app from cold start; splash screen shows brand logo for ≤1.5 s, then fades out. | Splash screen disappears, main onboarding screen appears. | P0 | Manual, E2E |
| ONB‑002 | Tap “Get Started” on welcome screen; navigation moves to email input screen. | Email input screen is focused, keyboard appears. | P0 | Unit, Integration |
| ONB‑003 | Enter valid email format (e.g., user@domain.com); press “Next”. | Advances to password screen; email stored in AsyncStorage. | P0 | Unit, Integration |
| ONB‑004 | Enter invalid email (missing @); press “Next”. | Inline error “Please enter a valid email” appears; focus remains on email field. | P1 | Unit, Integration |
| ONB‑005 | Leave email blank; press “Next”. | Error “Email is required” displays; no navigation. | P1 | Unit, Integration |
| ONB‑006 | Enter password <6 chars; press “Next”. | Error “Password must be at least 6 characters” shows. | P1 | Unit, Integration |
| ONB‑007 | Enter password ≥6 chars; press “Next”. | Navigates to permission request screen. | P0 | Unit, Integration |
| ONB‑008 | Deny camera permission on Android; press “Continue”. | App shows explanatory toast, remains on permission screen; does not crash. | P1 | Manual, E2E |
| ONB‑009 | Grant camera permission; press “Continue”. | Proceeds to profile photo screen; camera launches successfully. | P0 | Manual, E2E |
| ONB‑010 | Rotate device to landscape while on email screen; UI re‑flows without clipping. | All input fields and buttons fully visible, no horizontal scroll. | P2 | Manual, E2E |
| ONB‑011 | Simulate slow 3G network; fetch remote config for onboarding copy. | Local fallback copy displayed; no blank screen; retry after 5 s succeeds. | P1 | Integration, E2E |
| ONB‑012 | Enter email that already exists in backend; submit. | Server returns 409; UI shows “Account already exists, try logging in”. | P1 | Integration |
| ONB‑013 | Perform a long press on the “Terms of Service” link; context menu appears with “Copy link”. | Link copied to clipboard; no crash. | P2 | Manual |
| ONB‑014 | Enable TalkBack (Android) or VoiceOver (iOS); navigate via swipe. | Each focusable element announces correct label; hints describe action. | P0 | Manual, Accessibility |
| ONB‑015 | Attempt to paste >100 characters into email field; input rejects excess. | Field truncates to max length; no error toast. | P2 | Unit |
| ONB‑016 | Disable biometric auth in device settings; onboarding screen that offers fingerprint login shows fallback button. | Fallback “Continue with email” button visible and functional. | P2 | Manual |
| ONB‑017 | Run app with locale set to right‑to‑left language (Arabic); layout mirrors correctly. | All text right‑aligned, icons mirrored, no overlapping. | P2 | Manual, E2E |
| ONB‑018 | Simulate low memory warning (via adb shell am send-trim-memory); onboarding continues without OOM crash. | App stays responsive; memory usage stays below threshold. | P1 | Manual, Stress |
| ONB‑019 | Attempt to register with a password containing only spaces; trimmed validation triggers error. | Error “Password cannot be only whitespace” appears. | P1 | Unit |
| ONB‑020 | After successful onboarding, pressing device back returns to exit prompt, not to a previous onboarding screen. | Exit confirmation dialog appears; selecting “Cancel” returns to home screen. | P0 | Manual, E2E |
#### Happy Path Scenarios
Happy‑path tests (ONB‑001, ONB‑002, ONB‑003, ONB‑007, ONB‑009, ONB‑020) confirm that a user with correct data and granted permissions can finish onboarding without obstruction. These are the baseline for any automated suite.
#### Error and Validation Paths
Validation tests (ONB‑004‑ONB‑006, ONB‑008, ONB‑012, ONB‑015, ONB‑019) ensure the UI reacts correctly to malformed input, missing permissions, and server‑side conflicts. They guard against soft‑failures where the app lets bad data through or crashes on error handling.
#### Edge Cases (network, permissions, orientation, etc.)
Edge‑case rows (ONB‑010, ONB‑011, ONB‑013, ONB‑014, ONB‑016, ONB‑017, ONB‑018) cover device state changes, network flakiness, accessibility, and locale specifics. These are often missed in unit tests because they involve the interaction layer between JS and native modules.
#### Accessibility (WCAG) Checks
Rows ONB‑014 and ONB‑017 directly test WCAG 2.1 AA criteria: labels, contrast, touch target size, and RTL support. Automated accessibility audits (e.g., using @axe-core/react-native) can be added as a supplemental step.
#### Security and Privacy Considerations
ONB‑012 (duplicate‑email detection) prevents account enumeration, while ONB‑006 and ONB‑019 ensure password rules are enforced client‑side before transmission. Any analytics or logging calls must be gated behind user consent; this can be verified by inspecting network traffic in the E2E layer.
Manual Testing Approach Step‑by‑Step
Manual testing remains indispensable for exploratory checks, especially for accessibility and gesture‑based flows. Follow this procedure to execute the matrix reliably.
#### Setting Up Device/Emulator
- Provision a matrix of devices – At minimum: Android API 21 (emulator), Android API 33 (physical), iOS 15 (simulator), iOS 17 (physical).
- Install the debug build – Use
npx react-native run-android --variant=debugandnpx react-native run-ios --configuration=Debug. - Enable developer options – On Android, turn on “Show touches”, “Pointer location”, and “Disable HW overlays” to surface rendering glitches. On iOS, enable “Color Blended Layers” in the simulator.
- Prepare network throttling – Use Android’s
tccommand or iOS Network Link Conditioner to simulate 3G, LTE, and offline states. - Load test data – Seed AsyncStorage with a known empty state via
adb shell am broadcast -a com.example.RESET_STORAGEor a similar custom intent.
#### Executing the Matrix
- Print the matrix or load it into a test‑rail tool.
- For each TC‑ID, follow the exact steps, noting the actual result, any observed latency, and screenshots/video if a deviation occurs.
- Use a consistent naming convention for artifacts:
ONB-004_AndroidAPI33_fail.jpg. - If a test fails, create a defect ticket with: device model, OS version, build number, steps to reproduce, expected vs actual, and severity derived from the priority column.
#### Logging Observations and Defects
- Capture console logs with
react-native log-androidorxcrun simctl spawn booted log collect. - For UI freezes, record a short video using
scrcpy(Android) or QuickTime (iOS). - After the session, aggregate failures in a spreadsheet, calculate the pass‑rate per priority, and feed the data back into the test‑automation backlog (e.g., convert frequent manual failures into Detox tests).
Automated Testing Strategies for React Native Onboarding
Automation provides repeatability and regression safety. The React Native ecosystem offers several layers; combine them for maximal coverage.
#### Unit Tests with Jest & React Native Testing Library
Unit tests isolate pure JavaScript logic: validation functions, navigation event handlers, and AsyncStorage wrappers. They run in under a second and are ideal for CI gating.
// __tests__/onboarding/emailValidation.test.js
import { isValidEmail } from '../../src/utils/validation';
describe('Email validation utility', () => {
test('accepts correctly formatted email', () => {
expect(isValidEmail('jane.doe@example.com')).toBe(true);
});
test('rejects missing @ symbol', () => {
expect(isValidEmail('janedoeexample.com')).toBe(false);
});
test('rejects empty string', () => {
expect(isValidEmail('')).toBe(false);
});
});
Run with npm test or yarn test. Aim for >90 % coverage on validation and navigation‑helper files.
#### Integration Tests with Detox
Detox drives the actual native UI while synchronizing with the JavaScript thread, making it perfect for flow‑based checks like ONB‑001 through ONB‑020.
// e2e/onboarding.init.js
import { device, element, by, expect } from 'detox';
describe('Onboarding Flow – Happy Path', () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true, delete: true });
});
test('completes onboarding with valid data', async () => {
// Splash screen disappearance
await expect(element(by.id('splash-screen'))).toNotExist();
// Welcome screen
await expect(element(by.id('welcome-screen'))).toBeVisible();
await element(by.id('btn-get-started')).tap();
// Email screen
await expect(element(by.id('email-input'))).toBeVisible();
await element(by.id('email-input')).typeText('tester@example.com');
await element(by.id('btn-next')).tap();
// Password screen
await expect(element(by.id('password-input'))).toBeVisible();
await element(by.id('password-input')).typeText('Secure123');
await element(by.id('btn-next')).tap();
// Permission screen (mock permission grant)
await device.sendToApp({ type: 'PERMISSION_GRANTED', name: 'camera' });
await expect(element(by.id('permission-screen'))).toNotExist();
await expect(element(by.id('profile-photo-screen'))).toBeVisible();
// Final screen
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.id('welcome-message'))).toHaveText('Welcome, tester!');
});
});
Detox configuration (detox.config.js) should include separate configurations for Android emulator and iOS simulator, with testRunner: 'jest' and apps: { android.debug: { binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk' }, ios.debug: { binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/app.app' } }.
Run the suite with detox test -c android.emu.debug or detox test -c ios.sim.debug.
#### End‑to‑End UI Tests with Appium
Appium is useful when you need to test hybrid components, WebViews, or when you want to reuse the same scripts across pure native and React Native builds. It also facilitates cross‑platform cloud testing (Sauce Labs, BrowserStack).
// OnboardingTest.java
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
public class OnboardingTest {
private AppiumDriver<MobileElement> driver;
@Before
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("deviceName", "Pixel_4_API_33");
caps.setCapability("app", System.getenv("APK_PATH"));
caps.setCapability("automationName", "UiAutomator2");
driver = new AppiumDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), caps);
}
@Test
public void testOnboardingHappyPath() {
// Wait for splash to disappear
new WebDriverWait(driver, 20)
.until(ExpectedConditions.invisibilityOfElementLocated(By.id("splash-screen")));
// Welcome screen
driver.findElement(By.id("btn-get-started")).click();
// Email
MobileElement email = driver.findElement(By.id("email-input"));
email.sendKeys("user@example.com");
driver.findElement(By.id("btn-next")).click();
// Password
MobileElement pwd = driver.findElement(By.id("password-input"));
pwd.sendKeys("Strong!Pass456");
driver.findElement(By.id("btn-next")).click();
// Grant camera permission (via ADB)
driver.executeScript("mobile: shell", ImmutableMap.of(
"command", "pm",
"args", Arrays.asList("grant", "com.example.app", "android.permission.CAMERA")
));
driver.findElement(By.id("btn-continue")).click();
// Verify home screen
Assert.assertTrue(
driver.findElement(By.id("home-screen")).isDisplayed(),
"Home screen not reached"
);
}
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
Execute with Maven: mvn test. Ensure the Appium server is running (appium) and the Android SDK platform‑tools are in PATH.
#### Using SUSA for Autonomous Exploration (mention SUSA)
SUSA extends the above automated layers by exploring the app without pre‑written scripts. After you build a debug APK (./gradlew assembleDebug) or point SUSA at a dev server URL, you can launch an autonomous session:
# Install the agent
pip install susatest-agent
# Run a 30‑minute exploratory session on Android
susatest run \
--apk ./android/app/build/outputs/apk/debug/app-debug.apk \
--device emulator-5554 \
--personas curious impatient novice elderly accessibility power_user \
--max-time 1800 \
--output ./reports/onboarding_susa.json
SUSA will:
- Launch the app and begin tapping, scrolling, and typing according to each persona’s behavior model.
- Detect crashes, ANRs, dead buttons, WCAG violations, and privacy leaks (e.g., logging of PII before consent).
- Track flow completion (login, signup, onboarding) and assign PASS/FAIL based on whether the defined success criteria (reaching the home screen) are met.
- After the run, it generates regression scripts: Appium for Android and Playwright for Web (if you have a web counterpart).
- Cross‑session learning ensures that previously seen dead ends (e.g., a permission‑denial toast that blocks progress) are avoided in subsequent runs, making each session smarter.
You can then import the generated Appium script into your CI pipeline as a safety net for regressions that manual testers might miss.
Code Examples: Jest Unit Test for Onboarding Screens
Beyond validation utilities, you often want to assert that a screen renders the correct elements given certain props. Below is a test for the EmailScreen component using React Native Testing Library.
// __tests__/screens/EmailScreen.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import EmailScreen from '../../src/screens/EmailScreen';
test('shows error when email is invalid and enables button on valid input', async () => {
const { getByPlaceholderText, getByText, getByLabelText } = render(<EmailScreen />);
const emailInput = getByPlaceholderText('Enter your email');
const nextButton = getByLabelText('Next');
const errorText = getByText(/please enter a valid email/i);
// Initially button disabled
expect(nextButton).toHaveProp('disabled', true);
// Type invalid email
fireEvent.changeText(emailInput, 'invalidemail');
await waitFor(() => expect(errorText).toBeVisible());
expect(nextButton).toHaveProp('disabled', true);
// Type valid email
fireEvent.changeText(emailInput, 'valid@example.com');
await waitFor(() => expect(errorText).not.toBeVisible());
expect(nextButton).toHaveProp('disabled', false);
// Press next navigates (mock navigation)
fireEvent.press(nextButton);
// Assuming navigation prop is mocked; assert that navigate was called with 'password'
// navigateMock.expectToHaveBeenCalledWith('password');
});
Run with jest. This test catches regressions where the error message styling changes or the button state logic breaks.
Leveraging Persona‑Driven Autonomous Exploration to Find Hidden Bugs
Scripted tests verify that the app behaves as the designer imagined. Real users, however, deviate from the happy path in ways that are hard to anticipate. Persona‑driven exploration injects those variations automatically.
#### How SUSA Simulates Different User Personas
Each persona is defined by a stochastic policy:
- Curious – taps every visible element, explores long‑press menus, tries to type in non‑input fields.
- Impatient – rapidly taps buttons, often double‑taps, skips optional steps, and aborts if a screen takes >2 s to render.
- Novice – prefers default options, avoids advanced settings, and relies heavily on placeholders and hints.
- Elderly – uses larger touch targets, moves slowly, may trigger accessibility features like font scaling.
- Accessibility – enables TalkBack/VoiceOver, navigates via swipe, and expects accurate labels.
- Power user – uses gestures (swipe‑left to delete, long‑press for context), attempts to paste from clipboard, and tries edge‑case inputs like emojis or Unicode.
SUSA’s engine runs these policies in parallel, collecting telemetry (tap coordinates, timestamps, native bridge calls). When a crash or ANR occurs, it captures a stack trace and a screen‑recording snippet, making triage straightforward.
#### Examples of Bugs Found Only via Personas
During a recent SUSA run on a fintech React Native app, the following issues surfaced:
- Curious persona long‑pressed the “Terms” label, which triggered a hidden debug modal that leaked internal API keys.
- Impatient persona double‑tapped the “Next” button on the password screen, causing two navigation events that pushed the home screen twice, resulting in a blank screen on Android due to duplicated navigation state.
- Elderly persona with system font scale set to 200 % caused the email input’s placeholder to overflow, truncating the “@” symbol and making the field unusable.
- Accessibility persona using TalkBack reported that the “Show password” toggle lacked an
accessibilityLabel, so the screen reader announced “button” instead of “Show password”. - Power user pasted a 10‑character string containing a zero‑width joiner (ZWJ) into the email field; the validation regex allowed it, but the backend rejected it with a 500 error, exposing a server‑side injection point.
These defects would have remained invisible to a script that only followed the linear happy‑path flow. By integrating SUSA’s exploratory sessions into your weekly regression cadence, you gain a safety net that catches regression‑prone UI quirks, accessibility slips, and even security oversights before they reach production.
Checklist for Onboarding Flow Testing in React Native
Use this concise list before each release candidate. Tick each item; if any item is unchecked, treat the release as blocked until resolved.
- [ ] Splash screen disappears within 1.5 s on all device profiles.
- [ ] Welcome screen “Get Started” button is visible, enabled, and navigates to email screen.
- [ ] Email validation shows inline errors for missing @, empty string, and overly long input; disables next button accordingly.
- [ ] Password validation enforces minimum length, rejects whitespace‑only input, and provides clear error text.
- [ ] Permission dialogs (camera, location, notifications) appear only after the user taps the relevant CTA; denial shows a helpful toast and does not crash.
- [ ] Network failure (simulated 3G or offline) displays fallback copy and offers a retry mechanism without freezing the UI.
- [ ] Orientation change (portrait ↔ landscape) maintains full visibility of all inputs and buttons; no clipping or overlapping.
- [ ] Accessibility audit passes: every focusable element has a descriptive label, touch targets ≥48 dp, and contrast ratio ≥4.5:1 for AA text.
- [ ] RTL layout mirrors correctly when device language set to Arabic or Hebrew; no overlapping or misaligned icons.
- [ ] No PII (email, password, phone) is logged to analytics or crash reporting before explicit user consent is given.
- [ ] Back button behavior: from any onboarding screen, hardware back either exits to a confirmation prompt or returns to the previous onboarding step; never navigates to a screen outside the flow.
- [ ] After successful onboarding, the home screen loads and displays personalized welcome message using the supplied email or username.
- [ ] Crash‑free: zero native crashes or ANRs recorded during automated Detox runs and SUSA exploratory sessions across all personas.
- [ ] Regression scripts generated from the latest Susa run pass in CI without flakiness.
Closing Takeaways and Next Steps
Testing the onboarding flow in a React Native application is not a checkbox activity; it is a continuous, multi‑layered effort that spans unit validation, integration navigation, end‑to‑end UI verification, and exploratory, persona‑driven discovery. Start by codifying the pure‑JavaScript logic (validation, navigation helpers) with Jest and React Native Testing Library. Layer Detox tests on top to assert that the actual screens transition correctly under various input and permission scenarios. Complement these with Appium scripts when you need to verify hybrid components or run cross‑platform cloud tests. Finally, inject autonomous exploration via a tool like SUSA to surface the edge cases that only real‑world, varied user behavior can uncover—crashes triggered by long‑presses, accessibility label omissions, layout breaks under font scaling, or premature analytics leaks.
Maintain a living test matrix (like the one presented) and treat it as the source of truth for both manual and automated efforts. Feed failures back into the matrix, prioritize fixes by their impact on activation and retention, and close the loop by regenerating automated scripts from Susa’s discoveries. By doing so, you transform onboarding from a potential leak point into a verified, welcoming gateway that reliably converts first‑time users into engaged customers. Happy testing.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free