How to Test Deep Links on Android & iOS devices
By The Nerdy Geek, Community Contributor - March 17, 2022
What are Deep Links?
Deep link is a technology that launches an app and opens a specific page in the app once the user clicks a URL on a web page or in another app. Implementing deep links is an exciting way to enhance the user experience by seamlessly allowing users to access the specific page without interruption. As a result of this, user engagement and retention increase significantly.
Let’s understand deep links with a simple example:
When searching the keyword “Floral Wallpaper” on Google, #floralwallpaper of Instagram appears as a preview among the search results.
As the user clicks on the Search Result link, it opens the result page in the Instagram app that is already installed on the device. This is where Deep Linking comes into play, where the user gets redirected from the web search results page to the Instagram Application.
The intent here is to allow the user to access the #floralwallpaper on the Instagram app seamlessly, as opposed to opening it on the website. This is done to enrich the overall user experience, allowing the user to access the app features seamlessly.
However, considering security aspects, Deep Links are easily exploitable if not used with due diligence for authorization purposes. Sometimes these deep links contain very sensitive data and when not tested properly, they might allow a malicious app to handle the deep link instead of the legitimate app. In such scenarios, Deep Link Testing plays a crucial role in improving the overall quality and security of the product.
Let’s deep dive into the world of Deep Links and learn the ways to test them!
Deep link is a URL, which navigates the user from the web to a specific page in a given app. When the user clicks a deep link, Android performs either of the following actions:
- Opens the required app in the user’s device that can handle the link if the app is already available on the device.
- If the required app isn’t available, it opens the only app that can handle the link or opens PlayStore from the user can download the required app.
- In case when the URL can be handled by multiple apps, it opens a dialog allowing the user to select one app from the given list of compatible apps to open the link.
Why are Deep Links important?
Deep Linking makes any kind of transition between web and apps hassle-free and smooth for users. Given the seamless user experience, it helps advertisers have a better chance of converting the users into customers.
Retaining users is the key focus of deep linking. Deep linking is often used for re-engaging users and is often a key component of retargeting campaigns. Since it gives a seamless transition from a web link to an app, deep linking minimizes the chances of users not accessing the page redirected by the campaign URL.
Building Blocks of a Deep Link
Deep Link consists of several components, just like any URL. Let’s understand the components of a deep link with an example. Consider a dummy deep link https://www.browserstack.com/test/code=abcd. It can be categorized into:
- https – It identifies the protocol used to access the resource on the internet.
- www.browserstack.com – It is the host, i.e. the domain name or address of the web server that is being accessed.
- /test – It is the path that specifies a particular page of the content.
- code – The query parameter to extract from intents in your destination. abcd is the value of the parameter.
Types of Deep Links
Deep Links can be classified as Default, Deferred, and Contextual deep links.
1. Default Deep Links
These deep links function only to direct users to the required app if it’s already installed on the device. In case, where the app is not installed, the link is unable to reach the endpoint of an app, and thus an error message is displayed.
2. Deferred Deep Links
These deep links are more complex than default deep links. They can direct users to the App if it is available on the device. In case the app is not available on the device, it directs the users to Play Store or to another location, such as the app’s website for more information, and then open the original page that the user was directed to.
3. Contextual Deep Linking
Contextual deep linking involves links that ostensibly provide additional benefits. Contextual deep links are the usual default or deferred deep links with added parameters. Contextual deep links don’t exist by themselves, since the additional parameters are manually added. The parameters can be added by the marketers themselves. Such contextual deep links help in tracking the traffic source of the campaign.
How to test Deep Links on Android
Testing Deep Links are important to ensure a high-end user experience. Functioning of Deep Links directly impacts user engagement; that is why testing has to be performed diligently.
For getting accurate results, considering real user conditions is a must. Thus, it is recommended to perform Deep Link Testing on real devices. However, buying and maintaining real devices is costly. Hence using real device cloud, like BrowserStack, can be a great way to test deep linking under real user conditions on 3000+ browser device combinations.
Test Deep Link URL on Real Devices for Free
Following are the different ways of testing deep links on Android devices:
Using Android Debug Bridge
By using Android Debug Bridge (ADB) shell commands one can test the deep link flow. It is used to verify if the link navigates to the correct section of your app.
Open the terminal and enter the following command:
adb shell am start -W -a android.intent.action.VIEW -d "your deep link url"
This command starts the ADB shell with the VIEW action and specifies the deep link URL to be tested.
Using Appium Driver
The below example shows two ways of testing that the logged-in experience works correctly: first by navigating the login UI with Appium, and then by using the deep linking trick described in this edition.
import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileBy; import io.appium.java_client.android.AndroidDriver; import java.io.IOException; import java.net.URL; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.openqa.selenium.By; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; @RunWith(JUnit4.class) public class Appium_Deep_Linking { private String APP_ANDROID = "https://github.com/cloudgrey-io/the-app/releases/download/v1.2.1/TheApp-v1.2.1.apk"; private String AUTH_USER = "alice"; private String AUTH_PASS = "mypassword"; @Test public void testLoginSlowAndroid() throws IOException { AndroidModel model = new AndroidModel(); AndroidDriver driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), model.caps); runStepByStepTest(driver, model); } private void runStepByStepTest(AppiumDriver driver, Model model) { WebDriverWait wait = new WebDriverWait(driver, 10); try { wait.until(ExpectedConditions.presenceOfElementLocated(model.loginScreen)).click(); wait.until(ExpectedConditions.presenceOfElementLocated(model.username)).sendKeys(AUTH_USER); wait.until(ExpectedConditions.presenceOfElementLocated(model.password)).sendKeys(AUTH_PASS); wait.until(ExpectedConditions.presenceOfElementLocated(model.loginBtn)).click(); wait.until(ExpectedConditions.presenceOfElementLocated(model.getLoggedInBy(AUTH_USER))); } finally { driver.quit(); } } @Test public void testDeepLinkForDirectNavAndroid () throws IOException { AndroidModel model = new AndroidModel(); AndroidDriver driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), model.caps); runDeepLinkTest(driver, model); } private void runDeepLinkTest(AppiumDriver driver, Model model) { WebDriverWait wait = new WebDriverWait(driver, 10); try { driver.get("theapp://login/" + AUTH_USER + "/" + AUTH_PASS); wait.until(ExpectedConditions.presenceOfElementLocated(model.getLoggedInBy(AUTH_USER))); } finally { driver.quit(); } } private abstract class Model { public By loginScreen = MobileBy.AccessibilityId("Login Screen"); public By loginBtn = MobileBy.AccessibilityId("loginBtn"); public By username; public By password; public DesiredCapabilities caps; abstract By getLoggedInBy(String username); } } private class AndroidModel extends Model { AndroidModel() { username = MobileBy.AccessibilityId("username"); password = MobileBy.AccessibilityId("password"); caps = new DesiredCapabilities(); caps.setCapability("platformName", "Android"); caps.setCapability("deviceName", "Android Emulator"); caps.setCapability("app", APP_ANDROID); caps.setCapability("automationName", "UiAutomator2"); } public By getLoggedInBy(String username) { return By.xpath("//android.widget.TextView[@text=\"You are logged in as " + username + "\"]"); } } }
Using ActivityTestRule and Espresso Intent
Follow the steps mentioned below to test Deep Links:
Step 1 Start with an activity rule
@Rule public ActivityTestRule<YourAppMainActivity> mActivityRule = new ActivityTestRule<>(YourAppMainActivity.class, true, false);
Step 2 Parse the URI (Uniform Resource Identifier) from the link and return the intent
String uri = "http://your_deep_link_from_gmail"; private Intent getDeepLinkIntent(String uri){ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)) .setPackage(getTargetContext() .getPackageName()); return intent; }
Step 3 Launch the intent using the Activity Rule
Intent intent = getDeepLinkIntent(deepLinkUri); mActivityRule.launchActivity(intent);
Here’s a sample test that captures the above steps of Deep Link Testing using ActivityTestRule and Espresso Intent
import android.support.test.espresso.intent.Intents import android.support.test.espresso.intent.Intents.intended import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import android.content.Intent import android.net.Uri @RunWith(AndroidJUnit4::class) class DeepLinkingTest { @Rule @JvmField val activityTestRule = ActivityTestRule(MainActivity::class.java) @Before fun setUp() { Intents.init() } @After fun tearDown() { Intents.release() } @Test fun should_launch_secondActivity_when_deepLinkingToActivityTwo() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("myapp://example.com?screen=activitytwo")) activityTestRule.launchActivity(intent) intended(hasComponent(SecondActivity::class.java!!.getName())) } }
How to Deep Links on iOS
Following is the method to test deep links on iOS devices:
Using XCUITest
Use XCUIApplication class to launch, monitor, and terminate your app in a UI Test.
- For Launching the Application, enter the below command.
app.launch()
- For Terminating the Application, use the following command.
app.terminate()
- For Activating the Application, enter the following command.
app.activate()
To perform a UI test of a Safari deeplink, it is recommended that the required app should run in the background. If any other app is launched during an ongoing UI test, it is similar to launching the required app, but with a different bundle identifier. For launching Safari, the bundle identifier “com.apple.mobilesafari” is triggered.
To launch the required app and switch back to Safari right after, run the following code:
func testDeeplinkFromSafari() { let app = XCUIApplication() app.launch() let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari") safari.launch() }
For deeplinking back to the app, Safari has to be controlled similar to any regular UI test. This can be done in three steps:
- identify the address bar,
- type the deep link URL,
- click on the “Go” button.
Ideally, Safari will deeplink back to the required app, depicting that the deeplink logic in the app is working as expected.
func testDeeplinkFromSafari() { // Launch our app let app = XCUIApplication() app.launch() // Launch Safari and deeplink back to our app openFromSafari("swiftrocks://profile") // Make sure Safari properly switched back to our app before asserting XCTAssert(app.wait(for: .runningForeground, timeout: 5)) // Assert that the deeplink worked by checking if we're in the "Profile" screen XCTAssertTrue(app.navigationBars["Profile"].exists) } private func openFromSafari(_ urlString: String) { let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari") safari.launch() // Make sure Safari is really running before asserting XCTAssert(safari.wait(for: .runningForeground, timeout: 5)) // Type the deeplink and execute it let firstLaunchContinueButton = safari.buttons["Continue"] if firstLaunchContinueButton.exists { firstLaunchContinueButton.tap() } safari.buttons["URL"].tap() let keyboardTutorialButton = safari.buttons["Continue"] if keyboardTutorialButton.exists { keyboardTutorialButton.tap() } safari.typeText(urlString) safari.buttons["Go"].tap() _ = confirmationButton.waitForExistence(timeout: 2) if confirmationButton.exists { confirmationButton.tap() } }
It is best to add the additional wait (for: .runningForeground) assertion for safety. Inserting additional wait allows checking whether the app switching worked before the attempt to assert. If it fails, then it is evident that the failure is because the app failed to switch, and not due to something not being present in the UI of the app.
Testing Deep Links on Real Device Cloud
Deep Links play an important role in driving user traffic to an application from URLs; hence it is essential to test its functionality thoroughly to identify any bottlenecks. When testing deep linking on Real Devices, one can decipher the issues that could cause interruptions or altered behavior of the deep link. Testing on BrowserStack’s real device cloud includes all the real user conditions while performing tests. Thus, allowing developers to test on 3000+ browser-device combinations for cross-compatibility testing.
BrowserStack App Automate offers cloud-based access to both the latest and legacy devices (Android, iOS, and Windows) installed with real operating systems. App Automate also requires no additional setup, helping testers save precious time and meet their deadlines that much faster.
Since users demand high-functioning and engaging campaigns, deep link testing is an absolute requirement before releasing any campaign. By running deep link tests on real Android devices, testers can ensure that apps are working as expected in real user conditions. Run as many tests as possible on as many real Android devices to offer a consistently optimal user experience.