iOS Swift UI Test tap alert button - ios

A really small UI test fails when trying to tap an alert button, what step am I missing?
I'm trying to tap the "Continue" button in the alert displayed below with the following code (that I get from recording my steps).
let app = XCUIApplication()
let salesforceloginbuttonButton = app.buttons["salesforceLoginButton"]
salesforceloginbuttonButton.tap()
let posWantsToUseSalesforceComToSignInAlert = app.alerts["“POS” Wants to Use “salesforce.com” to Sign In"]
let continueButton = posWantsToUseSalesforceComToSignInAlert.buttons["Continue"]
continueButton.tap()
When I run the test it fails at the last line (i.e: continueButton.tap()) with the error No matches found for Find: Descendants matching type Alert from input.
Notes:
I already tried waiting a few seconds before tapping the continue button with the same result.
When the test is ran, the app launches and the alert gets displayed after tapping the salesforceloginbuttonButton

I think your alert is not recognized, maybe because of the double quotation marks
You can try and explicitly set the identifier of your alert like this:
let alert = UIAlertController(title: "Alert", message: "msg", preferredStyle: .alert)
alert.view.accessibilityIdentifier = "myAlert"
Then in your tests:
let alert = app.alerts["myAlert"]
let button = alert.buttons["Continue"]
button.tap()

Had a similar issue but I could solve it by using addUIInterruptionMonitor. In your XCTestCase add an override setUp like this. One caveat is that it's a little flakey... Will update the answer if I find a better solution.
class ExamplelUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
app = XCUIApplication()
continueAfterFailure = false
addUIInterruptionMonitor(withDescription: "Login Dialog") {
(alert) -> Bool in
let okButton = alert.buttons["Continue"]
okButton.tap()
return true
}
app.launch()
}
func testExample() throws {
app.buttons["Login"].tap()
app.tap() // This one is needed in order for the InteruptionMonitor to work
}
}

Related

IOS share extension show alert if user is not logged in?

I had added share extension to upload file.
But i want to stop open share extension when user is not logged in application and show alert similar like Messenger application from facebook.
Facebook messenger app
How can i do this
Note : I know how to do check is User logged in or not using app groups. But I want to check before open the share extension and show alert. in my case its first open the share extension and then i am showing alert. I want to check before open the share extension
Rather than directly open your custom view for share extension you could use alert first to check if user logged in or not, then if user has logged in, you can proceed to present your custom view with animation.
you can do this by adding below method on ShareViewController: SLComposeServiceViewController.
override func viewDidLoad() {
// check if user logged in or not here and if not below code will be executed.
self.loginAlertController = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert)
let onOk = UIAlertAction(title: "OK", style: .destructive) { alert in
self.extensionContext?.cancelRequest(withError: NSError(domain: "loging", code: 0, userInfo: nil))
}
loginAlertController!.addAction(onOk)
present(loginAlertController!, animated: true, completion: nil)
}
You should make use of App groups to communicate between the main app and extension app.(Sharing Data: NSUserDefaults and App Groups
)
You can add data in main app like this :-
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
mySharedDefaults?.set(false, forKey: "isLoggedIn")
Then you can get data like this in your extension
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
if let isLoggedIn = mySharedDefaults?.value(forKey: "isLoggedIn") as? Bool {
if !isLoggedIn {
showAlert()
}
}
Refer this for implementing app groups

Call button on UI Testing?

