Skip to content

Instantly share code, notes, and snippets.

@cornr
Last active December 25, 2023 04:01
Show Gist options
  • Save cornr/46e14d1e8b160f981dc0fae53ba68b09 to your computer and use it in GitHub Desktop.
Save cornr/46e14d1e8b160f981dc0fae53ba68b09 to your computer and use it in GitHub Desktop.
UI testing openURL and universal links via iMessage
import Foundation
import XCTest
//This is based on https://blog.branch.io/ui-testing-universal-links-in-xcode-9/
final class iMessage {
static func launch() -> XCUIApplication {
// Open iMessage App
let messageApp = XCUIApplication(bundleIdentifier: "com.apple.MobileSMS")
// Launch iMessage app
messageApp.launch()
// Wait some seconds for launch
XCTAssertTrue(messageApp.waitForExistence(timeout: 10))
// Continues "Whats new" if present
let continueButton = messageApp.buttons["Continue"]
if (continueButton.exists) {
continueButton.tap()
}
// Removes New Messages Sheet on iOS 13
let cancelButton = messageApp.navigationBars.buttons["Cancel"]
if cancelButton.exists {
cancelButton.tap()
}
// Return application handle
return messageApp
}
static func open(URLString urlString: String, inMessageApp app: XCUIApplication) {
XCTContext.runActivity(named: "Open URL \(urlString) in iMessage") { _ in
// Find Simulator Message
let kateBell = app.cells.staticTexts["Kate Bell"]
XCTAssertTrue(kateBell.waitForExistence(timeout: 10))
kateBell.tap()
// Tap message field
app.textFields["iMessage"].tap()
// Continues "Swipe to Text" Sheet
let continueButton = app.buttons["Continue"]
if continueButton.exists {
continueButton.tap()
}
// Enter the URL string
app.typeText("Open Link:\n")
app.typeText(urlString)
// Simulate sending link
app.buttons["sendButton"].tap()
//Wait for Main App to finish launching
sleep(2)
// The first link on the page
let messageBubble = app.cells.links["com.apple.messages.URLBalloonProvider"]
XCTAssertTrue(messageBubble.waitForExistence(timeout: 10))
messageBubble.tap()
}
}
static func openFromPasteboard(inMessageApp app: XCUIApplication) {
XCTContext.runActivity(named: "Open URL from Pasetboard in iMessage") { _ in
// Find Simulator Message
let kateBell = app.cells.staticTexts["Kate Bell"]
XCTAssertTrue(kateBell.waitForExistence(timeout: 10))
kateBell.tap()
// Add dummy text to workaround issue with link-only bubbles in iOS 12.4+
app.textFields["iMessage"].tap()
app.typeText("Open Link:\n")
// Tap message field to open paste menu
app.textFields["iMessage"].tap()
sleep(1)
app.menuItems["Paste"].tap()
// Simulate sending link
sleep(1)
app.buttons["sendButton"].tap()
//Wait for Main App to finish launching
sleep(3)
// The first link on the page
let messageBubble = app.cells.links["com.apple.messages.URLBalloonProvider"]
XCTAssertTrue(messageBubble.waitForExistence(timeout: 10))
messageBubble.tap()
}
}
}
import XCTest
class UniversialLinktTest: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
func testItDeepLinksIntoApp() {
// Open App via iMessage
let messageApp = iMessage.launch()
iMessage.open(URLString: "https://mydomain.com/verify/12345", inMessageApp: messageApp)
// Do test here...
let isInVerifyScreen = app.staticTexts["12345"].waitForExistence(timeout: 10)
XCTAssertTrue(isInVerifyScreen)
// For reliability terminate iMessage after test
messageApp.terminate()
}
}
@nagashreeabhilash100
Copy link

nagashreeabhilash100 commented Jul 1, 2019

@say
This is not working without https:// ***. For instance, appName://google/search?search_keyword=tea
Should be call other method instead of URLString?
Also, here in this case, the messageBubble is different from one implemented.

@Vkt0r
Copy link

Vkt0r commented Sep 24, 2019

Since iOS 12 posting the direct link to iMessage wouldn't work as the URL disappear after being pasted. To be able to make it work you should encapsulate the link inside a sentence, (e.g "Open the link https://appname.io.deeplink") to the iMessage app recognize it. I'm afraid this is a bug in the iMessage app in the Simulator as any URL posted would dissapear.

