iOS deep linking with Xcode UI testing

I want to test parts of my app UI in isolation, so avoid having to drill down from the splash screen to the pages in question. My app implements deep linking, but I can't figure out how to configure XCUIApplication in the setUp() to trigger the page transition.


I have a method in the app delegate called openURL() which handles this for the production app. It works great, but when I call it (below), nothing happens. If I put a breakpoint in openURL() it never even gets hit.


    override func setUp() {
        super.setUp()
        continueAfterFailure = false

        XCUIApplication().launch()
      
        guard let url = NSURL(string: "fooapp://page/3") else {
            fatalError("Couldn't create URL")
        }


        UIApplication.sharedApplication().openURL(url)
    }


What I really want is to set UIApplicationLaunchOptionsURLKey in launchOptions and just let the app behave as if it received an external URL. I see launchArguments and launchEnvironment are available on XCUIApplication, but then I have to write something special to extract those values out and manually call openURL somewhere in the app delegate. What's the right way to do this?

Post not yet marked as solved Up vote post of jamhughes Down vote post of jamhughes
10k views

Replies

We agree that it would be great if this could work. It is a "known issue" that it doesn't. Please file a bug report to request making this possible, so that we can better gauge how many developers that are interested in this request.

The scenario of applying URL is frequently used in our test environment. We try not to "hijeck" code of our app under test. The only workaround we found is to call UIApplication.sharedApplication().openURL() before XCUIApplication().launch() in Setup() method. However it seems only working for the first time.

Could we add applying url method in UI Testing API? Thank you so much!

Hi Joar

Just happen to see this thread, our company also have lots of deep links implemented in our iso app, and could love this deep link stuff work, I just tried pretty muc the same thing @timo_HZ mentioned, and got the same result, seems till now the deep link in uitesting still not work.


Leo

Hello, any progress? 😟

  • This API has been released in iOS 16.4, macOS 13.3, and Xcode 14.3

Add a Comment
Any progress on this
  • This API has been released in iOS 16.4, macOS 13.3, and Xcode 14.3

Add a Comment

Almost 5.1k developers that are interested in this request. Any update so far?

  • This API has been released in iOS 16.4, macOS 13.3, and Xcode 14.3

Add a Comment

I use this:

func open(appPath pathString: String) {

        openFromSafari("com.myapp.myapp://my.domain.com\(pathString)")

        XCTAssert(app.wait(for: .runningForeground, timeout: 5))

    }



    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.textFields["Address"].tap()

        let keyboardTutorialButton = safari.buttons["Continue"]

        if keyboardTutorialButton.exists {

            keyboardTutorialButton.tap()

        }

        safari.typeText(urlString)

        safari.buttons["go"].tap()

        let openButton = safari.buttons["Open"]

        let _ = openButton.waitForExistence(timeout: 2)

        if openButton.exists {

            openButton.tap()

        }
    }

As of iOS 16.4, macOS 13.3, and Xcode 14.3, new XCTest API's are available which let you do this very easily.

You can open an application with a specific URL: https://developer.apple.com/documentation/xctest/xcuiapplication/4108226-open

Or, open any URL in the default application for it: https://developer.apple.com/documentation/xctest/xcuisystem/4108234-open

Thank you for all of the feedback! This post was invaluable in helping this feature get made.

Great news! I tested on my device. XCUIApplication.openURL can launch the app, but scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) and application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) are not called. I am using XCode 14.3 and the simulator is iOS 16.4.

@Developer Tools Engineer I'm having some trouble to use https://developer.apple.com/documentation/xctest/xcuisystem/4108234-open

A system dialog "Open in MyApp?" will always show for the first time calling this method, and I couldn't find a way in XCUI to get over this dialog, which basically makes this API not usable.

The specific thread for this problem - https://developer.apple.com/forums/thread/730962

There seem to be two known problems with the released APIs that we are actively investigating.

The first, as you described, shows an un-skippable popup on the first call to XCUISystem.open when using a custom URL scheme.

The second is that XCUIApplication().open seems to be more flaky than XCUIApplication(bundleIdentifier:).open

We are investigating both of these issues and I will update this thread with progress.

XCUIApplication.open still does not call the AppDelegate.application(_:open:options:) even when it doesn't flake and opens the app.

Instead use XCUISystem.open. The trick to the popup is to async the open call (since it waits for the button to be pressed) while you simulate the button press from springboard:

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
// If you previously launched your app, close the app with:
springboard.launch()

DispatchQueue.main.async {
    // Need to async this because XCUIDevice.shared.system.open
    // synchronously waits for a button to be pressed the first time.
    XCUIDevice.shared.system.open(URL(string: domain + path)!)
}
if springboard.buttons["Open"].waitForExistence(timeout: 5) {
    springboard.buttons["Open"].tap()
}
  • Thanks for finding this! That's a good tip

  • This workaround didn't work for me. It didn't open the app.

  • Thanks for the trick, it does work under ideal condition but result varies much depending on the runner and environment. As another alternative, I'm using mobile Safari search field to open URL.

Add a Comment

Following some of the recent answers, here is a working solution for SwiftUI that I am using to deep link into parts of the app for our UITests.

   func launchDeepLink(with url: URL) {
        addUIInterruptionMonitor(withDescription: "System Dialog") { (alert) -> Bool in
            let button = alert.buttons.element(boundBy: 1)
            if button.exists {
                button.tap()
            }
            return true
        }

        if #available(iOS 16.4, *) {
            XCUIDevice.shared.system.open(url)
        }
    }

It includes the functionality to confirm the alert view that will pop up. Only tested this in SwiftUI using the .onOpenURL view modifier.

Note that it is using the open call on XCUISystem not XCUIApplication as that causes the flakey experience some have mentioned.

Hope that helps :)

For anybody else trying to work around this, I found that the new XCUISystem works on the iOS 17 simulator. It doesn't work at all on the iOS 16.4 simulator.

Hello! As of the release of Xcode 15 and iOS 17, many of the issues with these APIs should be resolved.

XCUISystem.open should now work in all cases on all platforms.

XCUIApplication(bundleIdentifier:).open should also now work in all cases on all platforms.

XCUIApplication().open may have outstanding issues on iOS Simulator which are currently being investigated. This API should work as expected on physical hardware.

Thank you to everyone that provided feedback and workarounds for this feature!!

  • This isn't working consistently for either of the XCUIApplication options on XCode 15 and iOS 17. XCUISystem does work, however because it doesn't launch your app via XCUIApplication you lose all your launch arguments.

    It's making testing these flows incredibly laborious and flaky, is there a suggested solution to testing opening URLs whilst retaining launch args?

Add a Comment