How to Write Test Cases for Deep Links (With Examples)

How to Write Test Cases for Deep Links (With Examples)

June 06, 2026 · 17 min read · How-To Guides

How to Write Test Cases for Deep Links (With Examples)

Deep links are a common way to launch a specific screen or perform an action inside a mobile or web application from an external source such as a message, email, or another app. Because they bypass the normal navigation flow, they often expose defects that never surface during regular UI testing. Writing effective test cases for deep links therefore requires a focused approach that treats the link itself as the entry point and validates that the application behaves correctly under a variety of conditions, data inputs, and device states. This guide walks you through the full process: from understanding the anatomy of a test case, through designing positive, negative, and edge cases, to setting up data, prioritizing work, executing manually or with automation, and finally augmenting your effort with autonomous exploration. Each section includes concrete examples, tables, and code snippets you can copy into your own test repository.

How to Write Test Cases for Deep Links (With Examples): Foundations

Understanding Deep Links

A deep link is a URI that contains enough information to direct the operating system to launch an application and navigate to a particular internal state. On Android, the URI is handled via an intent filter declared in the manifest; on iOS, universal links or custom schemes are used; on the web, the same concept appears as a URL that a single‑page app interprets via client‑side routing. The link may include query parameters, path fragments, or even JSON payloads encoded in the URL. Because the link can originate from any source, the application must be ready to handle it regardless of whether it is not running, running in the background, or already in the foreground showing a different screen.

Why Test Cases Matter

When a deep link fails, the user experiences a broken flow: they tap a link in a marketing email and land on a blank screen, or they are thrown into a login loop despite having a valid token. These failures hurt conversion, increase support load, and can even expose security issues if parameters are not validated. Test cases give you a repeatable way to verify that every supported link pattern works as intended, that unsupported or malformed links are rejected gracefully, and that the application state after the link is predictable. By documenting preconditions, steps, and expected results, you create a baseline that can be automated, reviewed by peers, and traced back to product requirements.

How to Write Test Cases for Deep Links (With Examples): Test Case Anatomy

ID, Title, Preconditions, Steps, Expected Result, Postconditions, Priority, Tags

A well‑structured test case contains the following fields:

Writing Clear Steps

Steps must be written from the perspective of an actor who has no knowledge of the implementation. Avoid phrases like “the app should handle the intent” and instead describe what the actor does: “From the home screen, open Chrome and paste the URL myapp://order/123?token=abc into the address bar, then press Enter.” If the step involves a command line tool, show the exact invocation. For example:


adb shell am start -W -a android.intent.action.VIEW -d "myapp://order/123?token=abc" com.example.app

Each step should produce a detectable change that can be verified in the next step or in the expected result. When you write steps, ask yourself: “If I followed these instructions on a clean device, would I know exactly what to do and what to look for?” If the answer is no, rewrite the step.

How to Write Test Cases for Deep Links (With Examples): Positive Test Cases

Positive test cases verify that the application correctly processes a valid deep link and reaches the intended screen or performs the intended action. Below is a worked set of examples that you can adapt to your own URI scheme.

Test Matrix: Positive Deep Link Cases

IDTitlePreconditionsStepsExpected Result
DLK-001Launch home screen from custom schemeApp installed, not running, device unlocked1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://home" com.example.appApp opens to the home screen; main toolbar title reads “Home”.
DLK-002Open product detail screen with numeric IDApp installed, user logged out1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/42" com.example.appProduct detail screen shows product ID 42; “Add to Cart” button is enabled.
DLK-003Deep link with authentication tokenValid token valid123 pre‑generated; app not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout?token=valid123" com.example.appCheckout screen loads; user sees order summary; no login prompt appears.
DLK-004Universal link opens in Safari and redirects to appiOS device, universal link https://example.com/checkout configured, app installed1. Open Safari, navigate to https://example.com/checkoutiOS prompts to open the app; after acceptance, app launches to checkout screen.
DLK-005Deep link when app is already in foregroundApp running on home screen1. From another app, share URL myapp://profile via the share sheetApp switches to profile screen; navigation stack reflects that home screen is still beneath it.
DLK-006Deep link with multiple query parametersApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://search?q= shoes&sort=price&page=2" com.example.appSearch results page displays shoes, sorted by price, showing page 2 of results.
DLK-007Deep link that launches a modal dialogApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://help?topic=faq" com.example.appHelp modal appears overlaying the home screen; background is dimmed; close button works.
DLK-008Deep link with UTF‑8 encoded pathApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://%E2%9C%93%20gift" com.example.appScreen titled “✓ gift” loads; special character renders correctly.
DLK-009Deep link that triggers a background syncApp not running, sync disabled in settings1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://sync/start" com.example.appSync service starts; a toast “Sync started” appears; background worker logs show activity.
DLK-010Deep link that opens a web view with external URLApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://web?url=https://example.org" com.example.appWeb view loads https://example.org; URL bar shows the correct address; back button returns to app.

*Each row above can be copied into a test‑management tool. Adjust the package name, scheme, and host to match your application.*

Commentary on the Positive Cases

How to Write Test Cases for Deep Links (With Examples): Negative Test Cases

Negative test cases confirm that the application rejects or safely handles invalid, unsupported, or malicious deep links. The goal is to avoid crashes, infinite loops, or unintended data exposure.

Test Matrix: Negative Deep Link Cases

IDTitlePreconditionsStepsExpected Result
DLK-011Malformed scheme (missing colon)App installed, not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp/home" com.example.appActivity not found; system shows “Opening URL failed” toast or logs an error; app does not launch.
DLK-012Unsupported custom schemeApp installed, not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "unknown://home" com.example.appSame as DLK-011; no activity matches the intent.
DLK-013Missing required parameter (token)App expects token for checkout; token omitted1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout" com.example.appApp redirects to login screen; login prompt appears; no checkout data is shown.
DLK-014Expired authentication tokenToken expired123 is known to be invalid; app not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout?token=expired123" com.example.appApp shows token‑error dialog or redirects to login; no secure data is displayed.
DLK-015Deep link to disabled feature (feature flag off)Feature new‑checkout is disabled via remote config; app not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://new‑checkout" com.example.appApp shows a feature‑not‑available message or falls back to legacy checkout screen.
DLK-016URL with SQL injection attemptApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://product?id=1 OR 1=1" com.example.appApp treats the value as a plain string; no query is executed; product screen shows error or “not found”.
DLK-017Excessively long path ( > 2000 characters)App not running1. Generate a 2500‑char path and execute the intentApp does not crash; logs show URI too long error; fallback to home screen or error dialog.
DLK-018Deep link with conflicting intents (two apps claim same scheme)Two apps installed, both declare myapp:// intent filter1. Execute the intent; observe chooserSystem presents a chooser dialog allowing the user to pick which app to open; no crash.
DLK-019Universal link that points to a non‑associated domainiOS device, link https://evil.com/checkout not associated with your app1. Open Safari, navigate to https://evil.com/checkoutSafari loads the web page; no app banner appears; the app is not launched.
DLK-020Deep link that attempts to traverse file systemApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://../../etc/passwd" com.example.appApp sanitizes the path; shows error or treats as invalid resource; no file access occurs.

*These cases can be automated using the same adb shell am start command, varying only the URI string.*

Commentary on the Negative Cases

How to Write Test Cases for Deep Links (With Examples): Boundary and Edge Cases

Boundary cases push the limits of input size, encoding, timing, and concurrency. Edge cases combine multiple factors that rarely appear in isolation but can surface in production under specific user behaviors or device states.

Test Matrix: Boundary and Edge Deep Link Cases