@RedFish
Copy link

RedFish commented Sep 25, 2019

Encapsulate the link inside a sentence is a perfect workaround. Thanks @Vkt0r 🙂

@cornr
Copy link
Author

cornr commented Sep 25, 2019

@Vkt0r exactly, we had the same issue. I updated the gist. Also for iOS 13 it now dismisses the New Message Sheet.

@cornr
Copy link
Author

cornr commented Sep 25, 2019

@nagashreeabhilash100 good question, have you found a solution yet?

@jonchui
Copy link

jonchui commented Feb 28, 2020

Whats XCUIApplication.launchInMemory() ? I'm guessing an extension you wrote b/c its not in any github code and not an apple function?

@cornr
Copy link
Author

cornr commented Mar 26, 2020

@jonchui exactly launchInMemory looks like this

func launchInMemory() {
     self.launchArguments.append("-NOFETCHDELAY")
     self.launch()
 }

I'll replace it in the gist. Sorry for the delayed response

@nvduc2910
Copy link

@cornr this is not working with exampleapp://path. Could you help me?

@cornr
Copy link
Author

cornr commented Jul 26, 2020

@nvduc2910 correct. Currently this only works with HTTP(S) URLs wich present itself with a preview as an com.apple.messages.URLBalloonProvider. If you send a url scheme link to iMessage you'll have to find the bubble and make the UITest tap on it (instead of https://gist.github.com/cornr/46e14d1e8b160f981dc0fae53ba68b09#file-imessage-swift-L60). After that handle the Popup to open your App.

@nvduc2910
Copy link

@cornr is there any way to make it works with the Deeplink scheme?

@nvduc2910
Copy link

I already find the bubble and make the UITest tap on it but nothing happened.

@cornr
Copy link
Author

cornr commented Jul 27, 2020

@nvduc2910 have you tried open the deeplink url manually in Messages or Safari. This works for me in the simulator.
Have you set it up the URL Type in your Info.plist?

@nvduc2910
Copy link

@cornr I tried open deeplink manual in messages (simulator) but it not works. It works well on devices.

@d27saurabh
Copy link

I tried this approach manually with HTTP(S) URL. But it did not open the app, instead opened the web browser.

@quifago
Copy link

quifago commented Nov 19, 2020

Looks like it's not working anymore with Xcode 12.2 / iOS 14.2 :(

@RobinDaugherty
Copy link

Can confirm, iOS 14.2 Simulator completely breaks links being sent through iMessage.

@RobinDaugherty
Copy link

Found a good way to launch links in UI tests: https://github.com/mattstanford/GhostHand

Obviously this doesn't help with testing aspects of iMessage, but it will let you launch a specific URL to test universal links or an app schema.

@harlynkingm
Copy link

As of iOS 16.4, macOS 13.3, and Xcode 14.3, new XCTest UI Testing API's are available which let you do this in an even easier way.

You can open your 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

@cornr
Copy link
Author

cornr commented May 16, 2023

Thanks @harlynkingm for mentioning (and maybe even implementing 😀) that. I guess this workaround is obsolete now. ❤️

@standinga
Copy link

As of iOS 16.4, macOS 13.3, and Xcode 14.3, new XCTest UI Testing API's are available which let you do this in an even easier way.

You can open your 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

Have you been able to use the XCUIApplication open (_ url:)? I'm trying to replace UITests that were using Safari, but this just opens the app like XCUIApplication launch, ignoring the URL completely.

XCUIDevice.shared.system.open(url) works, but only if the app is already installed :( Otherwise, it fails Failed to open the default app for a URL: The operation couldn’t be completed. (OSStatus error -10814.). I was thinking about a workaround to do app.launch / app.terminate before calling XCUIDevice.shared.system.open(url), but it would slow down tests and reduce benefits of directly opening urls.

Thanks!

@harlynkingm
Copy link

harlynkingm commented Jun 16, 2023

There seem to be two known problems with the released APIs that are being investigated.

The first shows an un-skippable popup on the first call to XCUISystem.open when using a custom URL scheme. This might be able to be worked-around by calling XCUISystem.open from inside an async thread, then tapping the "Open" button from SpringBoard as described here:
https://developer.apple.com/forums/thread/25355

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

Hopefully, fixes for these issues will be available soon.

@standinga
Copy link

Yeah, I saw this thread just after I posted here. Seems we need to wait some more for a non-workaround (native) solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment