How to Test Promo Codes on Android (Complete Guide)
Promo codes are a common lever for acquisition, retention, and revenue uplift in Android apps. When a code fails silently—accepting an invalid string, applying the wrong discount, or leaking internal
Why Promo Code Testing Matters
Promo codes are a common lever for acquisition, retention, and revenue uplift in Android apps. When a code fails silently—accepting an invalid string, applying the wrong discount, or leaking internal logic—users lose trust, support costs rise, and fraud can erode margins. A broken promo flow also masks deeper issues: mis‑configured backend endpoints, race conditions between UI and network layers, or accessibility barriers that prevent certain users from redeeming offers. Because promo code entry often sits at the boundary between UI, local storage, and remote validation, it exercises many moving parts in a single user action. Testing it thoroughly therefore gives a high‑signal view of overall app health.
Anatomy of a Promo Code Flow in Android Apps
A typical promo code interaction follows these stages:
- Entry point – a button, menu item, or deep‑link that launches a promo screen.
- Input UI – an
EditText(sometimes with input masks) plus a “Apply” button. - Local validation – client‑side checks for length, allowed characters, or format (e.g.,
^[A-Z0-9]{6,12}$). - Network request – a POST to
/promo/validate(or similar) with the code, device ID, and auth token. - Backend response – JSON containing status (
valid,invalid,expired,already_used), discount details, and any error messages. - UI update – show success toast, adjust cart total, or display an error snackbar.
- Persistence – store the applied code in SharedPreferences or a local DB for receipt generation.
- Post‑apply flow – proceed to checkout, share the discount, or trigger analytics events.
Each stage can introduce failure modes that are not obvious from unit tests alone. Understanding the flow helps you map test cases to specific components.
Comprehensive Test Matrix
Below is a detailed matrix that covers happy‑path, error, edge, accessibility, and security scenarios. Each row lists the test ID, description, expected outcome, and the layer(s) exercised.
| ID | Category | Description | Expected Outcome | Primary Layer(s) Tested |
|---|---|---|---|---|
| P1 | Happy Path | Valid, unused code entered correctly | Discount applied, toast shows success, cart total updated, analytics event promo_applied fired | UI → Network → Backend → UI |
| P2 | Happy Path | Code with leading/trailing spaces trimmed automatically | Same as P1 (spaces ignored) | UI (input sanitization) |
| P3 | Error – Client | Invalid format (e.g., lowercase letters when only uppercase allowed) | Inline error appears instantly, no network call | UI (local validation) |
| P4 | Error – Client | Empty string submitted | Field‑level error, focus remains on EditText | UI |
| P5 | Error – Backend | Code exists but is expired | Backend returns expired, UI shows appropriate message, no discount applied | Network → Backend |
| P6 | Error – Backend | Code already used by this account | Backend returns already_used, UI shows message, no discount | Network → Backend |
| P7 | Edge – Race | User taps Apply twice quickly before first response | Only one network request sent, second tap ignored or shows “already processing indicator shown | UI (debouncing) + Network |
| P8 | Edge – Connectivity | No network when Apply pressed | UI shows offline error, no request made, field stays enabled | UI → Network layer |
| P9 | Edge – Backend Downtime | Server returns 500 or times out | UI shows generic error, retry option offered, no crash | Network → Error handling |
| P10 | Accessibility | TalkBack user navigates to promo screen | All controls have proper content‑descriptions, input announced as “edit text, promo code”, Apply button announced | UI (accessibility) |
| P11 | Accessibility | Color contrast insufficient on error text | Contrast ratio < 4.5:1 flagged by automated tool | UI (visual) |
| P12 | Security | Code reflected in URL or logs without sanitization | No sensitive data appears in logcat or network sniffers | Security / Privacy |
| P13 | Security | Brute‑force attempt (rapidly trying many codes) | Rate‑limit triggered on backend, UI shows “too many attempts” after threshold | Network → Backend |
| P14 | Privacy | Promo code stored in plain‑text SharedPreferences | Code should be encrypted or omitted from backups | Storage |
| P15 | Regression | After a UI redesign, promo screen still reachable via deep link | Deep link opens promo screen, pre‑filled code if provided | Navigation |
| P16 | Regression | Promo code analytics event missing | Verify promo_applied event with correct parameters appears in analytics backend | Analytics |
How to Use the Matrix
- Map each test ID to a test case in your test management tool.
- Prioritize P1‑P4 for smoke runs; P5‑P9 for stability; P10‑P12 for compliance; P13‑P15 for regression; P16 for observability.
- Automate the IDs that are deterministic (P1‑P9, P13‑P16) and keep the accessibility checks (P10‑P11) as part of your CI lint step or a separate axe/Android‑accessibility test suite.
Manual Testing Step‑by‑Step Guide
Manual exploration remains valuable for spotting UX friction and unexpected edge cases. Follow this procedure on a physical device or emulator with API level 21+.
- Prepare the environment
- Install the app variant you want to test (debug, beta, or production).
- Ensure you have a test account with no prior promo usage.
- Enable Developer Options → Show taps (helps verify debouncing).
- Clear app data (
adb shell pm clear com.example.app) to start clean.
- Happy‑path verification
- Navigate to the promo screen via the intended entry point (e.g., Settings → Promotions).
- Enter a known valid code (obtained from backend or QA spreadsheet).
- Tap Apply.
- Observe: success toast, cart total reduction, analytics event (use
adb logcat | grep promo_applied). - Verify the code persists after a force‑stop/restart.
- Client‑side error handling
- Leave the field empty, tap Apply → confirm inline error appears.
- Enter a code with disallowed characters (e.g.,
promo!@#) → confirm immediate validation message. - Paste a code with leading/trailing spaces → verify they are stripped and the code is accepted if otherwise valid.
- Backend error simulation
- Use a tool like
Charles Proxyornetcatto intercept the validation request and modify the response:
# Example with netcat to return expired
nc -l 8080 <<EOF
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"expired","message":"Code has expired"}
EOF
- Race condition test
- Enable “Show taps” to see double taps.
- Rapidly tap Apply two times within 200 ms.
- Check logcat for duplicate network calls; expect only one request or a “please wait” indicator.
- Connectivity failure
- Turn off Wi‑Fi and mobile data.
- Tap Apply → verify offline snackbar, no request sent, field remains active.
- Server error handling
- Use the proxy to return HTTP 500 or drop the connection after a delay.
- Confirm the UI displays a generic error and offers a retry button.
- Accessibility check
- Turn on TalkBack, navigate to the promo screen.
- Swipe to each element; listen for correct labels (“Promo code edit text”, “Apply button”).
- Use the Accessibility Scanner app to capture contrast issues.
- Security/privacy sniff
- Connect the device to
adb logcatand filter for the promo code string:
adb logcat | grep -i "promo\|code"
adb shell run-as com.example.app cat shared_prefs/PromoPrefs.xml to verify storage is encrypted or absent.- Post‑apply flow
- Proceed to checkout; confirm the discounted price reflects in the order summary.
- After completing purchase, verify the promo code is attached to the transaction receipt (often sent to email or shown in order history).
Repeat steps for each promo code variant (percentage off, fixed amount, free shipping) and for different user states (new user, existing user, banned user). Document any deviation from the expected outcome in a bug report with reproduction steps, device model, OS version, and logs.
Automated Testing on Android: Frameworks and Sample Code
Automation gives repeatability and lets you embed promo validation in CI pipelines. Below are patterns for the three most common Android test stacks: Espresso (UI), UI Automator (cross‑app), and Appium (black‑box).
Espresso – In‑process UI Tests
Espresso runs with the app, giving fast feedback and direct access to ViewModels.
@RunWith(AndroidJUnit4::class)
class PromoCodeEspressoTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class)
@Test
fun `valid promo applies discount`() {
// Arrange – seed a valid code via a test‑only API
TestHelper.setValidPromo("SAVE10")
// Act
onView(withId(R.id.btn_promo)).perform(click())
onView(withId(R.id.et_promo_code)).perform(typeText("SAVE10"), closeSoftKeyboard())
onView(withId(R.id.btn_apply)).perform(click())
// Assert – UI updates
onView(withId(R.id.tv_discount)).check(matches(withText("$10.00 off")))
onView(withId(R.id.tv_total)).check(matches(withText("$90.00")))
// Assert – analytics (using a fake FirebaseAnalytics)
val firebase = FirebaseAnalytics.getInstance(ApplicationProvider.getApplicationContext())
TestHelper.verifyEvent(firebase, "promo_applied", mapOf("code" to "SAVE10", "amount" to 10.0))
}
@Test
fun `empty field shows error`() {
onView(withId(R.id.btn_promo)).perform(click())
onView(withId(R.id.btn_apply)).perform(click())
onView(withId(R.id.et_promo_code))
.check(matches(hasErrorText(containsString("Please enter a code"))))
}
}
Notes
- Use
IdlingResourceto wait for network responses if you rely on a mocked server (e.g., MockWebServer). - For debounce validation, disable the button after the first click and re‑enable it in the test to ensure no second request fires.
UI Automator – Cross‑app Scenarios
UI Automator is useful when the promo entry point lives in a separate Settings app or when you need to test deep links from a browser.
@RunWith(AndroidJUnit4.class)
public class PromoDeepLinkUiAutomatorTest {
private static final String PACKAGE = "com.example.app";
private static final int LAUNCH_TIMEOUT = 5000;
@Test
public void deepLinkLaunchesPromoScreen() throws Exception {
// Clear existing state
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("pm clear " + PACKAGE);
// Fire a deep link via an intent
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("myapp://promo?code=FREEPOST"));
intent.setPackage(PACKAGE);
ActivityManager am = (ActivityManager)
InstrumentationRegistry.getInstrumentation().getTargetContext()
.getSystemService(Context.ACTIVITY_SERVICE);
am.startActivity(intent);
// Wait for promo screen
UiObject promoTitle = new UiObject(new UiSelector()
.resourceId(PACKAGE + ":id/promo_title"));
assertTrue(promoTitle.waitForExists(LAUNCH_TIMEOUT));
// Verify code pre‑filled
UiObject codeField = new UiObject(new UiSelector()
.resourceId(PACKAGE + ":id/et_promo_code"));
assertEquals("FREEPOST", codeField.getText());
}
}
Appium – Black‑Box End‑to‑End
Appium lets you test the actual APK without source access, making it ideal for release candidates.
import io.appium.java_client.android.AndroidDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class PromoCodeAppiumTest {
private AndroidDriver 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", "/path/to/app-release.apk");
caps.setCapability("automationName", "UiAutomator2");
driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), caps);
}
@Test
public void testPromoApplication() {
// Navigate to promo screen
driver.findElement(By.accessibilityId("Promotions")).click();
driver.findElement(By.id("et_promo_code")).sendKeys("WELCOME20");
driver.findElement(By.id("btn_apply")).click();
// Verify discount
String total = driver.findElement(By.id("tv_total")).getText();
assertEquals("$80.00", total);
// Optional: grab network logs via ChromeDriver if using WebView
}
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
CI Integration Tips
- Run Espresso tests on every PR (fast feedback).
- Schedule UI Automator/Appium suites nightly on a device farm (Firebase Test Lab, AWS Device Farm).
- Capture screenshots on failure and attach them to the PR comment.
- Export test results to JUnit XML for ingestion by your dashboard.
Tooling and CI Integration
Choosing the right tools reduces flakiness and speeds up feedback. The table below compares common Android test tools relevant to promo code testing.
| Tool | Scope | Language | Setup Effort | Flakiness | Best For |
|---|---|---|---|---|---|
| Espresso | In‑process UI | Kotlin/Java | Low (Android Studio) | Low (synchronizes with UI thread) | Fast unit‑like UI tests, CI gate |
| UI Automator | Cross‑app/system UI | Java | Medium (need device API ≥18) | Medium (depends on system animations) | Deep links, settings, permission flows |
| Appium | Black‑box (APK) | Java, JS, Python, etc. | Medium (Appium server) | Higher (depends on device state) | Release candidate validation, cross‑platform |
| Robolectric | JVM‑based unit tests | Java/Kotlin | Low (no device/emulator) | Low (but limited to pure Android framework) | ViewModel, LiveData, business logic |
| MockWebServer | Network mocking | Java/Kotlin | Low | Low | Simulating backend responses (expired, 500) |
| Firebase Test Lab | Cloud device farm | Any (via Espresso/UI Automator) | Low (upload APK) | Low (managed devices) | Broad device coverage, nightly runs |
| Accessibility Scanner | UI accessibility checks | N/A | Low | Low | Early detection of contrast/touch‑target issues |
| LeakCanary | Memory leak detection | Java/Kotlin | Low | Low | Ensuring promo screens don’t leak Context |
Practical CI snippet (GitHub Actions)
name: Android Promo Tests
on:
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Download Test Lab artifacts (if any)
run: |
# (optional) download APK from previous build
- name: Run Espresso tests
run: ./gradlew connectedDebugAndroidTest
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: espresso-results
path: app/build/outputs/androidTest-results/connected/
Adjust the workflow to run UI Automator or Appium tests on a schedule (schedule: trigger) to avoid slowing down PRs.
Production‑Only Gotchas and How to Catch Them
Some bugs only surface when the app runs at scale or under specific production conditions. Below are frequent sources of failure and tactics to uncover them before they affect users.
| Gotcha | Why It Happens | Detection Technique | |
|---|---|---|---|
| Backend caching of promo validation | CDN or API gateway caches 200 OK responses for a short TTL, causing a previously used code to appear valid. | Use varying query strings or Cache‑Control: no‑cache headers in test requests; monitor response headers. | |
| Promo code leakage via referrer header | Some implementations append the code to a redirect URL, exposing it in logs or third‑party analytics. | Inspect network traffic with adb logcat or mitmproxy for query‑string leakage. | |
| Time‑zone drift | Server validates expiration based on UTC, but the device sends local time without conversion, leading to false expires. | Set device to different time zones, attempt to use a code near its expiry boundary. | |
| Concurrent promo application | Two users on the same device (e.g., shared tablet) trigger Apply almost simultaneously, causing race on server‑side “already used” flag. | Use two instrumented tests on the same emulator with cloned app data, send requests within 10 ms. | |
| Promo code reuse after refund | Refund flow does not invalidate the applied promo, allowing a user to re‑apply the same code after a return. | Perform a purchase with promo, initiate a refund via backend mock, then try to re‑apply the code. | |
| Promo stacking loophole | Backend permits multiple codes to be applied, but UI only shows one discount, leading to hidden extra savings. | Attempt to apply two different valid codes sequentially, inspect final price and backend logs for both codes being recorded. | |
| Promo code exposure in crash reports | Uncaught exception prints the code to stack trace, which gets sent to crash analytics. | Force a null‑pointer after code entry (e.g., by mocking a malformed JSON response) and verify crash reports do not contain the code. | |
| Promo code bypass via rooted device | Attackers modify SharedPreferences or use Xposed to inject a valid code flag. | Run the app on a rooted emulator or device with Magisk, attempt to toggle a “promo_applied” flag manually and see if the UI honors it without a server call. | |
| Network retry storms | Poor retry logic causes hundreds of validation requests when the backend is down, potentially triggering DoS protection. | Simulate 503 responses and count requests via `adb logcat | grep promo/validate`. |
| Localized string overflow | In languages with longer words, the promo input field or error messages get clipped, hiding crucial info. | Switch device locale to German, Russian, or Arabic and run the manual steps; use Layout Inspector to verify bounds. |
To catch these, augment your test suite with:
- Contract tests using Pact or Spring Cloud Contract to ensure the client and server agree on promo validation payloads.
- Chaos engineering injections (latency, faults) via tools like
toxiproxyorfbctto observe how the app behaves under adverse network conditions. - Data‑driven tests that iterate over a matrix of locales, time‑zones, device models, and network profiles.
- Production canary monitoring – instrument the promo flow with feature flags and alert on abnormal redemption rates or error spikes.
Accessibility, Security and Privacy Considerations
Promo code interfaces must be usable by everyone and must not leak sensitive data. Below are concrete checks you can embed in your test plan.
Accessibility
- Touch target size – Ensure the Apply button and any help icons are at least 48 dp. Use the
AccessibilityTestFragmentfrom the AndroidX test library to assert dimensions. - Labeling – Every
EditTextmust have an associatedlabelFororhintthat TalkBack reads. Test withonView(withId(R.id.et_promo_code)).check(matches(hasContentDescription("Promo code"))). - Error announcement – When validation fails, the error text should be announced immediately. Use
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGEDlisteners in a test to confirm. - Contrast – Run the Android Accessibility Scanner or
axe-androidon promo screens; fail the build if any contrast ratio < 4.5:1 for normal text.
Security
- Input sanitization – Restrict the character set via
InputFilterand verify that disallowed characters are rejected before they reach the network layer. - Rate limiting on client – Disable the Apply button after a tap and re‑enable only after a successful response or a timeout; test that rapid taps do not generate more than one request.
- Certificate pinning – Ensure the promo validation endpoint is pinned; attempt a MITM attack with `mitmproxy and confirm the connection fails.
- Token binding – The request must include a fresh auth token or device‑specific nonce; replaying a captured request should be rejected by the server.
Privacy
- No logging – Confirm that neither Logcat nor crash reporting libraries receive the raw promo code. Use a custom
Loggerthat redacts any string matching the promo pattern ([A-Z0-9]{6,12}). - Secure storage – If you need to persist a redeemed code (e.g., for receipt generation), store it encrypted with
EncryptedSharedPreferencesor Keystore‑backedSharedPreferences. - Minimal data sharing – The promo validation request should send only the code, user ID, and necessary device identifiers; avoid transmitting extra personal data like email or phone number unless required for fraud prevention.
Implementing these checks as part of your unit and UI test suites ensures that regressions are caught early and that your promo feature complies with accessibility guidelines (WCAG 2.1 AA) and data‑protection regulations (GDPR, CCPA).
Autonomous, Persona‑Driven Exploration with SUSATest
While scripted tests cover known paths, real users behave in unpredictable ways. An autonomous testing agent that simulates different personas can surface issues that static scripts never consider.
SUSATest (susatest.com) is an autonomous QA platform that, given an APK or a web URL, explores the app on its own. It builds a behavior model for each persona—curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user, etc.—and executes actions like taps, swipes, text entry, and dialog handling without any test code.
When pointed at an Android app that contains a promo flow, SUSATest will:
- Discover the promo entry point – Even if it is buried behind a settings menu or a promotional banner, the agent’s curiosity persona will eventually scroll and tap until it finds a screen with an
EditTextlabeled “Promo code”. - Vary input strategies – The impatient persona may rapidly tap Apply, paste long strings, or try to submit before the keyboard fully appears, exposing debounce bugs. The adversarial persona will attempt SQL‑like strings, extremely long inputs, or Unicode control characters to test injection and sanitization.
- Simulate network conditions – By throttling the virtual network, the agent can observe how the app handles timeouts, 500 responses, or intermittent connectivity, capturing the exact UI states that users see in flaky connections.
- Check accessibility – The accessibility‑focused persona enables TalkBack, changes font scale, and switches to high‑contrast mode, then attempts to complete the promo flow, logging any missing labels or focus traps.
- Detect silent failures – If the backend returns a success payload but the UI does not update the discount, the power‑user persona (which monitors cart totals) will notice the mismatch and flag it.
- Learn from past runs – After each session, SUSATest remembers which screens lead to dead ends (e.g., a promo screen that always shows an error due to a mis‑configured endpoint) and avoids re‑exploring them, making subsequent runs faster and more focused on risky areas.
The output includes a detailed report: screens visited, actions taken, any crashes or ANRs detected, accessibility violations flagged, and a list of discovered promo‑code‑related bugs with repro steps. Because the exploration is guided by learned behavior models rather than hard‑coded scripts, it often finds edge cases such as:
- A promo code that works only when entered via a voice‑input method (the agent’s “voice” persona uses the IME to dictate the code).
- A scenario where applying a promo while a discount‑eligible item is out of stock triggers a silent cart‑state mismatch.
- An issue where the promo screen appears behind a system overlay (e.g., a chat‑head) on certain OEM skins, making the Apply button unreachable.
Integrating SUSATest into your release pipeline complements scripted tests: run your Espresso/UI Automator suites on every PR, and schedule a nightly autonomous crawl on a device farm. The combined approach gives you both deterministic verification and emergent‑behavior discovery.
Quick Reference Checklist
Use this list before each release candidate to verify that the promo code feature is production‑ready.
| ✅ Item | How to Verify |
|---|---|
| Happy‑path flow | Manual or Espresso test with a known valid code → discount applied, toast shown, analytics event fired. |
| Client‑side validation | Enter empty string, invalid characters, leading/trailing spaces → proper inline errors, no network call. |
| Backend error handling | Mock expired, already_used, 500, timeout responses → appropriate UI messages, no crash, retry offered. |
| Debounce / double‑tap | Rapid double tap → single network request, UI shows processing indicator or ignores second tap. |
| Offline behavior | No connectivity → offline snackbar, field stays enabled, no request sent. |
| Accessibility | TalkBack navigation, font scaling, high contrast → all controls labeled, touch targets ≥48 dp, contrast ≥4.5:1. |
| Security | No promo code in logcat, crash dumps, or network leaks; input filtered; rate limiting on Apply; certificate pinning enforced. |
| Privacy | Code not stored in plain SharedPreferences; if persisted, encrypted; minimal data sent in validation request. |
| Locale & time‑zone | Test with at least three locales (en-US, de-DE, ja-JP) and two time zones (UTC, UTC+5:30) → UI layouts intact, expiration logic correct. |
| Analytics | Verify promo_applied event fires with correct parameters (code, discount amount, timestamp). |
| Regression guard | Ensure deep link myapp://promo?code=TEST still opens promo screen and pre‑fills the field. |
| Performance | Apply promo under 3G simulated latency (<2 s end‑to‑end) → UI remains responsive, no ANR. |
| Post‑apply state | After navigating away and returning, the applied promo is still reflected in cart total. |
| Fail‑safe | If the promo server is unreachable for >5 s, the app shows a clear error and lets the user continue checkout without the promo. |
| Release notes | Update any user‑facing copy (e.g., “Promo codes are case‑sensitive”) to reflect actual behavior. |
Run through the checklist on a physical device (or a representative emulator) for each build candidate. Mark any item that fails and create a ticket with reproduction steps, logs, and device info.
Closing Takeaways
Promo code testing is more than checking that a discount appears; it is a window into the health of your app’s UI, networking, data storage, accessibility, and security layers. A solid test matrix gives you confidence that the happy path works and that common error conditions are handled. Manual exploration uncovers UX friction that automated checks can miss, while automated Espresso, UI Automator, and Appium tests provide fast, repeatable feedback for CI pipelines.
Production‑only gotchas—caching, time‑zone mismatches, concurrency bugs, and leakage—require targeted chaos and contract tests, as well as vigilant monitoring in staging and pre‑release environments. Accessibility and privacy are not optional extras; they are legal and ethical obligations that can be verified with automated scanners and persona‑driven testing.
Finally, autonomous, persona‑driven agents like SUSATest add a valuable complementary layer: they discover the scenarios your team never thought to script, from voice‑input promo entry to adversarial input fuzzing. By combining scripted unit/UI tests, disciplined manual checks, and intelligent exploratory testing, you ship promo features that work reliably for every kind of user, every network condition, and every device your app encounters.
Keep this guide bookmarked, adapt the matrices to your specific promo logic, and iterate as your app evolves. Your users—and your bottom line—will thank you.
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