How to Automate Deep Links Testing (Step-by-Step)

How to Automate Deep Links Testing (Step-by-Step)

May 21, 2026 · 18 min read · How-To Guides

How to Automate Deep Links Testing (Step-by-Step)

Deep link testing validates that a mobile or web application correctly interprets a custom URI or universal link and routes the user to the intended screen or state. Automating this verification eliminates repetitive manual taps, ensures coverage across personas, and catches regressions that only appear after OS or SDK updates. In the following guide we walk through when automation pays off, how to pick a framework, write stable tests, handle flakiness, manage data, run in CI, and leverage autonomous exploration to bootstrap the effort.

The primary goal of automating deep link validation is to achieve repeatable, fast feedback on navigation correctness while reducing the cognitive load on QA engineers who would otherwise have to craft and execute the same tap‑or‑type sequences for every build. By encoding the expected URL scheme, host, path, and query parameters into test code, you create a living specification that can be executed on every pull request, on every device farm node, and after every platform upgrade. This approach also surfaces issues that are difficult to spot manually, such as missing intent filters, malformed universal‑link files, or race conditions between splash screens and deep‑link handlers.

1. Why Automate Deep Links Testing

1.1 When Automation Pays Off

Automation becomes worthwhile the moment you need to verify the same deep link across more than two device configurations, or when the link is part of a critical user flow such as payment confirmation, password reset, or invitation acceptance. If your team releases weekly, the manual effort to open a link on each emulator, verify the landing screen, and check for error states quickly outpaces the time required to write a single automated test. Automation also shines when you must test links that carry sensitive data (tokens, PII) because you can inject deterministic test values and avoid exposing real credentials in exploratory sessions.

Another trigger is the introduction of new deep‑link variants—e.g., adding a fallback web URL for users without the app installed. Each variant multiplies the matrix of (link, OS version, app version, persona) that must be validated. Automated suites let you expand the matrix horizontally without a proportional increase in test execution time, especially when you run tests in parallel on a device farm.

1.2 Risks of Manual Deep Link Validation

Manual validation relies on human observation of UI state after a link launch. This approach is prone to inattentional blindness: a tester may miss a subtle UI change such as a disabled button or a toast that appears for only 500 ms. Manual steps also cannot be repeated with exact timing, making it difficult to catch race conditions where the deep‑link handler fires before the home activity is fully initialized. Finally, manual testing does not scale to nightly regression runs; any regression introduced after a Friday release would remain undiscovered until the next manual test cycle, potentially impacting users for days.

2. Understanding Deep Links and URL Schemes

2.1 Types of Deep Links (URI, Universal Links, App Links)

On Android, a deep link can be a custom scheme (myapp://pay/123), an HTTP/HTTPS URL that maps to an activity via an intent‑filter, or a Chrome Intent URL that includes the package name. On iOS, the equivalent mechanisms are custom schemes (myapp://) and Universal Links, which are standard HTTPS URLs verified through an apple‑app‑site‑association file hosted on your domain. Web applications may also use the same HTTP/HTTPS URLs to trigger client‑side routing via the History API or libraries such as React Router.

Understanding the distinction matters because the test harness must interact with the OS‑level linking mechanism, not just the in‑app router. For custom schemes you can launch an intent directly; for Universal Links you must ensure the device has the association file cached and then open the URL via UIApplication or SafariViewController.

2.2 Anatomy of a Deep Link

A typical deep link consists of four parts: scheme, host, path, and query. Example: myapp://shop/checkout?orderId=9876&campaign=spring23. The scheme (myapp://) tells the OS which app to launch. The host (shop) and path (/checkout) are used by the intent‑filter or association file to route to a specific activity or view controller. The query string supplies parameters that the target screen reads to populate UI fields or trigger business logic.

When writing automated tests, you treat each component as a variable. The scheme and host are often static per app, while the path and query are parameterized to cover multiple scenarios (e.g., different order IDs, promotional codes, deep‑link‑only screens). This parametrization enables a single test method to validate dozens of link variations.

2.3 Common Failure Modes

Failures fall into three categories: routing, handling, and state. Routing errors occur when the OS cannot resolve the link to an app (missing intent‑filter, incorrect domain association, or a typo in the scheme). Handling errors happen when the target activity launches but fails to parse the intent extras or query parameters, leading to a blank screen or a default state. State errors appear when the link arrives at a screen that expects a logged‑in user, but the test harness launches the app in a logged‑out state, causing a redirect to a login flow that was not anticipated in the test assertion.

Automated tests must verify each layer: first that the correct activity/view controller is on screen, second that the expected data is present in UI elements or network calls, and third that the app does not crash or display an error dialog.

3. Choosing a Test Framework

3.1 Mobile Test Automation Options (Appium, Espresso, XCUITest)

Appium remains the most versatile choice for cross‑platform deep‑link testing because it drives the device via the WebDriver protocol and can launch an arbitrary URL with driver.startActivity (Android) or driver.launchApp with a URL argument (iOS via custom URL scheme). Espresso and XCUITest provide faster execution and richer UI synchronization primitives, but they require the test code to reside inside the app’s test target, which complicates launching external links unless you add a test‑only activity that forwards the intent.

If your team already maintains Espresso or XCUITest suites for functional UI tests, extending them with a deep‑link helper method can be the lowest friction route. Otherwise, Appium’s language‑agnostic clients (Java, JavaScript, Python, Ruby) let you keep test code in a separate repository, simplifying version control and CI integration.

3.2 Web Testing Options (Playwright, Cypress, Selenium)

For web applications that rely on Universal Links or custom schemes handled by a browser, Playwright offers the most straightforward API: page.goto('https://example.com/checkout?token=abc') followed by assertions on DOM state or network requests. Cypress works similarly but is limited to same‑origin navigations unless you configure chromeWebSecurity: false. Selenium Grid remains viable for legacy environments but lacks the built‑in auto‑wait and tracing features that reduce flake in Playwright.

When your deep link opens a custom scheme that the browser cannot handle (e.g., myapp://pay), you must delegate to a mobile test framework for the native portion and then possibly switch to a web context if the app launches a WebView for the landing screen. Hybrid approaches therefore often combine Appium for the native launch and Playwright for subsequent WebView validation.

3.3 Hybrid Approaches (SUSA autonomous exploration)

SUSA can explore an app without any test scripts, automatically tapping on UI elements that look like deep‑link triggers (buttons labelled “Open in app”, share menus, or QR‑code scanners) and recording the resulting intents or universal‑link URLs it observes. After an exploration run, SUSA exports a set of Appium (Android) and Playwright (Web) scripts that reproduce the discovered deep‑link flows. This bootstrap step eliminates the manual effort of enumerating every link in marketing copy, push notifications, or deep‑link‑enabled ads, and gives you a stable baseline to extend with persona‑specific variations.

3.4 Decision Matrix Table

CriteriaAppium (cross‑platform)Espresso / XCUITestPlaywright (Web)SUSA Autonomous
Language flexibilityJava, JS, Python, RubyJava/Kotlin, Swift/Obj‑CJS/TS, Python, C#Generates JS/Java
Setup overheadMedium (server, drivers)Low (inside IDE)Low (npm install)Very low (CLI)
Cross‑platform native supportYesNo (platform‑specific)NoYes (generates both)
SpeedModerateFastFastFast (exploration)
Ability to launch arbitrary URLYes (startActivity / openURL)Requires test‑only shimYes (page.goto)Yes (discovers & launches)
Best forTeams needing one repo for Android & iOSTeams already maintaining instrumented testsPure web deep linksQuick baseline creation, regression guardrails

Choose the framework that matches your existing test infrastructure, the platforms you support, and the depth of automation you need. Many teams start with SUSA to generate a baseline, then migrate critical flows to hand‑written Appium or Playwright tests for finer control over data and assertions.

4. Setting Up the Test Environment

4.1 Device/Emulator Preparation

For reliable deep‑link testing, use emulators or real devices with a clean state before each test run. On Android, wipe user data via adb emulator -wipe-data or use Firebase Test Lab’s --no‑cache flag. On iOS, reset the simulator with xcrun simctl erase or configure a fresh device pool in your device farm. Disable animations (adb shell settings put global window_animation_scale 0.0) to reduce nondeterministic delays, and grant any required permissions (e.g., android.permission.INTERNET) via adb shell pm grant.

If you test Universal Links, ensure the domain association file is reachable from the test device. You can host a staging version of apple-app-site-association on a mock server and add an entry to /etc/hosts on the device pointing to that server, or use a tool like ios-deploy to install a custom profile that forces the system to fetch the file from a local assets from your test server.

4.2 Server Stubbing for Deep Link Handlers

Many deep links carry query parameters that trigger API calls (e.g., fetching order details). To avoid flakiness from external services, stub those endpoints with a library such as WireMock, MockServer, or the built‑in network interception in Playwright (page.route). Define static JSON responses that match the schema your app expects, and vary the payload to test error handling (404, 500, malformed JSON). Record the stub URLs in environment variables so the same test suite can run against a local mock, a staging environment, or a production‑shadow endpoint.

4.3 CI/CD Integration Basics

Integrate deep‑link tests into the same pipeline that builds and distributes your app. A typical flow is: (1) compile APK/IPA, (2) upload to a device farm (BrowserStack, Sauce Labs, Firebase Test Lab), (3) run the test suite via a CLI step, (4) collect artifacts (logs, screenshots, video), and (5) publish a JUnit or TestNG report. If you use SUSA, add an exploratory step before the scripted suite to generate or update baseline tests; mark those as “smoke” and run them on every commit, while the full regression suite runs nightly.

4.4 Environment Variables and Secrets

Never hard‑code authentication tokens or API keys in test code. Instead, inject them at runtime via environment variables (DEEP_LINK_TOKEN, STUB_PORT) and retrieve them with System.getenv() (Java) or process.env (NodeJS). For CI platforms, configure secret masking so that values do not appear in logs. When testing links that contain sensitive data (e.g., password‑reset tokens), generate a one‑time token in a setup step, pass it as a query parameter, and immediately invalidate it after the test asserts the expected UI state.

5. Writing Stable Deep Link Tests

5.1 Locator Strategies for Deep Link Entry Points

The first step in a deep‑link test is to trigger the link. If you are testing a custom scheme, you can launch an intent directly:


// Android – Appium Java
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("myapp://shop/checkout?orderId=123"));
driver.startActivity(intent);

For Universal Links on iOS, use the openURL method provided by the XCUITest driver:


// iOS – Appium Java
Map<String, String> args = new HashMap<>();
args.put("url", "https://example.com/checkout?token=abc");
driver.executeScript("mobile: openUrl", args);

When the link is presented via a UI element (e.g., a “Open in App” button in an email webview), locate that button using a stable accessibility id or test‑id attribute rather than relying on positional XPath. Example with Playwright:


// Playwright TypeScript
await page.getByTestId('open-in-app-button').click();
await page.waitForURL('**/checkout/**');

5.2 Handling Asynchronous Navigation and Waits

Deep link handling often involves asynchronous processes: splash screen animation, native bridge initialization, or WebView load. Avoid static Thread.sleep; instead, wait for a deterministic condition. In Appium you can use WebDriverWait with an ExpectedCondition that checks for the presence of a UI element unique to the target screen:


new WebDriverWait(driver, Duration.ofSeconds(15))
    .until(ExpectedConditions.visibilityOfElementLocated(
        By.id("order_summary_screen")));

In Playwright, leverage auto‑waiting combined with explicit expectations:


await expect(page.getByRole('heading', { name: /Order #123/ })).toBeVisible();

If the app uses a loading indicator, wait for its disappearance:


new WebDriverWait(driver, Duration.ofSeconds(10))
    .until(ExpectedConditions.invisibilityOfElementLocated(By.id("loading_spinner")));

5.3 Data Setup and Teardown Patterns

Parameterize your test method to accept the deep link string and expected assertions. Use a data provider (TestNG) or @ParameterizedTest (JUnit 5) to feed a CSV or JSON file containing variations:


@DataProvider(name = "deepLinks")
public Object[][] deepLinks() {
    return new Object[][] {
        {"myapp://shop/checkout?orderId=1&promo=SPRING", "Order #1", "SPRING applied"},
        {"myapp://shop/checkout?orderId=2", "Order #2", null}
    };
}

In the @BeforeMethod (or beforeEach) launch the app in a clean state (clear app data, logout any existing session). In the @AfterMethod capture a screenshot on failure and, if the test created server‑side state (e.g., placed an order), call a cleanup API to delete it. This guarantees idempotency across runs.

5.4 Flaky Test Mitigation Techniques

Flakiness in deep‑link tests usually stems from timing, device state, or network variability. Mitigate by:

  1. Deterministic device state – start each test with adb shell pm clear or simulator erase.
  2. Network throttling – use the device farm’s network profile to emulate 3G or LTE consistently.
  3. Retry wrapper – wrap the assertion in a lightweight retry loop (max 2 attempts) with exponential backoff, but only for known intermittent issues such as occasional WebKit race conditions.
  4. Log‑based verification – in addition to UI checks, assert that a specific logcat line (DeepLinkHandler: Received checkout intent) appears, which is less UI‑dependent.
  5. Avoid shared resources – do not rely on a singleton server stub that retains state between tests; reset the stub in a beforeEach hook.

5.5 Example Test Script (Appium + Java)

Below is a complete, ready‑to‑run Appium test that validates a checkout deep link on Android. It assumes an Appium server running locally on port 4723 and an emulator or device connected via adb.


package com.example.deeplink;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Duration;

@RunWith(Parameterized.class)
public class CheckoutDeepLinkTest {

    private AppiumDriver driver;
    private final String deepLink;
    private final String expectedOrderId;
    private final String expectedPromo;

    public CheckoutDeepLinkTest(String deepLink, String expectedOrderId, String expectedPromo) {
        this.deepLink = deepLink;
        this.expectedOrderId = expectedOrderId;
        this.expectedPromo = expectedPromo;
    }

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {"myapp://shop/checkout?orderId=1001&promo=WELCOME", "1001", "WELCOME"},
                {"myapp://shop/checkout?orderId=2005", "2005", null}
        });
    }

    @Before
    public void setUp() throws MalformedURLException {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("platformName", "Android");
        caps.setCapability("deviceName", "Pixel_4_API_33");
        caps.setCapability("appPackage", "com.example.myapp");
        caps.setCapability("appActivity", "com.example.myapp.MainActivity");
        caps.setCapability("automationName", "UiAutomator2");
        caps.setCapability("noReset", false); // fresh state each test
        driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps);
    }

    @Test
    public void verifyCheckoutScreen() {
        // Launch the deep link via intent
        Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(deepLink));
        driver.startActivity(viewIntent);

        // Wait for the checkout screen to appear
        new WebDriverWait(driver, Duration.ofSeconds(20))
                .until(ExpectedConditions.visibilityOfElementLocated(
                        By.id("checkout_order_id")));

        // Verify order ID
        String orderIdText = driver.findElement(By.id("checkout_order_id")).getText();
        assert orderIdText.equals("Order #" + expectedOrderId) :
                "Order ID mismatch: expected Order #" + expectedOrderId + ", got " + orderIdText;

        // Verify promo if applicable
        if (expectedPromo != null) {
            String promoText = driver.findElement(By.id("checkout_promo")).getText();
            assert promoText.equals("Promo: " + expectedPromo) :
                    "Promo mismatch: expected Promo: " + expectedPromo + ", got " + promoText;
        } else {
            // Ensure promo view is gone or shows default
            boolean promoVisible = driver.findElements(By.id("checkout_promo")).size() > 0;
            assert !promoVisible : "Promo should not be visible when none supplied";
        }
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Key points: the test is parameterized, clears app state before each iteration, uses explicit waits, and validates both UI text and conditional visibility. Replace the resource IDs with those from your application, and adjust the capabilities to match your test lab or your device farm configuration.

6. Autonomous Exploration Bootstrapping Deep Link Tests

6.1 How SUSA Discovers Deep Links Without Scripts

SUSA starts by installing the app on a device or emulator, then initiates a guided exploration that treats every clickable element as a potential deep‑link source. It monitors the Android Intent broadcast or iOS openURL callbacks to capture any URI the app attempts to handle. When it observes a link that leads to a new activity or view controller, it records the full URL, the UI state before and after the launch, and any query parameters extracted from the intent. The exploration repeats with different personas (curious, impatient, power user) to surface links that may only be reachable after certain UI states (e.g., after a promotional banner appears).

Because SUSA does not rely on pre‑written test cases, it can discover links buried in deep‑nested menus, share sheets, or dynamic webviews that are often missed when drafting test plans manually. The output is a set of JSON files each containing: { "url": "...", "preState": { "screen": "...", "elements": [...] }, "postState": { "screen": "...", "expectedAssertions": [...] } }.

6.2 Generating Baseline Test Suites

From the exploration JSON, SUSA’s CLI command susatest generate --framework appium --language java emits a starter test class for each unique URL. The generated code mirrors the pattern shown in section 5.5: it launches the link via startActivity (Android) or mobile: openUrl (iOS), waits for a stable element identified by a heuristic (largest text change, new fragment tag), and then asserts that the UI contains at least one element whose content differs from the pre‑state. This gives you a working smoke test for every discovered link without writing a single line of assertion logic yourself.

You can then enrich the generated tests: replace the generic “any change” assertion with domain‑specific checks (order ID, price, error message), add data‑driven variations, and incorporate server stubs for network calls. Because the baseline is under version control, you treat it as any other test source—review, refactor, and extend.

6.3 Cross‑Session Learning and Regression Guardrails

After each test run, SUSA records which screens were visited, which links resulted in a crash or an ANR, and which links led to a dead end (no new UI elements). On subsequent runs it prioritizes unexplored paths and deprioritizes already‑validated stable routes, effectively reducing test execution time while increasing coverage. If a previously passing deep link starts failing (e.g., returns to the home screen instead of the expected checkout), SUSA flags it as a regression and adds it to the failure report with a before/after screenshot diff.

This learning loop means that the cost of maintaining a deep‑link test suite diminishes over time: the platform remembers which links are flaky and which are stable, allowing you to focus manual effort on the truly problematic areas.

6.4 When to Combine Autonomous Runs with Hand‑Written Tests

Use autonomous exploration as the first line of defense for every release candidate: run a short SUSA smoke suite on every pull request to catch broken links introduced by UI refactors or manifest changes. Reserve hand‑written Appium or Playwright tests for scenarios that require precise data setup (e.g., testing a link that carries a JWT token that must be validated against a mock auth server) or for complex assertions that involve multiple screens and asynchronous events. The hybrid strategy gives you broad, fast coverage plus deep, reliable validation where it matters most.

7. Running Tests in CI and Reporting

7.1 Pipeline Stages (build, deploy, test, report)

A typical CI workflow for deep‑link testing consists of four stages:

  1. Build – compile the APK or IPA, run unit tests, and produce an artifact.
  2. Deploy – push the artifact to a device farm (e.g., upload to BrowserStack with browserstack-android-test CLI) or to an internal test lab.
  3. Test – execute the test suite via a CLI step; collect JUnit XML, logs, screenshots, and video.
  4. Report – publish the JUnit report to the CI UI, upload artifacts to a storage bucket, and optionally post a summary to Slack or Teams.

If you use SUSA, insert an exploratory step after Deploy and before Test: run susatest explore --app --personas curious,impatient --duration 5m to generate or update baseline tests, then run the generated suite in the Test stage.

7.2 Parallel Execution and Device Farm Strategies

Deep‑link tests are largely independent, making them ideal for parallel sharding. Most device farms allow you to specify a number of concurrent sessions; allocate each session a unique subset of the data‑provider CSV to avoid duplicate work. For example, with 4 parallel devices you can split a 120‑row CSV into four 30‑row files and pass the file path as an environment variable to the test runner.

When testing Universal Links that require a domain association file, ensure each parallel device has the same network configuration (e.g., same /etc/hosts entry or same mock server reachable via Wi‑Fi). Some farms provide a “network profile” feature you can apply uniformly across all sessions.

7.3 Capturing Logs, Screenshots, and Video

Configure Appium to automatically pull logcat after each test:


String logcat = driver.manage().log().get(LogType.LOGCTRL).get();
Files.writeString(Paths.get("build/logs/" + testName + ".log"), logcat);

For screenshots on failure, use a TestRule or @After method:


@After
public void tearDown(ITestResult result) {
    if (result.getStatus() == ITestResult.Failure) {
        File src = driver.getScreenshotAs(OutputType.FILE);
        try {
            FileUtils.copyFile(src, new File("screenshots/" + result.getName() + ".png"));
        } catch (IOException e) { /* handle */ }
    }
}

In Playwright, enable video and trace:


await context.tracing.start({screenshots:true, snapshots:true, sources:true});
// ... test ...
await context.tracing.stop({path: `trace-${testInfo.title}.zip`);

These artifacts are invaluable for debugging flaky runs and for providing evidence to developers when a bug is reproduced.

7.4 Integrating Results with Test Management Tools

Most CI systems can publish JUnit XML to tools like Zephyr, Xray, or TestRail. Ensure your test framework emits a proper XML file (testng-results.xml or surefire-reports/*.xml). Map each test case to a requirement or user story in your tracker; this gives traceability from a deep‑link requirement (“User can reset password via email link”) to an automated verification.

If you use SUSA, the exploration run also produces a CSV summary (susatest-report.csv) that you can import as a custom test result type, marking each discovered link as either “passed”, “failed – crash”, or “failed – no UI change”.

7.5 Sample GitHub Actions Workflow

Below is a complete workflow that builds an Android APK, uploads it to Firebase Test Lab, runs an Appium test suite, and posts a Slack notification on failure.


name: Deep Link CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test-deeplinks:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: '17'
          cache: gradle

      - name: Build APK
        run: ./gradlew assembleDebug

      - name: Upload to Firebase Test Lab
        uses: w9jds/firebase-test-lab-action@v1
        with:
          app-path: app/build/outputs/apk/debug/app-debug.apk
          device-model: pixel4
          os-version: '33'
          locale: en
          orientation: portrait

      - name: Run Appium tests
        run: |
          npm install -g appium
          appium &  # start Appium server in background
          mvn test -DdeepLinkData=src/test/resources/deeplinks.csv

      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: test-artifacts
          path: ./artifacts

      - name: Post failure to Slack
        if: failure()
        uses: slackapi/slack-github-action@v1.23.0
        with:
          payload: |
            {
              "text": ":rotating_light: Deep link tests failed on ${{ github.sha }}. See artifacts for details."
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Adjust the mvn test command to pass the path to your CSV data provider, and replace the Firebase Test Lab step with your preferred farm (BrowserStack, Sauce Labs, etc.). The workflow demonstrates how you can keep the entire deep‑link validation loop inside a single CI definition.

8. Checklist and Best Practices

8.1 Pre‑Flight Checklist

ItemWhy it mattersHow to verify
Device/emulator is wiped before each runPrevents stale state (logged‑in user, cached tokens) from masking link failuresRun adb shell pm clear or simulator erase as a pre‑step
Intent‑filter or apple‑app‑site‑association file is correctly deployedEnsures the OS can resolve the scheme/host to your appUse adb shell am start -W -a android.intent.action.VIEW -d "myapp://test" and check"` and verify activity launches
Test data (tokens, IDs) is unique per parallel shardAvoids collisions when multiple devices hit the same backend endpointGenerate UUIDs in a @BeforeMethod and pass them as query params
Server stubs are reset between testsGuarantees deterministic responsesCall WireMock reset() or Playwright route.fulfill() with fresh JSON in beforeEach
Test timeouts are tuned to device speedPrevents false timeouts on slower emulatorsStart with 20 s, then adjust based on 95th‑percentile duration from past runs
Test results are archived (logs, screenshots, video)

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