{"id":912,"date":"2026-04-26T09:00:00","date_gmt":"2026-04-26T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=912"},"modified":"2026-03-07T03:48:35","modified_gmt":"2026-03-07T08:48:35","slug":"cross-platform-testing-deep-links","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/cross-platform-testing-deep-links\/","title":{"rendered":"Testing Deep Links Across Platforms and Devices"},"content":{"rendered":"\n<p>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.<\/p>\n\n\n\n<p>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 <a href=\"https:\/\/tolinku.com\/docs\/troubleshooting\/common-issues\/\">Tolinku troubleshooting guide<\/a>. For testing tools, see <a href=\"https:\/\/tolinku.com\/blog\/deep-link-testing-tools\/\">Best Deep Link Testing Tools<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Testing Matrix<\/h2>\n\n\n\n<p>Every deep link test has four dimensions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Platform<\/strong>: iOS, Android<\/li>\n<li><strong>Link type<\/strong>: Universal Link, App Link, custom scheme, web fallback<\/li>\n<li><strong>App state<\/strong>: Cold start (not running), warm start (backgrounded), not installed<\/li>\n<li><strong>Source app<\/strong>: Safari\/Chrome, Messages, Email, social apps, QR scanner<\/li>\n<\/ol>\n\n\n\n<p>A full matrix for a single link path looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Scenario<\/th>\n<th>iOS<\/th>\n<th>Android<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Cold start from Messages<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>Cold start from Mail<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>Cold start from Safari\/Chrome<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>Warm start from Messages<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>Warm start from Safari\/Chrome<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>App not installed (web fallback)<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>App not installed (deferred deep link)<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>Custom scheme from another app<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<tr>\n<td>QR code scan<\/td>\n<td>Test<\/td>\n<td>Test<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>For each cell, verify:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The app opens (or the correct fallback occurs)<\/li>\n<li>Navigation lands on the correct screen<\/li>\n<li>Parameters are passed correctly<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Manual Testing Procedures<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS Testing<\/h3>\n\n\n\n<p><strong>Prerequisites:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Physical iPhone (not just Simulator; some Universal Link behaviors differ)<\/li>\n<li>App installed via TestFlight or development build<\/li>\n<li>AASA file deployed and cached by Apple<\/li>\n<\/ul>\n\n\n\n<p><strong>Test 1: Universal Link from Notes<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open Notes<\/li>\n<li>Paste: <code>https:\/\/go.yourapp.com\/product\/123<\/code><\/li>\n<li>Tap the link<\/li>\n<li>Expected: App opens, Product screen with ID 123<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 2: Universal Link from Messages<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Send <code>https:\/\/go.yourapp.com\/product\/123<\/code> to yourself via iMessage<\/li>\n<li>Tap the link in the conversation<\/li>\n<li>Expected: App opens, Product screen<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 3: Cold start<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Force-quit the app (swipe up in app switcher)<\/li>\n<li>Tap a link from Notes or Messages<\/li>\n<li>Expected: App launches from scratch and navigates to the correct screen<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 4: Warm start<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open the app, navigate to any screen<\/li>\n<li>Press Home to background the app<\/li>\n<li>Tap a link from Messages<\/li>\n<li>Expected: App comes to foreground and navigates to the linked screen<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 5: Safari address bar (negative test)<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Type <code>https:\/\/go.yourapp.com\/product\/123<\/code> directly in Safari&#39;s address bar<\/li>\n<li>Expected: Opens in Safari (Universal Links don&#39;t trigger from typed URLs; this is by design)<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 6: Web fallback<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Uninstall the app<\/li>\n<li>Tap a link<\/li>\n<li>Expected: Opens the web fallback URL in Safari<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Android Testing<\/h3>\n\n\n\n<p><strong>Prerequisites:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Physical Android device (emulator works for most tests, but real device recommended)<\/li>\n<li>App installed (debug or release build)<\/li>\n<li>assetlinks.json deployed<\/li>\n<\/ul>\n\n\n\n<p><strong>Test 1: App Link from ADB<\/strong><\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell am start -a android.intent.action.VIEW \\\n  -d &quot;https:\/\/go.yourapp.com\/product\/123&quot; \\\n  com.yourapp\n<\/code><\/pre>\n\n\n\n<p>Expected: App opens to Product screen. No disambiguation dialog.<\/p>\n\n\n\n<p><strong>Test 2: From Chrome<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open Chrome<\/li>\n<li>Navigate to a page that contains a link to <code>https:\/\/go.yourapp.com\/product\/123<\/code><\/li>\n<li>Tap the link<\/li>\n<li>Expected: App opens directly<\/li>\n<\/ol>\n\n\n\n<p>Note: Pasting a URL in Chrome&#39;s address bar may not trigger App Links. The link must be tapped from a page context.<\/p>\n\n\n\n<p><strong>Test 3: From Messages<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Send the link to yourself via SMS<\/li>\n<li>Tap it from the messaging app<\/li>\n<li>Expected: App opens to correct screen<\/li>\n<\/ol>\n\n\n\n<p><strong>Test 4: Verification status<\/strong><\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell pm get-app-links com.yourapp\n<\/code><\/pre>\n\n\n\n<p>Expected output includes <code>verified<\/code> for your domain.<\/p>\n\n\n\n<p><strong>Test 5: Web fallback<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Uninstall the app<\/li>\n<li>Tap a link from Messages<\/li>\n<li>Expected: Opens in Chrome to the web fallback URL<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Deferred Deep Links<\/h2>\n\n\n\n<p>Deferred deep linking requires a full install cycle, making it harder to test.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Manual Test<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Uninstall the app completely<\/li>\n<li>Clear browser cookies and data<\/li>\n<li>Tap <code>https:\/\/go.yourapp.com\/product\/123<\/code><\/li>\n<li>The browser opens (app not installed)<\/li>\n<li>You&#39;re redirected to the App Store \/ Play Store<\/li>\n<li>Install the app<\/li>\n<li>Open the app<\/li>\n<li>Expected: App navigates to Product screen with ID 123 on first launch<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Tips for Reliable Tests<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Use a different network<\/strong>: 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.<\/li>\n<li><strong>Act quickly<\/strong>: Most platforms have a 30-minute to 24-hour matching window. Install the app shortly after clicking the link.<\/li>\n<li><strong>Fresh device state<\/strong>: Clear app data, browser data, and any SDK cache before testing.<\/li>\n<li><strong>Test both platforms<\/strong>: Deferred deep linking uses different fingerprinting signals on iOS vs Android. A match on one platform doesn&#39;t guarantee a match on the other.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Testing OG Previews<\/h2>\n\n\n\n<p>When users share your deep links on social media or messaging apps, the link preview (title, description, image) should render correctly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Facebook<\/h3>\n\n\n\n<p>Use the <a href=\"https:\/\/developers.facebook.com\/tools\/debug\/\" rel=\"nofollow noopener\" target=\"_blank\">Sharing Debugger<\/a> to check how Facebook renders your link. It also lets you clear the cache.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Twitter\/X<\/h3>\n\n\n\n<p>Use the <a href=\"https:\/\/cards-dev.twitter.com\/validator\" rel=\"nofollow noopener\" target=\"_blank\">Card Validator<\/a> to preview your link&#39;s Twitter card.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LinkedIn<\/h3>\n\n\n\n<p>Use the <a href=\"https:\/\/www.linkedin.com\/post-inspector\/\" rel=\"nofollow noopener\" target=\"_blank\">Post Inspector<\/a> to check LinkedIn&#39;s rendering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">iMessage<\/h3>\n\n\n\n<p>Send the link to yourself via iMessage. The preview should show the OG title, description, and image.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WhatsApp<\/h3>\n\n\n\n<p>Send the link in a WhatsApp chat. WhatsApp fetches its own preview; verify it matches your OG tags.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Automated Testing<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">URL Validation Script<\/h3>\n\n\n\n<p>Write a script that verifies all your deep link routes resolve correctly:<\/p>\n\n\n\n<pre><code class=\"language-bash\">#!\/bin\/bash\n# test-deep-links.sh\n\nDOMAIN=&quot;https:\/\/go.yourapp.com&quot;\nROUTES=(\n  &quot;\/product\/123&quot;\n  &quot;\/invite\/abc&quot;\n  &quot;\/promo\/summer-sale&quot;\n  &quot;\/category\/shoes&quot;\n)\n\nfor route in &quot;${ROUTES[@]}&quot;; do\n  status=$(curl -s -o \/dev\/null -w &quot;%{http_code}&quot; &quot;$DOMAIN$route&quot;)\n  if [ &quot;$status&quot; = &quot;200&quot; ] || [ &quot;$status&quot; = &quot;301&quot; ] || [ &quot;$status&quot; = &quot;302&quot; ]; then\n    echo &quot;PASS: $route (HTTP $status)&quot;\n  else\n    echo &quot;FAIL: $route (HTTP $status)&quot;\n  fi\ndone\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">AASA and assetlinks Validation<\/h3>\n\n\n\n<pre><code class=\"language-bash\">#!\/bin\/bash\n# validate-config.sh\n\nDOMAIN=&quot;go.yourapp.com&quot;\n\n# Check AASA\necho &quot;Checking AASA...&quot;\naasa_status=$(curl -s -o \/dev\/null -w &quot;%{http_code}&quot; &quot;https:\/\/$DOMAIN\/.well-known\/apple-app-site-association&quot;)\necho &quot;AASA: HTTP $aasa_status&quot;\n\n# Check assetlinks\necho &quot;Checking assetlinks...&quot;\nasset_status=$(curl -s -o \/dev\/null -w &quot;%{http_code}&quot; &quot;https:\/\/$DOMAIN\/.well-known\/assetlinks.json&quot;)\necho &quot;assetlinks: HTTP $asset_status&quot;\n\n# Validate AASA JSON\necho &quot;Validating AASA JSON...&quot;\ncurl -s &quot;https:\/\/$DOMAIN\/.well-known\/apple-app-site-association&quot; | python3 -m json.tool &gt; \/dev\/null 2&gt;&amp;1\nif [ $? -eq 0 ]; then\n  echo &quot;AASA JSON: Valid&quot;\nelse\n  echo &quot;AASA JSON: INVALID&quot;\nfi\n\n# Validate assetlinks JSON\necho &quot;Validating assetlinks JSON...&quot;\ncurl -s &quot;https:\/\/$DOMAIN\/.well-known\/assetlinks.json&quot; | python3 -m json.tool &gt; \/dev\/null 2&gt;&amp;1\nif [ $? -eq 0 ]; then\n  echo &quot;assetlinks JSON: Valid&quot;\nelse\n  echo &quot;assetlinks JSON: INVALID&quot;\nfi\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">ADB Automation (Android)<\/h3>\n\n\n\n<p>Automate Android deep link testing with a script:<\/p>\n\n\n\n<pre><code class=\"language-bash\">#!\/bin\/bash\n# test-android-deep-links.sh\n\nPACKAGE=&quot;com.yourapp&quot;\nROUTES=(\n  &quot;https:\/\/go.yourapp.com\/product\/123&quot;\n  &quot;https:\/\/go.yourapp.com\/invite\/abc&quot;\n  &quot;https:\/\/go.yourapp.com\/promo\/summer-sale&quot;\n)\n\nfor url in &quot;${ROUTES[@]}&quot;; do\n  echo &quot;Testing: $url&quot;\n  adb shell am start -a android.intent.action.VIEW -d &quot;$url&quot; &quot;$PACKAGE&quot;\n  sleep 3\n  # Take screenshot for visual verification\n  adb shell screencap -p \/sdcard\/screenshot.png\n  adb pull \/sdcard\/screenshot.png &quot;screenshot_$(echo $url | sed &#39;s\/[^a-zA-Z0-9]\/_\/g&#39;).png&quot;\n  echo &quot;Screenshot saved&quot;\n  # Press back to return to launcher\n  adb shell input keyevent KEYCODE_HOME\n  sleep 1\ndone\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">CI\/CD Integration<\/h3>\n\n\n\n<p>Add deep link configuration validation to your CI pipeline:<\/p>\n\n\n\n<pre><code class=\"language-yaml\"># .github\/workflows\/deep-link-check.yml\nname: Deep Link Config Check\non: [push, pull_request]\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Validate AASA file\n        run: |\n          curl -sf &quot;https:\/\/go.yourapp.com\/.well-known\/apple-app-site-association&quot; | python3 -m json.tool\n\n      - name: Validate assetlinks.json\n        run: |\n          curl -sf &quot;https:\/\/go.yourapp.com\/.well-known\/assetlinks.json&quot; | python3 -m json.tool\n\n      - name: Check route resolution\n        run: bash scripts\/test-deep-links.sh\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Common Cross-Platform Discrepancies<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Links Open in Browser on One Platform<\/h3>\n\n\n\n<p>If links open the app on iOS but not Android (or vice versa):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Check that both AASA and assetlinks.json are deployed<\/li>\n<li>Verify the signing fingerprint on Android (this is the most common cause)<\/li>\n<li>iOS caches the AASA on install; try uninstalling and reinstalling<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Different Navigation Behavior<\/h3>\n\n\n\n<p>If the same link navigates to different screens on iOS vs Android:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Check your routing code for platform-specific branches<\/li>\n<li>Verify the URL is being parsed identically on both platforms<\/li>\n<li>Some URL parsing libraries handle trailing slashes or query string encoding differently<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Timing Differences<\/h3>\n\n\n\n<p>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).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Checklist<\/h2>\n\n\n\n<ul class=\"checklist wp-block-list\"><li><input type=\"checkbox\" disabled> Direct deep link works on iOS (cold start)<\/li><li><input type=\"checkbox\" disabled> Direct deep link works on iOS (warm start)<\/li><li><input type=\"checkbox\" disabled> Direct deep link works on Android (cold start)<\/li><li><input type=\"checkbox\" disabled> Direct deep link works on Android (warm start)<\/li><li><input type=\"checkbox\" disabled> Web fallback works on iOS (app not installed)<\/li><li><input type=\"checkbox\" disabled> Web fallback works on Android (app not installed)<\/li><li><input type=\"checkbox\" disabled> Deferred deep link works on iOS<\/li><li><input type=\"checkbox\" disabled> Deferred deep link works on Android<\/li><li><input type=\"checkbox\" disabled> Links work from Messages on both platforms<\/li><li><input type=\"checkbox\" disabled> Links work from email on both platforms<\/li><li><input type=\"checkbox\" disabled> OG previews render correctly on Facebook, Twitter, iMessage<\/li><li><input type=\"checkbox\" disabled> AASA and assetlinks.json return HTTP 200 with valid JSON<\/li><li><input type=\"checkbox\" disabled> No disambiguation dialog on Android (verified App Links)<\/li><li><input type=\"checkbox\" disabled> Parameters passed correctly on both platforms<\/li><\/ul>\n\n\n\n<p>For Universal Link testing specifics, see <a href=\"https:\/\/tolinku.com\/blog\/testing-universal-links\/\">How to Test Universal Links<\/a>. For the cross-platform overview, see <a href=\"https:\/\/tolinku.com\/blog\/cross-platform-deep-linking-guide\/\">Cross-Platform Deep Linking Guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Test deep links across iOS, Android, and multiple devices. Build a testing matrix, automate tests, and ensure consistent link behavior.<\/p>\n","protected":false},"author":2,"featured_media":911,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Testing Deep Links Across Platforms and Devices","rank_math_description":"Test deep links across iOS, Android, and multiple devices. Build a testing matrix, automate tests, and ensure consistent link behavior.","rank_math_focus_keyword":"testing deep links cross-platform","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cross-platform-testing-deep-links.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-cross-platform-testing-deep-links.png","footnotes":""},"categories":[15],"tags":[25,23,156,20,24,189,80,22],"class_list":["post-912","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-android","tag-app-links","tag-cross-platform","tag-deep-linking","tag-ios","tag-quality-assurance","tag-testing","tag-universal-links"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/912","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=912"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/912\/revisions"}],"predecessor-version":[{"id":913,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/912\/revisions\/913"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/911"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=912"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=912"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=912"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}