How to Write Test Cases for Deep Links (With Examples)
How to Write Test Cases for Deep Links (With Examples)
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:
- ID – a unique identifier, often prefixed with the feature area (e.g.,
DLK-001for deep link test case one). - Title – a short, readable summary that appears in test management tools.
- Preconditions – the exact state the device or browser must be in before the test starts (app version, login status, cleared cache, network condition, etc.).
- Steps – a numbered list of actions the tester or automation script performs. Each step should be atomic and unambiguous.
- Expected Result – the observable outcome after the final step, expressed in terms of UI elements, logs, network calls, or state changes.
- Postconditions – any cleanup required to return the system to a neutral state (e.g., closing the app, removing test data).
- Priority – usually P0 (critical), P1 (high), P2 (medium), P3 (low) based on risk and impact.
- Tags – keywords that help filter and group tests (e.g.,
deep-link,android,auth,edge-case).
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
| ID | Title | Preconditions | Steps | Expected Result |
|---|---|---|---|---|
| DLK-001 | Launch home screen from custom scheme | App installed, not running, device unlocked | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://home" com.example.app | App opens to the home screen; main toolbar title reads “Home”. |
| DLK-002 | Open product detail screen with numeric ID | App installed, user logged out | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/42" com.example.app | Product detail screen shows product ID 42; “Add to Cart” button is enabled. |
| DLK-003 | Deep link with authentication token | Valid token valid123 pre‑generated; app not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout?token=valid123" com.example.app | Checkout screen loads; user sees order summary; no login prompt appears. |
| DLK-004 | Universal link opens in Safari and redirects to app | iOS device, universal link https://example.com/checkout configured, app installed | 1. Open Safari, navigate to https://example.com/checkout | iOS prompts to open the app; after acceptance, app launches to checkout screen. |
| DLK-005 | Deep link when app is already in foreground | App running on home screen | 1. From another app, share URL myapp://profile via the share sheet | App switches to profile screen; navigation stack reflects that home screen is still beneath it. |
| DLK-006 | Deep link with multiple query parameters | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://search?q= shoes&sort=price&page=2" com.example.app | Search results page displays shoes, sorted by price, showing page 2 of results. |
| DLK-007 | Deep link that launches a modal dialog | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://help?topic=faq" com.example.app | Help modal appears overlaying the home screen; background is dimmed; close button works. |
| DLK-008 | Deep link with UTF‑8 encoded path | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://%E2%9C%93%20gift" com.example.app | Screen titled “✓ gift” loads; special character renders correctly. |
| DLK-009 | Deep link that triggers a background sync | App not running, sync disabled in settings | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://sync/start" com.example.app | Sync service starts; a toast “Sync started” appears; background worker logs show activity. |
| DLK-010 | Deep link that opens a web view with external URL | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://web?url=https://example.org" com.example.app | Web 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
- DLK-001 establishes the baseline: the app must respond to the scheme and show the correct entry point.
- DLK-002 tests path‑based parameters; ensure your routing extracts the ID correctly and handles non‑existent IDs gracefully (covered later as a negative case).
- DLK-003 validates that token‑based authentication works without prompting the user to log in again—a common source of friction.
- DLK-004 shows the iOS universal‑link flow; the test must confirm that the OS presents the “Open in ‘App’?” banner and that the app receives the URL.
- DLK-005 checks that the app does not create a duplicate instance when already running; instead it should reuse the existing task and push the new screen onto the stack.
- DLK-006 and DLK-008 exercise query‑string and UTF‑8 handling, which often reveal encoding bugs.
- DLK-007 and DLK-009 test non‑UI outcomes such as dialogs and background services, proving that deep links can trigger headless work.
- DLK-010 ensures that when a deep link intends to load external content inside a web view, the URL is passed correctly and the web view respects navigation controls.
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
| ID | Title | Preconditions | Steps | Expected Result |
|---|---|---|---|---|
| DLK-011 | Malformed scheme (missing colon) | App installed, not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp/home" com.example.app | Activity not found; system shows “Opening URL failed” toast or logs an error; app does not launch. |
| DLK-012 | Unsupported custom scheme | App installed, not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "unknown://home" com.example.app | Same as DLK-011; no activity matches the intent. |
| DLK-013 | Missing required parameter (token) | App expects token for checkout; token omitted | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout" com.example.app | App redirects to login screen; login prompt appears; no checkout data is shown. |
| DLK-014 | Expired authentication token | Token expired123 is known to be invalid; app not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://checkout?token=expired123" com.example.app | App shows token‑error dialog or redirects to login; no secure data is displayed. |
| DLK-015 | Deep link to disabled feature (feature flag off) | Feature new‑checkout is disabled via remote config; app not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://new‑checkout" com.example.app | App shows a feature‑not‑available message or falls back to legacy checkout screen. |
| DLK-016 | URL with SQL injection attempt | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://product?id=1 OR 1=1" com.example.app | App treats the value as a plain string; no query is executed; product screen shows error or “not found”. |
| DLK-017 | Excessively long path ( > 2000 characters) | App not running | 1. Generate a 2500‑char path and execute the intent | App does not crash; logs show URI too long error; fallback to home screen or error dialog. |
| DLK-018 | Deep link with conflicting intents (two apps claim same scheme) | Two apps installed, both declare myapp:// intent filter | 1. Execute the intent; observe chooser | System presents a chooser dialog allowing the user to pick which app to open; no crash. |
| DLK-019 | Universal link that points to a non‑associated domain | iOS device, link https://evil.com/checkout not associated with your app | 1. Open Safari, navigate to https://evil.com/checkout | Safari loads the web page; no app banner appears; the app is not launched. |
| DLK-020 | Deep link that attempts to traverse file system | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://../../etc/passwd" com.example.app | App 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
- DLK-011 and DLK-012 verify that the OS correctly rejects intents that do not match any intent filter, preventing the app from being launched inadvertently.
- DLK-013 ensures that mandatory parameters are enforced; forgetting to check for a required token often leads to broken flows or security bypasses.
- DLK-014 checks token validity; an expired token must not grant access to protected screens.
- DLK-015 validates that feature flags are consulted before navigating to a feature‑specific screen.
- DLK-016 tests for injection‑style attacks; the app should never interpret query values as code or SQL.
- DLK-017 catches buffer‑overflow or URI‑length bugs that could crash the activity manager.
- DLK-018 confirms that the OS chooser works as expected when multiple apps claim the same scheme—a scenario that appears when users install a test or beta version alongside the production app.
- DLK-019 is the iOS counterpart: a universal link to a domain not associated with your app must stay in the browser.
- DLK-020 guards against path‑traversal attempts; the app should normalize or reject any
..segments.
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
| ID | Title | Preconditions | Steps | Expected Result | |
|---|---|---|---|---|---|
| DLK-021 | Very long query string ( > 4000 chars) | App not running | 1. Build a query string with 4200 characters and execute the intent | App truncates or rejects excess data gracefully; no crash; logs indicate length limit exceeded. | |
| DLK-022 | Mixed case scheme and host | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "MyApp://Home" com.example.app | App treats scheme and host case‑insensitively (per Android spec) and launches home screen. | |
| DLK-023 | Deep link with embedded spaces (not percent‑encoded) | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://search?q=hello world" com.example.app | App either rejects the URL (shows error) or decodes the space correctly; behavior must be documented. | |
| DLK-024 | Simultaneous deep links from two sources | App not running | 1. Device receives two intents within 200 ms: myapp://screenA and myapp://screenB via adb shell am start twice in quick succession | App 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-025 | Deep link after app update (version change) | App version 1.0 installed; user has version 2.0 APK ready | 1. Install version 2.0 over existing app without clearing data | 2. 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-026 | Deep link from background when device is locked | App running in background, device locked | 1. Send intent via adb while device is locked | After unlock, app launches to the target screen; lock screen does not interfere. | |
| DLK-027 | Deep link with null bytes in the URI | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "myapp://%00home" com.example.app | App rejects the URI; no crash; logs show illegal character. | |
| DLK-028 | Deep link that triggers a rotation change | App not running, auto‑rotate enabled | 1. Execute intent; immediately rotate device to landscape | Screen loads in landscape orientation; layout adapts without overlapping or clipping. | |
| DLK-029 | Deep link with HTTP scheme instead of custom | App not running | 1. Execute adb shell am start -W -a android.intent.action.VIEW -d "http://myapp.com/home" com.example.app | If the app declares an HTTP intent filter, it opens the corresponding screen; otherwise, the browser opens the URL. | |
| DLK-030 | Deep link that launches app while low memory warning is active | Device under memory pressure (use adb shell am send-trim-memory com.example.app MODERATE) | 1. Send low‑memory signal, then execute deep link | App 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
- DLK-021 and DLK-022 test the limits of the URI length and case sensitivity, both of which are defined by the platform but can be mishandled by custom routing logic.
- DLK-023 highlights a common mistake: accepting raw spaces instead of requiring
%20. The expected result should be documented explicitly because some teams choose to reject, others to decode. - DLK-024 probes race conditions when the app receives multiple intents before it has finished processing the first. The ideal behavior is to queue intents and handle them sequentially, but some implementations drop the second or corrupt the navigation stack.
- DLK-025 is crucial for release management: a deep link that pointed to a removed screen must not crash the app; instead, a graceful fallback or migration notice should be shown.
- DLK-026 validates that the lock screen does not block the delivery of the intent after unlock—a frequent source of “link didn’t work” reports.
- DLK-027 ensures that null bytes, which can be used in fuzzing attacks, are safely rejected.
- DLK-028 checks that configuration changes (orientation, locale) occurring simultaneously with deep link delivery do not break the UI.
- DLK-029 verifies that if you support HTTP(s) links for SEO or web‑to‑app conversion, the intent filter works and the app receives the URL; otherwise, the browser should handle it.
- DLK-030 confirms that the app remains functional under memory constraints, which is especially important for low‑end devices.
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 |
| High | Medium | P1 |
| Medium (UI glitch, non‑critical) | High | P2 |
| Low | Any | P3 |
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 ID | Description | Linked Test Case IDs |
|---|---|---|
| REQ-DL-01 | App shall launch home screen when scheme myapp://home is invoked | DLK-001 |
| REQ-DL-02 | App shall navigate to product screen with numeric ID | DLK-002 |
| REQ-DL-03 | App shall accept a valid auth token and skip login | DLK-003 |
| REQ-DL-04 | App shall reject missing token and show login | DLK-013 |
| REQ-DL-05 | App shall handle expired token gracefully | DLK-014 |
| REQ-DL-06 | App shall show feature‑not‑available message for disabled features | DLK-015 |
| REQ-DL-07 | App shall not crash on malformed URI | DLK-011, DLK-012 |
| REQ-DL-08 | App shall process UTF‑8 characters in path | DLK-008 |
| REQ-DL-09 | App shall queue multiple incoming intents | DLK-024 |
| REQ-DL-10 | App shall fallback gracefully after version upgrade | DLK-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>
-Wtellsamto wait for the launch to complete and return the final activity name, which you can assert against in a script.- Replace
with the full deep link (including scheme, host, path, query). is the application’s package name; omitting it launches the resolver, which may present a chooser.
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:
- 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