I was wondering if it's possible to tap call button from tel's scheme (e.g tel//555555555). Because if I touch call button I'll have an alert that I need to confirm my call or cancel it. Is it possible?
I have this on my code:
addUIInterruptionMonitor(withDescription: "Phone Dialog") { (alert) -> Bool in
let button = alert.buttons["Llamar"]
if button.exists {
button.tap()
return true
}
return false
}
app.tap()
XCTAssert(app.buttons["call_button"].exists, "No se encuentra el boton de llamar")
app.buttons["call_button"].tap()
sleep(2)
Any Idea?
Regards
The UIInteractionMonitor does not work in the case of the phone call system dialog. The phone call dialog is handled by the Springboard and not your app.
Xcode 9 allows you access to the Springboard so you can tap on the "Call" button by doing this:
func testPhoneCall() {
let app = XCUIApplication()
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
app.launch()
app.buttons["call_button"].tap()
// tap on the call button
springboard.buttons["Llamar"].tap()
}

Can't insert message after TouchID authentication

So I'm playing around a bit with iMessage apps, and have hit a weird issue. I want to try and use TouchID authentication inside of iMessage, and am able to pop the TouchID alert fine from the iMessage app. However, when I go to insert a message showing the result of TouchID, it won't insert the message for me. Here's the relevant code:
#IBAction func authenticateTapped(_ sender: Any) {
let context = LAContext()
var wasSuccessful = false
self.group.enter()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Testing authentication") { (successful, _) in
wasSuccessful = successful
self.group.leave()
}
self.group.notify(queue: DispatchQueue.main) {
self.sendResult(wasSuccessful)
}
}
#IBAction func sendMessageTapped(_ sender: Any) {
sendResult(true)
}
func sendResult(_ successful: Bool) {
guard let conversation = self.activeConversation else { fatalError("expected conversation") }
var components = URLComponents()
components.queryItems = [URLQueryItem(name: "successful", value: successful.description)]
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "green_checkmark")
layout.caption = "Authentication Result"
let message = MSMessage(session: conversation.selectedMessage?.session ?? MSSession())
message.url = components.url!
message.layout = layout
print("queryParts: \(String(describing: components.queryItems))")
print("message: \(message)")
print("activeConversation: \(String(describing: conversation))")
conversation.insert(message) {
(error) in
print("in completion handler")
print(error ?? "no error")
}
}
When authenticateTapped is triggered, the TouchID prompt shows, I successfully authenticate, and then see every log message inside of the sendResult message, except for any of the ones in the completion handler of the insert method.
The weird thing is, when the sendMessageTapped method is fired, everything works as expected. Does anyone know what's going on here, and why I can't seem to insert a message after I successfully authenticate using TouchID?
The only thing I can think of that's different between the two is that the view controller is disappearing when the TouchID prompt comes up, however, if that were the cause, I would expect none of my print statements would show up in the console, when everyone does except those in the completion handler?
Edit: I've done a bit more digging. When presenting the Touch ID authentication in compact mode, your view controller resigns active. When presenting in expanded mode, it stays active, allowing you to insert the message.
Does anyone know if resigning active when presenting the Touch ID alert is a bug or intended behavior?

Swift 3 iMessage Extension doesn't open URL