IDTitlePreconditionsStepsExpected Result
DLK-021Very long query string ( > 4000 chars)App not running1. Build a query string with 4200 characters and execute the intentApp truncates or rejects excess data gracefully; no crash; logs indicate length limit exceeded.
DLK-022Mixed case scheme and hostApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "MyApp://Home" com.example.appApp treats scheme and host case‑insensitively (per Android spec) and launches home screen.
DLK-023Deep link with embedded spaces (not percent‑encoded)App not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://search?q=hello world" com.example.appApp either rejects the URL (shows error) or decodes the space correctly; behavior must be documented.
DLK-024Simultaneous deep links from two sourcesApp not running1. Device receives two intents within 200 ms: myapp://screenA and myapp://screenB via adb shell am start twice in quick successionApp processes the first intent fully; second intent is either queued and handled after the first screen loads, or is ignored if the app locks navigation. No crash.
DLK-025Deep link after app update (version change)App version 1.0 installed; user has version 2.0 APK ready1. Install version 2.0 over existing app without clearing data2. Execute deep link that relied on a removed screen (e.g., myapp://old‑screen)App shows a migration screen or falls back to a relevant fallback; no crash; logs show handling of deprecated route.
DLK-026Deep link from background when device is lockedApp running in background, device locked1. Send intent via adb while device is lockedAfter unlock, app launches to the target screen; lock screen does not interfere.
DLK-027Deep link with null bytes in the URIApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://%00home" com.example.appApp rejects the URI; no crash; logs show illegal character.
DLK-028Deep link that triggers a rotation changeApp not running, auto‑rotate enabled1. Execute intent; immediately rotate device to landscapeScreen loads in landscape orientation; layout adapts without overlapping or clipping.
DLK-029Deep link with HTTP scheme instead of customApp not running1. Execute adb shell am start -W -a android.intent.action.VIEW -d "http://myapp.com/home" com.example.appIf the app declares an HTTP intent filter, it opens the corresponding screen; otherwise, the browser opens the URL.
DLK-030Deep link that launches app while low memory warning is activeDevice under memory pressure (use adb shell am send-trim-memory com.example.app MODERATE)1. Send low‑memory signal, then execute deep linkApp launches successfully; any heavy resources are lazily loaded; no OutOfMemoryError.

*These cases often require scripts to generate the malformed URIs or to manipulate device state. The steps column shows the essential actions; you can wrap them in Bash or Python loops for repeatability.*

Commentary on Boundary and Edge Cases

Test Data Setup and Environment

Configuring Intent Filters / Universal Links

Before you can run any deep‑link test, the application must declare the appropriate intent filters (Android) or associated domains entitlement (iOS). For Android, the manifest entry looks like:


<activity android:name=".ui.MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="myapp"/>
        <data android:host="home"/>
    </intent-filter>
</activity>

For iOS, the apple-app-site-association file hosted on your domain must contain:


{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "TEAMID.com.example.app",
                "paths": ["/checkout/*", "/product/*", "/**"]
            }
        ]
    }
}

Your test environment should verify that these files are reachable and correctly signed. A simple curl check can be part of your CI pipeline:


curl -s https://example.com/apple-app-site-association | jq .

Mock Servers for Parameter Validation

Many deep links carry tokens or IDs that the app validates against a backend. To avoid hitting real services during test execution, spin up a lightweight mock server (e.g., using WireMock, MockServer, or a simple Express.js route). Example WireMock stub for token validation:


{
  "request": {
    "method": "GET",
    "urlPath": "/auth/validate",
    "queryParameters": {
      "token": {
        "matches": "valid123"
      }
    }
  },
  "response": {
    "status": 200,
    "jsonBody": { "valid": true, "userId": "abc123" },
    "headers": { "Content-Type": "application/json"}
  }
}

Start the mock server on a known port and configure the app (via build flavors or remote config) to point to that endpoint during test runs.

Device States (Clearing Data, Installing Specific Version)

Consistent preconditions are essential for repeatable results. Use the following adb commands to reset the device to a known state before each test case:


