commit a673edaafd6c35ce6946c5df0a1baeb25d186581
parent 091adff761737eff6a8f1d099bb3921022dbe822
Author: Roger Yang <royang@mozilla.com>
Date: Thu, 16 Oct 2025 20:08:59 +0000
Bug 1993601 - Add more UI automated tests for AppLinks. r=android-reviewers,tthibaud
Differential Revision: https://phabricator.services.mozilla.com/D268229
Diffstat:
5 files changed, 302 insertions(+), 0 deletions(-)
diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/appLinksLinks.html b/mobile/android/fenix/app/src/androidTest/assets/pages/appLinksLinks.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Html_Control_Form</title>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ </head>
+
+ <p>Misc Link Types</p>
+ <section>
+ <a href="tel://1234567890">Telephone link</a>
+ </section>
+
+ <section>
+ <a href="intent://com.example.app">Intent schema link</a>
+ </section>
+
+ <section>
+ <a href="vnd.youtube://@Mozilla">Youtube schema link</a>
+ </section>
+
+ <section>
+ <a href="https://m.youtube.com/user/mozilla">Youtube link</a>
+ </section>
+
+ <section>
+ <a href="appLinksPostNavigationPhone.html" target="_blank">
+ Telephone post navigation link
+ </a>
+ </section>
+</html>
diff --git a/mobile/android/fenix/app/src/androidTest/assets/pages/appLinksPostNavigationPhone.html b/mobile/android/fenix/app/src/androidTest/assets/pages/appLinksPostNavigationPhone.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+This is the page with the auto-submitting form that redirects to an phone
+intent.
+<form action="tel://1234567890" method="POST"></form>
+<script>
+ const f = document.forms[0];
+ f.submit();
+</script>
diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt
@@ -101,6 +101,12 @@ object TestAssetHelper {
return TestAsset(url, "", "")
}
+ fun getAppLinksRedirectAsset(server: MockWebServer): TestAsset {
+ val url = server.url("pages/appLinksLinks.html").toString().toUri()!!
+
+ return TestAsset(url, "", "")
+ }
+
fun getCreditCardFormAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/creditCardForm.html").toString().toUri()!!
diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/AppLinksTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/AppLinksTest.kt
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.ui
+
+import androidx.compose.ui.test.junit4.AndroidComposeTestRule
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mozilla.fenix.helpers.AppAndSystemHelper.assertNativeAppOpens
+import org.mozilla.fenix.helpers.Constants
+import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
+import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
+import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
+import org.mozilla.fenix.helpers.OpenLinksInApp
+import org.mozilla.fenix.helpers.TestAssetHelper
+import org.mozilla.fenix.helpers.TestHelper.mDevice
+import org.mozilla.fenix.helpers.TestSetup
+import org.mozilla.fenix.helpers.perf.DetectMemoryLeaksRule
+import org.mozilla.fenix.ui.robots.clickPageObject
+import org.mozilla.fenix.ui.robots.navigationToolbar
+
+class AppLinksTest : TestSetup() {
+ private val youtubeSchemaUrlLink = itemContainingText("Youtube schema link")
+ private val youtubeUrlLink = itemContainingText("Youtube link")
+ private val intentSchemaUrlLink = itemContainingText("Intent schema link")
+ private val phoneUrlLink = itemContainingText("Telephone link")
+ private val formRedirectLink = itemContainingText("Telephone post navigation link")
+
+ private val phoneSchemaLink = "tel://1234567890"
+
+ @get:Rule
+ val composeTestRule =
+ AndroidComposeTestRule(
+ HomeActivityIntentTestRule(openLinksInExternalApp = OpenLinksInApp.ASK),
+ ) { it.activity }
+
+ @get:Rule
+ val memoryLeaksRule = DetectMemoryLeaksRule()
+
+ lateinit var externalLinksPage: TestAssetHelper.TestAsset
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ externalLinksPage = TestAssetHelper.getAppLinksRedirectAsset(mockWebServer)
+ }
+
+ @Test
+ fun askBeforeOpeningLinkInAppYoutubeSchemeCancelTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeSchemaUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningLinkWithIntentSchemeTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(intentSchemaUrlLink)
+ mDevice.waitForIdle()
+ verifyOpenLinkInAnotherAppPromptIsNotShown()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningLinkInAppYoutubeSchemeCancelMultiTapTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeSchemaUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ clickPageObject(youtubeSchemaUrlLink)
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ verifyOpenLinkInAnotherAppPromptIsNotShown()
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ verifyOpenLinkInAnotherAppPromptIsNotShown()
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningLinkInAppYoutubeSchemeCancelOnlyAffectCurrentTabTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeSchemaUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }.openTabDrawer(composeTestRule) {
+ }.openNewTab {
+ }.submitQuery(externalLinksPage.url.toString()) {
+ clickPageObject(youtubeSchemaUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun neverOpeningLinkInAppYoutubeTest() {
+ composeTestRule.activityRule.applySettingsExceptions {
+ it.openLinksInExternalApp = OpenLinksInApp.NEVER
+ }
+
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeUrlLink)
+ mDevice.waitForIdle()
+ verifyOpenLinkInAnotherAppPromptIsNotShown()
+ verifyUrl("youtube.com")
+ }
+ }
+
+ @Test
+ fun neverOpeningLinkInAppYoutubeSchemeCancelTest() {
+ composeTestRule.activityRule.applySettingsExceptions {
+ it.openLinksInExternalApp = OpenLinksInApp.NEVER
+ }
+
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeSchemaUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningLinkInAppYoutubeCancelTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(youtubeUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "YouTube")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl("youtube.com")
+ }
+ }
+
+ @Test
+ fun appLinksRedirectPhoneLinkPromptTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(phoneUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "Phone")
+ clickPageObject(itemWithResIdAndText("android:id/button2", "Cancel"))
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningLinkInAppPhoneCancelTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(phoneUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "Phone")
+ }
+ }
+
+ @Test
+ fun alwaysOpenPhoneLinkInAppTest() {
+ composeTestRule.activityRule.applySettingsExceptions {
+ it.openLinksInExternalApp = OpenLinksInApp.ALWAYS
+ }
+
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(phoneUrlLink)
+ mDevice.waitForIdle()
+ assertNativeAppOpens(Constants.PackageName.PHONE_APP, phoneSchemaLink)
+ }
+ }
+
+ @Test
+ fun askBeforeOpeningPhoneLinkInAcceptTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(phoneUrlLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "Phone")
+ clickPageObject(itemWithResIdAndText("android:id/button1", "Open"))
+ mDevice.waitForIdle()
+ assertNativeAppOpens(Constants.PackageName.PHONE_APP, phoneSchemaLink)
+ mDevice.waitForIdle()
+ verifyUrl(externalLinksPage.url.toString())
+ }
+ }
+
+ @Test
+ fun appLinksNewTabRedirectAskTest() {
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(formRedirectLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "Phone")
+ }
+ }
+
+ @Test
+ fun appLinksNewTabRedirectAlwaysTest() {
+ composeTestRule.activityRule.applySettingsExceptions {
+ it.openLinksInExternalApp = OpenLinksInApp.ALWAYS
+ }
+
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(formRedirectLink)
+ mDevice.waitForIdle()
+ assertNativeAppOpens(Constants.PackageName.PHONE_APP, phoneSchemaLink)
+ }
+ }
+
+ @Test
+ fun appLinksNewTabRedirectNeverTest() {
+ composeTestRule.activityRule.applySettingsExceptions {
+ it.openLinksInExternalApp = OpenLinksInApp.NEVER
+ }
+
+ navigationToolbar {
+ }.enterURLAndEnterToBrowser(externalLinksPage.url) {
+ clickPageObject(formRedirectLink)
+ verifyOpenLinkInAnotherAppPrompt(appName = "Phone")
+ }
+ }
+}
diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt
@@ -932,6 +932,22 @@ class BrowserRobot {
)
}
+ fun verifyOpenLinkInAnotherAppPromptIsNotShown() {
+ assertUIObjectIsGone(
+ itemContainingText(
+ getStringResource(
+ applinksR.string.mozac_feature_applinks_normal_confirm_dialog_title_with_app_name,
+ appName,
+ ),
+ ),
+ itemContainingText(
+ getStringResource(
+ applinksR.string.mozac_feature_applinks_normal_confirm_dialog_message,
+ ),
+ ),
+ )
+ }
+
fun verifyPrivateBrowsingOpenLinkInAnotherAppPrompt(appName: String, url: String, pageObject: UiObject) {
for (i in 1..RETRY_COUNT) {
try {