Deep Link Testing

A deep link is a link that opens a specific screen inside a mobile app instead of its start screen. Tapping https://shop.example.com/orders/A-1042 in a shipping email should show order A-1042 in the shop app. Links reach users through email, SMS, QR codes, push notifications and other apps, and each one is an entry point that skips the app’s normal navigation.

Deep link testing checks that every such entry point lands on the right screen with the right data in every state the user can be in: app installed or not, running or closed, user signed in, signed out or signed in as someone else. It builds on the mobile scenarios you already know and adds the platform rules that decide whether a link opens the app at all.

Why it matters

Deep links carry the moments that matter: a campaign, a password reset, a delivery update. When one breaks, the failure is quiet. The user lands on the home screen, on a web page that says "not found", or worse on someone else’s data, and the team hears about it through support tickets. Many causes sit outside the app’s code, in a file on the website, a signing certificate or a redirect added by an email tool, so ordinary UI tests that start inside the app do not catch them.

A deep link is also input from outside the app. Anyone can craft one, so the app must validate it and must never skip sign-in or perform an action without confirmation because a link asked it to. For the tester this is a compact, high-value area: a small, well-chosen set of combinations covers the real risks once you know which states and platform rules to combine.

Installed or not, signed in or not

Test the same link in several states, because each state takes a different path. Not installed: a custom-scheme link fails, while a verified https link opens the website at the same URL, so that URL must be a real page, ideally with a way to get the app, never a 404. Installed but not verified: the website opens although the app is there, the classic sign of a wrong fingerprint or a broken association file. Installed in an older version: the app must fall back to a sensible screen for a path it does not know, not crash.

How the app is running matters too. A link that arrives while the app is closed starts it from scratch; one that arrives while it is in the background or showing another screen reaches different code. Test both, and check where Back leads afterwards.

Then the user. Signed out: sign-in appears and, after it succeeds, the linked screen opens, not the home screen. Signed in as the owner: the right record opens. Signed in as another user: a clear not-available message and none of the owner’s data, enforced by the server and not only hidden by the app. Session expired or first launch after install: re-authentication, onboarding or a permission prompt must not swallow the link.

Try it: Pairwise Testing Generator

Triggering links and checking verification

Tap links from the channels users actually use: the email app, SMS, a chat app, a QR code scanned with the camera, a push notification. Some apps open links in their own in-app browser, which may not hand them to your app, and email click tracking often wraps a link in a redirect through another domain that is not associated with your app. Test the link exactly as it is sent, not the clean URL from the ticket.

For fast, repeatable checks use the platform tools. On Android, adb fires a link and lets the system choose the handler, and since Android 12 it shows and re-runs domain verification. On the iOS Simulator, simctl opens a URL; on a real iPhone, paste the link into Notes or Messages and tap it.

# Android: fire the link and let the system choose the app (no package name)
adb shell am start -W -a android.intent.action.VIEW \
  -c android.intent.category.BROWSABLE \
  -d "https://shop.example.com/orders/A-1042"

# Android 12+: domain verification state, then a fresh verification
adb shell pm get-app-links com.example.shop
adb shell pm verify-app-links --re-verify com.example.shop

# iOS Simulator: open the link through the system
xcrun simctl openurl booted "https://shop.example.com/orders/A-1042"

Try it: Redirect Checker

Worked example: the order link in a shipping email

Requirement: "The shipping email links to the order. With the app, the link opens the order details; without it, the order page on the website." The link is https://shop.example.com/orders/A-1042 and the order belongs to Sara. Start from a decision table over four true/false conditions: installed, verified, signed in and owner. Each column of the table is one combination of the four with the screen the link should land on, and the columns give the core cases; the lesson on decision table testing shows how to build one and merge columns where a condition makes no difference. Then add running-state and channel variations to the riskiest columns instead of multiplying everything.

  • Installed, Sara signed in, app closed: order A-1042 opens, and Back leads to the orders list as agreed.
  • Same, with the app in the background on the cart: order A-1042 opens and the cart is unchanged.
  • Installed, signed out: sign-in appears; after Sara signs in, order A-1042 opens, not the home screen.
  • Installed, another user signed in: a not-available message, no data from A-1042, and the API refuses the request for that order.
  • Not installed: the order page opens on the website (after web sign-in) with a link to the store; no 404.
  • A build signed with a key missing from assetlinks.json: the website opens and pm get-app-links does not show the domain as verified. Report it against the configuration, not the screen.
  • Unknown, empty or overlong id (/orders/A-9999, /orders/, 300 characters): a clear error screen, no crash.
  • The real email, through its click-tracking redirect, in each supported email app: the same result as the clean link.

Try it: Decision Table Generator Try it: Test Case Generator