I am creating an iOS Application iMessage Extension.
According to Example by Apple, I creating a message according to provided logic
guard let url: URL = URL(string: "http://www.google.com") else { return }
let message = composeMessage(url: url)
activeConversation?.insert(message, completionHandler: { [weak self] (error: Error?) in
guard let error = error else { return }
self?.presentAlert(error: error)
})
also
private func composeMessage(url: URL) -> MSMessage {
let layout = MSMessageTemplateLayout()
layout.caption = "caption"
layout.subcaption = "subcaption"
layout.trailingSubcaption = "trailing subcaption"
let message = MSMessage()
message.url = url
message.layout = layout
return message
}
and
private func presentAlert(error: Error) {
let alertController: UIAlertController = UIAlertController(
title: "Error",
message: error.localizedDescription,
preferredStyle: .alert
)
let cancelAction: UIAlertAction = UIAlertAction(
title: "OK",
style: .cancel,
handler: nil
)
alertController.addAction(cancelAction)
present(
alertController,
animated: true,
completion: nil
)
}
As far as I understand, after message is sent, on a click, Safari browser should be opened.
When I click on a sent message, MessageViewController screen takes place in whole screen, without opening safari or another app.
Where is the problem? How can I achieve desired functionality?
Here is the code I use to open a URL from a iMessage extension. It is currently working to open the Music app in the WATUU iMessage application. For instance with the URL
"https://itunes.apple.com/us/album/as%C3%AD/1154300311?i=1154300401&uo=4&app=music"
This functionality currently works in iOS 10, 11 and 12
func openInMessagingURL(urlString: String){
if let url = NSURL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: Selector("openURL:")) == true{
responder?.perform(Selector("openURL:"), with: url)
}
responder = responder!.next
}
}
}
UPDATE FOR SWIFT 4
func openInMessagingURL(urlString: String){
if let url = URL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: #selector(UIApplication.open(_:options:completionHandler:))) == true{
responder?.perform(#selector(UIApplication.open(_:options:completionHandler:)), with: url)
}
responder = responder!.next
}
}
}
I think safari Browser only opens for macOS. This worked for me:
override func didSelectMessage(message: MSMessage, conversation: MSConversation) {
if let message = conversation.selectedMessage {
// message selected
// Eg. open your app:
let url = // your apps url
self.extensionContext?.openURL(url, completionHandler: { (success: Bool) in
})
}
}
Using the technique shown by Julio Bailon
Fixed for Swift 4 and that openURL has been deprecated.
Note that the extensionContext?.openURL technique does not work from an iMessage extension - it only opens your current app.
I have posted a full sample app showing the technique on GitHub with the relevant snippet here:
let handler = { (success:Bool) -> () in
if success {
os_log("Finished opening URL")
} else {
os_log("Failed to open URL")
}
}
let openSel = #selector(UIApplication.open(_:options:completionHandler:))
while (responder != nil){
if responder?.responds(to: openSel ) == true{
// cannot package up multiple args to openSel so we explicitly call it on the iMessage application instance
// found by iterating up the chain
(responder as? UIApplication)?.open(url, completionHandler:handler) // perform(openSel, with: url)
return
}
responder = responder!.next
}
It seems it is not possible to open an app from a Message Extension, except the companion app contained in the Workspace. We have tried to open Safari from our Message Extension, it did not work, this limitation seems by design.
You could try other scenari to solve your problem :
Webview in Expanded Message Extension
You could have a Webview in your Message Extension, and when you click
on a message, you could open the Expanded mode and open you Url in the
Webview.
The user won't be in Safari, but the page will be embedded in your Message Extension.
Open the Url in the Companion App
On a click on the message, you could open your Companion app (through
the Url Scheme with MyApp://?myParam=myValue) with a special parameter
; the Companion app should react to this parameter and could redirect
to Safari through OpenUrl.
In this case, you'll several redirects before the WebPage, but it should allow to go back to the conversation.
We have also found that we could instance a SKStoreProductViewController in a Message Extension, if you want to open the Apple Store right in Messages and let the user buy items.
If you only need to insert a link, then you should use activeConversation.insertText and insert the link. Touching the message will open Safari.
openURL in didSelectMessage:conversation: by using extensionContext
handle the URL scheme in your host AppDelegate

Present Alert on ViewController only once

I am having various ViewControllers in my app. On one of them I want a alert to be displayed on load of the VC once to the user.
I have followed the instructions to set a glob var under the import section:
var disalert:Bool = true
and in the function I got:
if disalert {
let actionSheetController: UIAlertController = UIAlertController(title: "How-to use Holiday List", message: "message here", preferredStyle: .Alert)
//Create and add the Cancel action
//Create and an option action
let nextAction: UIAlertAction = UIAlertAction(title: "OK", style: .Default) { action -> Void in
}
actionSheetController.addAction(nextAction)
//Add a text field
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
disalert = false
}
The alert is not presented whilst the app is open. When I restart the phone or quit the app completely its again there.
Thank you!
If I am reading your question properly, my suggestion would be to user NSUserDefaults to save a key when the user first opens the view. Then just use an IF statement to decide whether an alertView should be displayed.
Before showing the alert, wherever you want to show it, check the value against the "disalert" key in your userDefaults with this statement:
var disalert: Bool = NSUserDefaults.standardUserDefaults.boolForKey("disalert");
if disalert {
// The alert has already been shown so no need to show it again
}
else
{
// The alert hasn't been shown yet. Show it now and save in the userDefaults
// After showing the alert write this line of code
NSUserDefaults.standardUserDefaults.setBool(true, forKey: "disalert")
}
Adeel's code worked for me, with a slight improvement:
var disalert: Bool =
NSUserDefaults.standardUserDefaults().boolForKey("disalert");
if disalert {
// The alert has already been shown so no need to show it again
}
else
{
// The alert hasn't been shown yet. Show it now and save in the userDefaults
// After showing the alert write this line of code
NSUserDefaults.standardUserDefaults.setBool(true, forKey: "disalert")
}
NSUserDefaults cried for the following: NSUserDefaults.standardUserDefaults()

Resources