How to Test Permission Dialogs on React Native (Complete Guide)
How to Test Permission Dialogs on React Native (Complete Guide)
How to Test Permission Dialogs on React Native (Complete Guide)
Testing permission dialogs is a critical part of ensuring that a React Native app behaves correctly when it accesses device capabilities such as the camera, microphone, location, or contacts. If the dialog handling is flawed, users may encounter crashes, silent failures, or unexpected UI states that erode trust and lead to negative reviews. This guide walks you through why permission dialogs matter, provides a comprehensive test matrix, shows manual and automated techniques, shares concrete code snippets, and explains how autonomous, persona‑driven exploration can surface bugs that scripted tests often miss.
How to Test Permission Dialogs on React Native (Complete Guide): Why It Matters
Permission dialogs are the gatekeepers between your app and sensitive device resources. On both Android and iOS, the operating system presents a modal that asks the user to grant or deny access. When your React Native code calls a permission‑requesting API—such as PermissionsAndroid.request or the expo‑permissions wrapper—the native bridge shows this dialog, and your JavaScript layer receives a callback with the result. If you assume the call always succeeds, you risk trying to read from a camera that the user blocked, which can throw exceptions or return empty data. Moreover, Android 13 introduced a new runtime permission model for near‑by Wi‑Fi devices, and iOS 17 added provisional authorization for location, meaning the same code path can produce different outcomes depending on OS version. In production, missing or mishandled dialog responses have led to crashes that appear only after a user upgrades their OS, to privacy complaints when an app continues to request a permission after a “Never ask again” selection, and to accessibility failures when the dialog is not announced correctly by screen readers. By systematically testing every possible user interaction with these dialogs, you catch those issues before they reach users and you build confidence that your app respects user choices and platform guidelines.
How to Test Permission Dialogs on React Native (Complete Guide): Test Matrix
A solid test matrix covers the happy path, error paths, edge cases, accessibility considerations, and security/privacy implications. Below is a table that enumerates the scenarios you should verify for each permission type (camera, microphone, location, contacts, notifications, etc.). Mark each cell with ✅ when the scenario passes and ❌ when it fails; use the table as a living checklist during manual or automated runs.
| Permission Type | Scenario | Expected Behavior | Test Notes |
|---|---|---|---|
| Camera | User taps Allow | Camera stream starts, UI updates accordingly | Verify that the preview renders and no error is thrown |
| Camera | User taps Deny | App shows a fallback UI or disables camera‑dependent features | Ensure no crash and that the app does not repeatedly request |
| Camera | User selects Never ask again (Android) or Don’t Allow (iOS) | Subsequent requests return immediately denied without showing dialog | Check that the permission status is persisted across app restarts |
| Camera | Dialog appears while another modal is open (e.g., alert) | OS stacks dialogs; app receives result after the modal dismisses | Test on Android API 30+ where dialogs can be overlapped |
| Camera | Request made from a background service or headless JS | On Android, request is postponed until foreground; on iOS, request fails | Validate that your code handles the postponed case gracefully |
| Camera | User changes permission via Settings while app is running | Next request reflects the new setting without needing a restart | Use adb shell cmd or Settings app to toggle and observe |
| Camera | Accessibility: TalkBack/VoiceOver reads the dialog | Announces title, options, and default action correctly | Verify with accessibility scanner and manual listening |
| Camera | Deny then quickly re‑request (rapid‑tap) | Second request shows dialog again (unless Never ask again) | Ensure no UI glitch or duplicate dialog |
| Location (Android 13+) | Request for ACCESS_FINE_LOCATION while ACCESS_COARSE_LOCATION already granted | System may upgrade to fine location if user consents | Test both upgrade and downgrade paths |
| Location (iOS 17+) | Request for WhenInUse triggers provisional authorization | App receives limited accuracy until user upgrades to full | Confirm that your app degrades gracefully |
| Notifications | User taps Allow | App receives device token and can register with push service | Check token receipt and backend registration |
| Notifications | User taps Don’t Allow | Subsequent requests return denied immediately | Ensure no loop of request calls |
| Contacts | User grants access, then revokes via Settings mid‑session | Next read returns empty array; no crash | Verify that your code checks status before each read |
| Multiple permissions (camera + microphone) | User allows one, denies the other | App receives partial grant object; features depending on denied permission are disabled | Test that UI reflects mixed state correctly |
| Permission rationale missing (Android) | Request without providing a rationale when required (targetSdk >= 29) | System may show a rationale dialog automatically; app may appear confusing | Ensure you pass a rationale string or handle missing gracefully |
| Security/privacy | App logs permission results to analytics without masking | Potential leakage of denial patterns | Review analytics payloads for PII |
Happy Path Validation
The happy path confirms that when a user grants permission, the intended functionality works without error. For each permission, invoke the request API, assert that the callback returns a granted status, and then execute the feature that depends on that permission (e.g., start camera preview, read location, open contacts picker). Record any latency between the dialog dismissal and the feature activation; excessive delays can indicate a race condition where the native module hasn’t yet updated its internal state.
Denial and “Never Ask Again” Paths
When a user denies a permission, your app should degrade gracefully. This means hiding UI elements that require the permission, showing an informative message, and avoiding repeated requests that could annoy the user. The “Never ask again” (Android) or “Don’t Allow” (iOS) selection is especially important because subsequent calls to the permission API return immediately with a denied status, bypassing the dialog. Your test must verify that the app does not enter an infinite request loop and that any cached permission state is updated correctly.
Edge Cases Involving System State
Permissions can be altered while your app is in the background, or while another modal is displayed. Test scenarios where the user opens Settings, toggles a permission, and then returns to your app. Also test situations where a system alert (e.g., low‑battery warning) appears over the permission dialog; the OS will stack the dialogs, and your app should still receive the correct result once the user interacts with both. On Android, be aware of the new “approximate location” permission introduced in Android 12, which can be granted alongside or instead of fine location.
Accessibility and Localization
Accessibility testing ensures that TalkBack (Android) and VoiceOver (iOS) announce the dialog title, the two action buttons, and any explanatory text. Verify that the spoken language matches the app’s localization and that the focus order is logical. Additionally, check that the dialog respects dynamic type settings; on iOS, the system may increase the button size, and on Android, the dialog should scale with font size adjustments.
Security and Privacy Considerations
From a security standpoint, ensure that your app does not inadvertently grant broader access than requested. For example, requesting camera access should not also enable microphone access unless you explicitly ask for it. Review any third‑party libraries that bundle permission requests; they may ask for more than your code intends. From a privacy perspective, avoid logging denial reasons to external analytics without user consent, as this can reveal sensitive behavior patterns.
How to Test Permission Dialogs on React Native (Complete Guide): Manual Approach
Manual testing remains valuable for exploratory checks, especially when you want to observe the exact look and feel of the dialog under different device configurations. The steps below outline a reproducible manual workflow that you can adapt for each permission type.
Setting Up the Test Environment
- Device or Emulator Selection – Use a physical device whenever possible because system dialogs can behave differently on emulators (e.g., some Android emulators do not show the “Never ask again” checkbox). Keep a matrix of devices covering Android API levels 21‑34 and iOS versions 13‑17.
- Clear Permission State – Before each test, reset the permission to its default state. On Android, run
adb shell pm reset-permissions; on iOS, delete the app from the simulator or device and reinstall, or go to Settings → Your App → Permissions and toggle off. - Instrument the App – Add a temporary debug screen that lists all permissions you intend to test and provides a button to request each one. This screen should also display the current permission status (granted, denied, never ask again) after each request, which you can obtain via the
PermissionsAPI orexpo-permissions. - Enable Accessibility Tools – Turn on TalkBack or VoiceOver, and optionally use an accessibility scanner (e.g., Android Accessibility Test Framework or Xcode’s Accessibility Inspector) to capture announcements.
Step‑by‑Step Manual Test Procedure
For each permission type, repeat the following steps:
- Launch the app and navigate to the debug permission screen.
- Press the request button for the target permission. Observe the system dialog that appears.
- Record the dialog’s visual properties – title, message, button labels, and any icons.
- Interact with the dialog – tap Allow, Deny, or (on Android) the “Never ask again” checkbox followed by Deny/Allow as appropriate.
- Check the app’s immediate response – verify that the status displayed on the debug screen updates correctly and that any feature‑specific UI changes occur as expected.
- Test the “Never ask again” path – after denying with the checkbox, press the request button again; the dialog should not appear, and the status should remain denied.
- Simulate a permission change via Settings – exit the app, open Settings, toggle the permission, return to the app, and press the request button again. Confirm that the app reflects the new setting without a restart.
- Accessibility check – with TalkBack/VoiceOver enabled, listen to the announcements when the dialog appears. Ensure that the purpose of the permission and the two options are clearly conveyed.
- Repeat for edge cases – launch a system alert (e.g., incoming call or low‑battery warning) before pressing the request button, then verify that the dialog still appears and the result is captured correctly.
Manual Testing Checklist (Markdown Table)
| Checklist Item | Description | Pass/Fail |
|---|---|---|
| Permission reset | App starts with permission in default state | |
| Dialog appears | System modal shows after request call | |
| Correct title/message | Matches the rationale string you provided | |
| Allow action | Feature works, status updates to granted | |
| Deny action | Feature disabled, status updates to denied | |
| Never ask again (Android) | Subsequent requests skip dialog, status stays denied | |
| iOS Don’t Allow | Same as above | |
| Settings toggle respected | Changing permission in Settings updates app without restart | |
| Accessibility announced | TalkBack/VoiceOver reads title and options | |
| Overlapping modal handled | Dialog appears above other alerts, result still captured | |
| Background permission change | Permission altered while app in background, next request reflects change | |
| No request loop | Repeated requests after denial do not spam dialogs | |
| Localization respected | Dialog text matches app’s current locale | |
| Dynamic type scaling | Buttons and text scale with system font size |
Perform this checklist for each permission you support. Any failed item indicates a defect that should be logged and prioritized.
How to Test Permission Dialogs on React Native (Complete Guide): Automated Testing with Detox
Detox is a popular end‑to‑end testing framework for React Native that works well with native dialogs because it runs on the actual device or emulator and can interact with system alerts. The key to testing permission dialogs with Detox is to use the device API to press buttons on the native alert that the OS presents.
Detox Setup Overview
- Add Detox to your project –
yarn add --dev detoxanddetox init -r jest. - Configure a test device – In
detox.config.js, specify an Android emulator or iOS simulator that matches the OS version you want to test. Ensure that thebinaryPathpoints to your built app (usegradlew assembleDebugorxcodebuild). - Grant test permissions to the test runner – On Android, you may need to add
and ensure that the test instrumentation has access to theandroid.permission.ACCESS_MOCK_LOCATIONif you plan to mock location. - Disable animation – For faster and more reliable tests, turn off window and animator scales via
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.
Writing a Permission Test
Below is a sample Detox test that verifies the camera permission flow on Android. The test assumes you have a screen called PermissionScreen with a button requestCamera and a text element cameraStatus that shows “granted”, “denied”, or “neverAskAgain”.
// e2e/cameraPermission.test.js
describe('Camera Permission Flow', () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true, permissions: {} }); // start with clean permissions
});
it('should grant camera access when user taps Allow', async () => {
await element(by.id('requestCamera')).tap();
// Detect the system alert and press Allow
await waitFor(element(by.text('Allow'))).toBeVisible().withTimeout(5000);
await element(by.text('Allow')).tap();
// Verify status updates
await waitFor(element(by.id('cameraStatus'))).toHaveText('granted').withTimeout(5000);
// Optionally, start preview and check that a video element appears
await expect(element(by.id('cameraPreview'))).toBeVisible();
});
it('should respect Deny and show fallback UI', async () => {
await element(by.id('requestCamera')).tap();
await waitFor(element(by.text('Deny'))).toBeVisible().withTimeout(5000);
await element(by.text('Deny')).tap();
await waitFor(element(by.id('cameraStatus'))).toHaveText('denied').withTimeout(5000);
await expect(element(by.id('cameraPreview'))).toNotExist();
await expect(element(by.id('cameraDisabledMessage'))).toBeVisible();
});
it('should handle Never ask again correctly', async () => {
await element(by.id('requestCamera')).tap();
// On Android, the Never ask again checkbox appears only after a prior denial
await element(by.text('Deny')).tap(); // first denial to trigger checkbox
await element(by.id('requestCamera')).tap(); // second request shows checkbox
await waitFor(element(by.id('neverAskAgainCheckbox'))).toBeVisible().withTimeout(5000);
await element(by.id('neverAskAgainCheckbox')).tap(); // check it
await element(by.text('Deny')).tap(); // deny with checkbox
// Third request should not show dialog
await element(by.id('requestCamera')).tap();
await expect(element(by.text('Allow'))).toNotExist(); // dialog absent
await waitFor(element(by.id('cameraStatus'))).toHaveText('neverAskAgain').withTimeout(5000);
});
});
#### Key Points in the Detox Example
device.launchApp({ permissions: {} })clears granted permissions before each test, ensuring a clean slate.waitFor+element(by.text(...))is used to locate the native alert buttons. Detox treats system alerts as regular UI elements, which works on both Android and iOS.- Checkbox interaction – On Android, the “Never ask again” checkbox has a resource ID that you can target; on iOS, there is no checkbox, but the system records the choice after a denial.
- Assertions on UI – After each permission outcome, you verify that the app’s internal status element reflects the correct state and that dependent UI (preview, disabled message) appears or disappears accordingly.
Handling iOS Permission Dialogs
On iOS, the system alert does not contain a “Never ask again” checkbox; instead, a second denial results in the system setting the permission to denied permanently. The same test pattern applies, but you omit the checkbox step. Additionally, iOS may present a provisional authorization dialog for location; you can treat it as a regular alert with options “Allow While Using App” and “Don’t Allow”.
Dealing with Flaky Tests
Permission dialogs can be flaky if the test races with the native animation. To mitigate:
- Increase the timeout for
waitForwhen targeting the alert (e.g., 7000 ms). - Disable animations as mentioned in the setup.
- On Android, you can also use
adb shell settings put global animator_duration_scale 0to remove all animation delays. - If you notice that the alert sometimes appears behind another modal, insert a small
await new Promise(r => setTimeout(r, 500));before tapping the button to let any pending animations settle.
Integrating Detox into CI
Add a step to your CI pipeline that runs detox test --configuration android.emu.debug or detox test --configuration ios.sim.debug. Most CI services (GitHub Actions, Bitrise, CircleCI) provide Android emulators and iOS simulators; just ensure you have the required Xcode and Android SDK versions installed.
How to Test Permission Dialogs on React Native (Complete Guide): Automated Testing with React Native Testing Library and Jest
For unit‑level verification where you want to mock the native permission layer, React Native Testing Library (RNTL) combined with Jest is effective. This approach lets you test the logic that follows a permission request without invoking the actual system dialog, making tests fast and deterministic.
Mocking the Permissions API
If you are using react-native-permissions, you can mock its functions in a Jest setup file:
// jest.setup.js
jest.mock('react-native-permissions', () => ({
check: jest.fn(),
request: jest.fn(),
PERMISSIONS: {
CAMERA: 'camera',
LOCATION: 'location',
},
}));
Then, in your test, you control what request returns:
// __tests__/PermissionLogic.test.js
import { request, PERMISSIONS } from 'react-native-permissions';
import { requestCameraPermission } from '../src/permissionHelpers';
describe('Permission logic unit tests', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('calls request and proceeds on granted', async () => {
request.mockResolvedValueOnce('granted');
const result = await requestCameraPermission();
expect(request).toHaveBeenCalledWith(PERMISSIONS.CAMERA);
expect(result).toBe('granted');
});
it('handles denied status and returns fallback', async () => {
request.mockResolvedValueOnce('denied');
const result = await requestCameraPermission();
expect(result).toBe('denied');
// assert that your helper returns a fallback object or throws a custom error
});
it('treats blocked status as permanent denial', async () => {
request.mockResolvedValueOnce('blocked'); // Android term for never ask again
const result = await requestCameraPermission();
expect(result).toBe('blocked');
});
});
Testing Component Reactions
When your component renders different UI based on permission status, you can mock the hook that fetches the status. Suppose you have a custom hook usePermission that returns { status: 'granted' | 'denied' | 'blocked' }. In the test, you mock its return value:
// __tests__/CameraComponent.test.js
import { render, screen } from '@testing-library/react-native';
import CameraComponent from '../src/CameraComponent';
import { usePermission } from '../src/hooks/usePermission';
jest.mock('../src/hooks/usePermission');
describe('CameraComponent UI', () => {
beforeEach(() => {
usePermission.mockReset();
});
it('shows preview when permission granted', async () => {
usePermission.mockReturnValueOnce({ status: 'granted' });
render(<CameraComponent />);
expect(await screen.findByTestId('camera-preview')).toBeVisible();
expect(screen.getByTestId('permission-denied-message')).not.toBeInTheDocument();
});
it('shows request button when denied', async () => {
usePermission.mockReturnValueOnce({ status: 'denied' });
render(<CameraComponent />);
expect(screen.getByTestId('request-permission-button')).toBeVisible();
expect(screen.queryByTestId('camera-preview')).not.toBeInTheDocument();
});
it('shows permanent denial message when blocked', async () => {
usePermission.mockReturnValueOnce({ status: 'blocked' });
render(<CameraComponent />);
expect(screen.getByTestId('permission-blocked-message')).toBeVisible();
expect(screen.queryByTestId('request-permission-button')).not.toBeInTheDocument();
});
});
Advantages and Limitations
- Speed – Unit tests run in milliseconds, ideal for pre‑commit hooks.
- Isolation – You can test edge cases like malformed responses from the native module without needing a device.
- Limitation – You do not validate that the actual system dialog appears or that the native bridge correctly translates the user’s tap into the JavaScript callback. Therefore, complement unit tests with at least one end‑to‑end test (Detox) per critical permission.
Setting Up Jest for React Native
If you haven’t already, add the following to your package.json:
{
"jest": {
"preset": "react-native",
"setupFilesAfterEnv": ["<rootDir>/jest.setup.js"],
"testMatch": ["**/__tests__/**/*.js", "**/?(*.)+(spec|test).js"]
}
}
Run tests with yarn test or npm test.
How to Test Permission Dialogs on React Native (Complete Guide): Using Expo Permissions API
Expo simplifies permission handling by providing a unified expo-permissions module that works across managed and bare workflows. Testing with Expo follows similar patterns, but there are a few nuances due to the Expo Go app and the EAS build process.
Testing in Expo Go
When you run your app via Expo Go, the permission dialogs are still native; however, the Expo client adds an extra layer that may affect timing. To test:
- Launch Expo Go on a device or emulator.
- Open the developer menu (shake device or press
Cmd+Din simulator) and select “Reload” to ensure a clean state. - Use the Permissions module to check and request permissions. Example snippet:
import * as Permissions from 'expo-permissions';
import { useState } from 'react';
export default function PermissionTester() {
const [status, setStatus] = useState(null);
const askCamera = async () => {
const { status: camStatus } = await Permissions.askAsync(Permissions.CAMERA);
setStatus(camStatus);
};
return (
<View>
<Button title="Ask Camera" onPress={askCamera} />
<Text>Current status: {status ?? 'unknown'}</Text>
</View>
);
}
You can manually tap the button and observe the dialog. To automate, you can use Detox with Expo as described earlier; the only difference is that the binary you build is the Expo client with your bundled JS.
EAS Build and Automated Cloud Testing
Expo’s Application Services (EAS) allow you to create a standalone binary (.apk or .ipa) that you can upload to services like Firebase Test Lab or Bitrise. In those environments, you can run Detox or XCTest/XCUITest scripts just as with a bare React Native app.
A typical EAS workflow for permission testing:
# 1. Build a debug binary for Android
eas build --platform android --profile debug
# 2. Download the artifact after build completes
# 3. Upload to Firebase Test Lab
gcloud firebase test android run \
--type instrumentation \
--app path/to/app-debug.apk \
--test path/to/detox-test.apk \
--device model=Pixel3,version=30,locale=en,orientation=portrait
# 4. Retrieve test results from the Firebase console
When using EAS, ensure that your eas.json includes a debug profile with developmentClient: true if you intend to use Detox, because Detox requires the ability to inject its test server into the binary.
Permissions Specific to Expo
Expo provides a few permission types that are not directly available in the core React Native APIs, such as Permissions.NOTIFICATIONS (iOS/Android) and Permissions.SYSTEM_ALERT_WINDOW (Android only). Test these the same way, but keep in mind that some permissions (like SYSTEM_ALERT_WINDOW) require the app to be set as the default launcher or to have special privileges granted via ADB:
adb shell pm grant host.exp.exporter android.permission.SYSTEM_ALERT_WINDOW
In an EAS build, you can add this permission to the android.permissions array in app.json and then rely on the runtime request.
How to Test Permission Dialogs on React Native (Complete Guide): CI/CD Integration and SUSA
Integrating permission‑dialog tests into your continuous delivery pipeline ensures that regressions are caught before they reach users. Below we outline a typical CI setup using GitHub Actions, how to incorporate Fastlane for beta distribution, and where an autonomous QA platform like SUSA can add value.
GitHub Actions Workflow Example
name: Permission Test Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [24, 28, 30, 33] # Android versions to test
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
- name: Set up Android SDK
uses: android-actions/setup-android@v2
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run unit tests
run: |
yarn install --frozen-lockfile
yarn test
- name: Build debug APK
run: |
cd android && ./gradlew assembleDebug
- name: Install Detox dependencies
run: |
npm i -g detox-cli
detox test --configuration android.emu.debug --loglevel verbose
For iOS, you would add a macOS runner and use xcodebuild to produce a .app for the simulator, then run Detox with the ios.sim.debug configuration.
Fastlane for Beta Distribution
Fastlane can automate the process of uploading a new build to Firebase App Distribution or TestFlight after your permission tests pass. A simplified Fastfile lane might look like:
lane :beta do
gradle(
task: 'assemble',
build_type: 'release'
)
firebase_app_distribution(
app: "1:1234567890:android:abcdef",
groups: "qa-team",
release_notes: "Permission dialog regression tests passed",
firebase_cli_token: ENV['FIREBASE_TOKEN']
)
end
You can chain this lane after the Detox job in GitHub Actions, ensuring that only builds that have passed permission testing are distributed to testers.
Leveraging SUSA for Autonomous Exploration
SUSA (Susatest) is an autonomous QA platform that explores your app without pre‑written scripts. After you upload an APK or point it at a web URL, SUSA launches the app and begins interacting with UI elements using a variety of user personas (curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user, etc.). Each persona has its own behavior profile—taps, scrolls, typing speed, tolerance for delays, and likelihood to grant or deny permissions.
When SUSA encounters a permission dialog, it treats it like any other UI element and decides, based on the persona, whether to tap Allow, Deny, or (on Android) check the “Never ask again” box before denying. Because the exploration is driven by statistical models rather than hard‑coded assertions, SUSA can surface issues that scripted tests often miss, such as:
- A permission dialog that appears behind a transient loading spinner, causing the curious persona to tap elsewhere and miss the dialog, leading to a silent denial that the app does not recover from.
- An adversarial persona that repeatedly taps Deny in rapid succession, exposing a race condition where the app re‑requests the permission before the native dialog has fully dismissed, resulting in multiple overlapping dialogs.
- An accessibility‑focused persona that relies on TalkBack; SUSA can verify that the dialog’s announcement includes the permission purpose and that the focus order is logical, flagging missing accessibility labels.
- A power‑user persona that grants a permission, then immediately revokes it via Settings while the app is still in the foreground, testing whether your app handles mid‑session permission revocation gracefully.
SUSA automatically generates regression scripts (Appium for Android, Playwright for Web) from the flows it discovers, which you can then commit to your repository and run in CI. This creates a feedback loop: the more you run SUSA, the smarter its exploration becomes, as it remembers previously visited screens and dead ends.
To use SUSA in your CI, you can add a step that uploads the latest build to the SUSA cloud and triggers an exploration run:
- name: Upload to SUSA and run exploration
uses: susatest/action@v1
with:
api-key: ${{ secrets.SUSA_API_KEY }}
build-path: ./android/app/build/outputs/apk/debug/app-debug.apk
personas: curious,impatient,elderly,accessibility
max-depth: 5
output-format: junit
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