Common mistakes

  • Testing only with the app installed and the user signed in, the one state developers have already tried.
  • Firing links only with adb aimed at the app’s package, or typing them into a browser, which bypasses or changes the platform’s link handling; tap them where users do.
  • Reporting "the website opened" as an app defect without checking verification first; the cause is usually the association file, the fingerprint or a redirect.
  • Accepting the home screen after sign-in as a pass: the user wanted the order, so landing anywhere else is a failure.
  • Forgetting older app versions, which still receive links and, on iOS, the current association file.
  • Trusting link parameters: a link must never skip sign-in, show another user’s data, or pay, delete or send anything without confirmation.

The QA perspective

Ask early for a deep link inventory: every link the product sends or prints, its URL pattern, the target screen, the fallback and who owns the association files. Agree with developers and the product owner where Back leads after a link, what an unknown path shows and whether deferred deep linking is in scope. Add a check of both association files to the release checklist, because a website deploy can break links without any app change.

In a defect report, record the link exactly as sent, the channel it was tapped from, the device, OS, app version and build, whether the app was installed, running or closed, the signed-in user and the verification state. Probe security behaviour, such as a link that bypasses sign-in, only in apps your team owns or has written permission to test, against the MASVS platform-interaction requirements your team follows.

Practice exercise

Design deep link tests for a promotion SMS

Your task

A shop sends the SMS below. Offers are public, but a code can only be applied to a signed-in user’s cart, and an offer can end before the user taps the link.

Design the test cases: the states you combine, the expected result of each, and the checks you run before you blame the app for a failure.

Material
SMS:
Your 10% code is waiting: https://shop.example.com/offers/ramadan?code=SAVE10

Rule:
With the app, the link opens the offer screen and applies the code to the cart.
Without the app, it opens the offer page on the website.
Steps
  1. List the states: installed or not, verified or not, app closed or in the background, user signed in or out.
  2. Write the expected result for each meaningful combination, including what happens to the code.
  3. Add invalid input: a missing, unknown or lower-case code, and an offer that has ended.
  4. List the checks that separate a configuration fault from an app defect.
Show the model answer
Model answer

Installed and signed in, with the app closed and again in the background: the offer screen opens and the cart shows SAVE10 applied once; tapping the link a second time does not apply it twice.

Installed, signed out: the offer opens without sign-in because offers are public. Applying the code asks for sign-in, after which the code is applied and the user is back on the offer or the cart, not the home screen.

Not installed: the website offer page opens with the code, plus a way to get the app; no 404. Keeping the code through a store install is deferred deep linking and needs its own requirement.

Invalid input: no code parameter, an unknown code, save10 in lower case (accepted or rejected as the rules say) and an ended offer each give a clear message; a wrong code never empties the cart.

Installed but not verified: the website offer page opens instead of the app, a configuration fault rather than an app defect. Before you blame the app for this or any other failure, run the checks: tap the link in the SMS app, not the browser; run pm get-app-links on Android; confirm both association files load over HTTPS without redirects, the iOS file covers /offers/* and the Android intent filter declares that path; and trace any link shortener for a redirect through another domain.

Frequently asked questions

What is the difference between a deep link, an App Link and a Universal Link?

A deep link is any link that opens a specific place in an app. Android App Links and iOS Universal Links are deep links on ordinary https URLs whose domain the platform has verified for the app, so they open the app when it is installed and the website otherwise. A custom-scheme link such as shop:// is a deep link without that verification.

Why does the link open the website when the app is installed?

Usually the platform did not verify the domain for this build: the fingerprint in assetlinks.json does not match the signing key, a file is missing, invalid or redirected, or the domain is missing from the intent filter or the associated domains. On iOS it is also expected when the URL was typed into Safari or tapped on a page of the same site. Check verification before you report.

Can I test deep links on emulators and simulators?

Many checks, yes: adb works on the Android emulator and xcrun simctl openurl on the iOS Simulator. Keep at least one pass on real devices with the real channels, such as email apps, in-app browsers, the camera’s QR scanner and store installs, because they behave differently there or exist only there.

Should deep link tests be automated?

The routing, yes: a UI test can open each link pattern on an emulator or simulator and assert the screen, which catches routing regressions on every build, and a script can fetch both association files after each website deploy. Channel behaviour and the not-installed path usually stay manual and run before a release or a campaign.

References

Practise with Mutqan tools

Build decision tables from conditions and actions, generate all rule combinations and collapse impossible ones.

Test Design

Break a URL into protocol, host, port, path, query parameters, fragment and credentials.

Web

Validate JSON syntax and get precise error locations with helpful fix suggestions.

JSON & Data

Trace redirect chains (301/302/307/308) and see every hop, status and final URL.

Web

Generate structured test cases (id, title, preconditions, steps, expected result, priority) from a feature description and field list.

Test Design

Go deeper

Your progress on this topic

Part of the QA Roadmap