Deep links can break in subtle ways: working on one platform but not another, opening in some apps but not others, handling cold starts but failing on warm starts. A systematic testing approach catches these issues before your users do.
This guide provides a testing matrix, manual testing procedures, and automation patterns for verifying deep links across iOS and Android. For platform-specific troubleshooting, see the Tolinku troubleshooting guide. For testing tools, see Best Deep Link Testing Tools.
The Testing Matrix
Every deep link test has four dimensions:
- Platform: iOS, Android
- Link type: Universal Link, App Link, custom scheme, web fallback
- App state: Cold start (not running), warm start (backgrounded), not installed
- Source app: Safari/Chrome, Messages, Email, social apps, QR scanner
A full matrix for a single link path looks like this:
| Scenario | iOS | Android |
|---|---|---|
| Cold start from Messages | Test | Test |
| Cold start from Mail | Test | Test |
| Cold start from Safari/Chrome | Test | Test |
| Warm start from Messages | Test | Test |
| Warm start from Safari/Chrome | Test | Test |
| App not installed (web fallback) | Test | Test |
| App not installed (deferred deep link) | Test | Test |
| Custom scheme from another app | Test | Test |
| QR code scan | Test | Test |
For each cell, verify:
- The app opens (or the correct fallback occurs)
- Navigation lands on the correct screen
- Parameters are passed correctly
Manual Testing Procedures
iOS Testing
Prerequisites:
- Physical iPhone (not just Simulator; some Universal Link behaviors differ)
- App installed via TestFlight or development build
- AASA file deployed and cached by Apple
Test 1: Universal Link from Notes
- Open Notes
- Paste:
https://go.yourapp.com/product/123 - Tap the link
- Expected: App opens, Product screen with ID 123
Test 2: Universal Link from Messages
- Send
https://go.yourapp.com/product/123to yourself via iMessage - Tap the link in the conversation
- Expected: App opens, Product screen
Test 3: Cold start
- Force-quit the app (swipe up in app switcher)
- Tap a link from Notes or Messages
- Expected: App launches from scratch and navigates to the correct screen
Test 4: Warm start
- Open the app, navigate to any screen
- Press Home to background the app
- Tap a link from Messages
- Expected: App comes to foreground and navigates to the linked screen
Test 5: Safari address bar (negative test)
- Type
https://go.yourapp.com/product/123directly in Safari's address bar - Expected: Opens in Safari (Universal Links don't trigger from typed URLs; this is by design)
Test 6: Web fallback
- Uninstall the app
- Tap a link
- Expected: Opens the web fallback URL in Safari
Android Testing
Prerequisites:
- Physical Android device (emulator works for most tests, but real device recommended)
- App installed (debug or release build)
- assetlinks.json deployed
Test 1: App Link from ADB
adb shell am start -a android.intent.action.VIEW \
-d "https://go.yourapp.com/product/123" \
com.yourapp
Expected: App opens to Product screen. No disambiguation dialog.
Test 2: From Chrome
- Open Chrome
- Navigate to a page that contains a link to
https://go.yourapp.com/product/123 - Tap the link
- Expected: App opens directly
Note: Pasting a URL in Chrome's address bar may not trigger App Links. The link must be tapped from a page context.
Test 3: From Messages
- Send the link to yourself via SMS
- Tap it from the messaging app
- Expected: App opens to correct screen
Test 4: Verification status
adb shell pm get-app-links com.yourapp
Expected output includes verified for your domain.
Test 5: Web fallback
- Uninstall the app
- Tap a link from Messages
- Expected: Opens in Chrome to the web fallback URL
Testing Deferred Deep Links
Deferred deep linking requires a full install cycle, making it harder to test.
Manual Test
- Uninstall the app completely
- Clear browser cookies and data
- Tap
https://go.yourapp.com/product/123 - The browser opens (app not installed)
- You're redirected to the App Store / Play Store
- Install the app
- Open the app
- Expected: App navigates to Product screen with ID 123 on first launch
Tips for Reliable Tests
- Use a different network: The fingerprinting system matches device IP, user agent, and other signals. Testing from the same Wi-Fi as your development machine may cause false matches. Use cellular data or a different network.
- Act quickly: Most platforms have a 30-minute to 24-hour matching window. Install the app shortly after clicking the link.
- Fresh device state: Clear app data, browser data, and any SDK cache before testing.
- Test both platforms: Deferred deep linking uses different fingerprinting signals on iOS vs Android. A match on one platform doesn't guarantee a match on the other.
Testing OG Previews
When users share your deep links on social media or messaging apps, the link preview (title, description, image) should render correctly.
Use the Sharing Debugger to check how Facebook renders your link. It also lets you clear the cache.
Twitter/X
Use the Card Validator to preview your link's Twitter card.
Use the Post Inspector to check LinkedIn's rendering.
iMessage
Send the link to yourself via iMessage. The preview should show the OG title, description, and image.
Send the link in a WhatsApp chat. WhatsApp fetches its own preview; verify it matches your OG tags.
Automated Testing
URL Validation Script
Write a script that verifies all your deep link routes resolve correctly:
#!/bin/bash
# test-deep-links.sh
DOMAIN="https://go.yourapp.com"
ROUTES=(
"/product/123"
"/invite/abc"
"/promo/summer-sale"
"/category/shoes"
)
for route in "${ROUTES[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" "$DOMAIN$route")
if [ "$status" = "200" ] || [ "$status" = "301" ] || [ "$status" = "302" ]; then
echo "PASS: $route (HTTP $status)"
else
echo "FAIL: $route (HTTP $status)"
fi
done
AASA and assetlinks Validation
#!/bin/bash
# validate-config.sh
DOMAIN="go.yourapp.com"
# Check AASA
echo "Checking AASA..."
aasa_status=$(curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN/.well-known/apple-app-site-association")
echo "AASA: HTTP $aasa_status"
# Check assetlinks
echo "Checking assetlinks..."
asset_status=$(curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN/.well-known/assetlinks.json")
echo "assetlinks: HTTP $asset_status"
# Validate AASA JSON
echo "Validating AASA JSON..."
curl -s "https://$DOMAIN/.well-known/apple-app-site-association" | python3 -m json.tool > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "AASA JSON: Valid"
else
echo "AASA JSON: INVALID"
fi
# Validate assetlinks JSON
echo "Validating assetlinks JSON..."
curl -s "https://$DOMAIN/.well-known/assetlinks.json" | python3 -m json.tool > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "assetlinks JSON: Valid"
else
echo "assetlinks JSON: INVALID"
fi
ADB Automation (Android)
Automate Android deep link testing with a script:
#!/bin/bash
# test-android-deep-links.sh
PACKAGE="com.yourapp"
ROUTES=(
"https://go.yourapp.com/product/123"
"https://go.yourapp.com/invite/abc"
"https://go.yourapp.com/promo/summer-sale"
)
for url in "${ROUTES[@]}"; do
echo "Testing: $url"
adb shell am start -a android.intent.action.VIEW -d "$url" "$PACKAGE"
sleep 3
# Take screenshot for visual verification
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png "screenshot_$(echo $url | sed 's/[^a-zA-Z0-9]/_/g').png"
echo "Screenshot saved"
# Press back to return to launcher
adb shell input keyevent KEYCODE_HOME
sleep 1
done
CI/CD Integration
Add deep link configuration validation to your CI pipeline:
# .github/workflows/deep-link-check.yml
name: Deep Link Config Check
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate AASA file
run: |
curl -sf "https://go.yourapp.com/.well-known/apple-app-site-association" | python3 -m json.tool
- name: Validate assetlinks.json
run: |
curl -sf "https://go.yourapp.com/.well-known/assetlinks.json" | python3 -m json.tool
- name: Check route resolution
run: bash scripts/test-deep-links.sh
Common Cross-Platform Discrepancies
Links Open in Browser on One Platform
If links open the app on iOS but not Android (or vice versa):
- Check that both AASA and assetlinks.json are deployed
- Verify the signing fingerprint on Android (this is the most common cause)
- iOS caches the AASA on install; try uninstalling and reinstalling
Different Navigation Behavior
If the same link navigates to different screens on iOS vs Android:
- Check your routing code for platform-specific branches
- Verify the URL is being parsed identically on both platforms
- Some URL parsing libraries handle trailing slashes or query string encoding differently
Timing Differences
Deferred deep links may resolve faster on one platform than the other due to different fingerprinting signals and matching algorithms. Test with realistic timing (not instantly after installing).
Testing Checklist
- Direct deep link works on iOS (cold start)
- Direct deep link works on iOS (warm start)
- Direct deep link works on Android (cold start)
- Direct deep link works on Android (warm start)
- Web fallback works on iOS (app not installed)
- Web fallback works on Android (app not installed)
- Deferred deep link works on iOS
- Deferred deep link works on Android
- Links work from Messages on both platforms
- Links work from email on both platforms
- OG previews render correctly on Facebook, Twitter, iMessage
- AASA and assetlinks.json return HTTP 200 with valid JSON
- No disambiguation dialog on Android (verified App Links)
- Parameters passed correctly on both platforms
For Universal Link testing specifics, see How to Test Universal Links. For the cross-platform overview, see Cross-Platform Deep Linking Guide.
Get deep linking tips in your inbox
One email per week. No spam.