# Uninstall any existing version
adb uninstall com.example.app
# Install the APK under test
adb install -r path/to/app.apk
# Clear app data and cache
adb shell pm clear com.example.app
# Grant any runtime permissions required for the test
adb shell pm grant com.example.app android.permission.POST_NOTIFICATIONS

For iOS, you can use xcrun simctl to erase and boot a simulator:


xcrun simctl erase iPhone-14
xcrun simctl boot iPhone-14
xcrun simctl install booted path/to/App.app

Automating these steps in a test‑setup hook (JUnit @Before, TestNG @BeforeMethod, or a pytest fixture) guarantees that each test starts from a clean slate.

Prioritization and Traceability to Requirements

Risk‑Based Prioritization

Not all deep links carry the same risk. Assign priority using a simple matrix:

Impact (User)Likelihood (Failure)Priority
High (blocks core flow, e.g., payment)High (frequent misuse)P0
HighMediumP1
Medium (UI glitch, non‑critical)HighP2
LowAnyP3

Apply this matrix to each test case ID. For example, DLK-003 (valid token checkout) is P0 because a failure directly prevents revenue. DLK-018 (chooser dialog) is P2 because it is rare and does not block functionality.

Linking Test Cases to Requirements (Traceability Matrix)

Create a traceability table that maps each requirement (often stored in a tool like Jira, Azure DevOps, or a spreadsheet) to the test cases that verify it. Below is an example excerpt:

Requirement IDDescriptionLinked Test Case IDs
REQ-DL-01App shall launch home screen when scheme myapp://home is invokedDLK-001
REQ-DL-02App shall navigate to product screen with numeric IDDLK-002
REQ-DL-03App shall accept a valid auth token and skip loginDLK-003
REQ-DL-04App shall reject missing token and show loginDLK-013
REQ-DL-05App shall handle expired token gracefullyDLK-014
REQ-DL-06App shall show feature‑not‑available message for disabled featuresDLK-015
REQ-DL-07App shall not crash on malformed URIDLK-011, DLK-012
REQ-DL-08App shall process UTF‑8 characters in pathDLK-008
REQ-DL-09App shall queue multiple incoming intentsDLK-024
REQ-DL-10App shall fallback gracefully after version upgradeDLK-025

Maintaining this table in a living document helps auditors see coverage and assists product managers when they change a requirement—you can instantly see which test cases need updating.

Example Traceability Table (Markdown)

You can keep the matrix directly in your test repository as a markdown file for easy viewing:


| Requirement | Summary                               | Test Cases            |
|-------------|---------------------------------------|-----------------------|
| REQ-DL-01   | Home screen launch                    | DLK-001               |
| REQ-DL-02   | Product detail navigation             | DLK-002               |
| REQ-DL-03   | Token‑based checkout                  | DLK-003               |
| REQ-DL-04   | Missing token → login                 | DLK-013               |
| REQ-DL-05   | Expired token → error/login           | DLK-014               |
| REQ-DL-06   | Disabled feature fallback             | DLK-015               |
| REQ-DL-07   | Malformed URI handling                | DLK-011, DLK-012      |
| REQ-DL-08   | UTF‑8 path support                    | DLK-008               |
| REQ-DL-09   | Intent queuing                        | DLK-024               |
| REQ-DL-10   | Post‑upgrade fallback                 | DLK-025               |

Update this file whenever you add, modify, or retire a test case.

Manual Execution Techniques

Using ADB to Send Intents

The most direct way to fire a deep link on an Android device or emulator is via the Android Debug Bridge. The generic pattern is:


adb shell am start -W -a android.intent.action.VIEW -d "<URI>" <package>

To verify the result, you can inspect the current activity:


adb shell dumpsys activity activities | grep mResumedActivity

Or check for a specific UI element using uiautomator:


adb shell uiautomator dump /tmp/view.xml
grep -i "checkout" /tmp/view.xml

Using Safari / Chrome to Open Universal Links

On iOS simulators or physical devices, you can test universal links by opening Safari and navigating to the HTTPS URL. The steps are:

  1. Ensure the device is online

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