UITest interruption handler calling tap() twice - ios

My UITest requires a button to be tapped and the UIInteruptionMonitor should handle it. However, what happens is the button gets tapped, the interruption appears and gets handled and then it tries to tap the button again. It seems to think it hasn't actually tapped the button when it has...
I have the following code:
addUIInterruptionMonitor(withDescription: "Permissions") { alert -> Bool in
let okButton = alert.buttons["OK"]
if okButton.exists {
okButton.tap()
}
return true
}
app.buttons["Enable"].tap()
What happens is the following:
t = 91.24s Find the "Enable" Button
t = 91.40s Check for interrupting elements affecting "Enable" Button
t = 91.42s Wait for com.apple.springboard to idle
t = 91.85s Found 1 interrupting element:
t = 91.86s Find the "“MyApp” Would Like to Access the Camera" Alert
t = 92.03s "“MyApp” Would Like to Access the Camera" Alert from Application 'com.apple.springboard'
t = 92.03s Invoking UI interruption monitors for "“MyApp” Would Like to Access the Camera" Alert from Application 'com.apple.springboard'
t = 92.03s Invoking Permissions
t = 92.04s Checking existence of `"OK" Button`
t = 92.21s Tap "OK" Button
t = 92.21s Wait for com.apple.springboard to idle
t = 92.50s Find the "OK" Button
t = 92.66s Check for interrupting elements affecting "OK" Button
t = 92.82s Synthesize event
t = 93.14s Wait for com.apple.springboard to idle
t = 93.54s Verifying handling...
t = 93.54s Check for interrupting elements affecting "Enable" Button
t = 93.55s Wait for com.apple.springboard to idle
t = 94.10s Confirmed successful handling of interrupting element
t = 94.10s Synthesize event
t = 95.39s Scroll element to visible
t = 96.55s Failed: Failed to scroll to visible (by AX action) Button, label: 'Enable', error: Error kAXErrorCannotComplete performing AXAction 2003 on element AX element pid: 62934, elementOrHash.elementID: 140664883255456.613
t = 97.57s Retrying `Tap "Enable" Button` (attempt #2)

This is normal. When interrupted by a system alert, the intended action could not be performed, so it is retried after successful handling of the interruption.
More generally, you must check within the handler if you dealt with the interruption successfully (e.g. you found the ok button and could tap it), otherwise, you must return false.

Related

Hide Activity Indicator when firebase authentication redirects to recapcha window (iOS - Swift)

I have implemented a Phone Authentication in my iOS app using Firebase, In that case, I have added an activity indicator to show a loading window. But, in some cases a ReCaptcha appears and, since the activity indicator is still in the window, the ReCaptcha cannot be completed, which makes the app freeze in one state.
Below is my code for login:
let activityIndicator = EAActivityIndicator()
activityIndicator.show()
Auth.auth().languageCode = "en"
PhoneAuthProvider.provider().verifyPhoneNumber(mobileNumber, uiDelegate: nil) { verificationId, error in
activityIndicator.hide()
if let error = error {
// handle error
} else {
self.navigationController?.pushViewController(ViewController(), animated: true)
}
}
As in the code, I show an activity indicator when the login button is tapped, if the mobile number is correct and if a 6 digit code is sent it will redirect to the Code entering View Controller. In certain cases, a ReCaptcha appears, but since the activity indicator is still on the screen, the ReCaptcha cannot be completed.
Screenshots:

UITests: cannot disable precise location on iOS 14.5

I have a UITest (implemented using XCTest) in which I want to test my app’s behaviour when user want to disable precise location (a new feature introduced in iOS 14).
When location authorization system alert appears (check attached screenshot), I can access it with the following code
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let locationAlert = springboard.alerts.element
And I can access the precise location button with:
locationAlert.buttons["Esatta: sì"]
However, when interacting with it, the alert is considered as a "blocking" element and the default "interruption monitor" is invoked, resulting in the alert to be dismissed
t = 834.87s Check for interrupting elements affecting "Esatta: sì" Button
t = 834.92s Found 1 interrupting element:
t = 834.92s Find the "Vuoi consentire a “*****” di utilizzare la tua posizione?" Alert
t = 834.97s "Vuoi consentire a “*****” di utilizzare la tua posizione?" Alert from Application 'com.apple.springboard'
t = 834.97s Invoking UI interruption monitors for "Vuoi consentire a “*****” di utilizzare la tua posizione?" Alert from Application 'com.apple.springboard'
t = 834.97s Find the "Vuoi consentire a “*****” di utilizzare la tua posizione?" Alert
t = 835.01s Checking existence of `Button`
t = 835.06s Get all elements bound by accessibility element for: Elements matching predicate 'userTestingAttributes CONTAINS "default-button"'
t = 835.14s Checking existence of `"Non consentire" Button`
t = 835.19s Default interruption handler attempting to dismiss alert by tapping "Non consentire" Button.
t = 835.19s Tap "Non consentire" Button
t = 835.19s Wait for com.apple.springboard to idle
t = 835.22s Find the "Non consentire" Button
t = 835.28s Check for interrupting elements affecting "Non consentire" Button
t = 835.33s Synthesize event
t = 835.42s Wait for com.apple.springboard to idle
t = 835.84s Verifying handling...
t = 835.84s Check for interrupting elements affecting "Esatta: sì" Button
t = 835.85s Wait for xxxxxx to idle
t = 835.91s Confirmed successful handling of interrupting element
This only occurs when using Xcode 12.5, I don't have any problem with previous versions.

Handler of addUIInterruptionMonitor is not called for Alert related to Photos

private func acceptPermissionAlert() {
_ = addUIInterruptionMonitor(withDescription: "") { alert -> Bool in
if alert.buttons["Don’t Allow"].exists { //doesnt get here second time
alert.buttons.element(boundBy: 1).tapWhenExists()
return true
}
return false
}
}
and this doesn't work for:
In the beginning of the app, it works perfect while accepting permission for notifications, but here, it doesn't work. Why is this?
I'vs found that addUIInterruptionMonitor sometimes doesn't handle an alert in time, or until tests have finished. If it isn't working, try using Springboard, which manages the iOS home screen. You can access alerts, buttons, and more from there, and this is particularly useful for tests where you know exactly when an alert will show.
So, something like this:
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let alertAllowButton = springboard.buttons.element(boundBy: 1)
if alertAllowButton.waitForExistence(timeout: 5) {
alertAllowButton.tap()
}
The buttons.element(boundBy:1) will ensure you tap the button on the right, change 1 to 0 to tap the left, (because sometimes the ' in "Don't Allow" causes a problem).
Add:
app.tap()
at the end of the method.
This is because you need to interact with the app for the handler to fire.
After adding the interruption monitor, you should continue to interact with the app as if it has not appeared.
Also note that you have a 'smart quote' in your button identifier, instead of a regular apostrophe.
let photosAlertHandler = addUIInterruptionMonitor(withDescription: "Photo Permissions") { alert -> Bool in
if alert.buttons["Don't Allow"].exists {
alert.buttons.element(boundBy: 1).tapWhenExists()
return true
}
return false
}
// Do whatever you want to do after dismissing the alert
let someButton = app.buttons["someButton"]
someButton.tap() // The interruption monitor's handler will be invoked if the alert is present
When the next interaction happens after the alert appears, the interruption monitor's handler will be invoked and the alert will be handled.
You should also remove the interruption monitor when you think you're done with it, otherwise it will be invoked for any other alerts that appear.
removeUIInterruptionMonitor(photosAlertHandler)

Why Cordova resume event is not firing in power lock mode / Sleep mode in iOS using sencha

In my application if user lock the mobile.I need to navigate to login screen.I have implemented resume event to navigate login screen if user unlock the device.can anybody tell Why Cordova resume event is not firing in power lock mode / Sleep mode in iOS
is there any other event i need to use in lock mode?
P.S It is working in minimizing the app and maximizing the app
Although in IOS a resume event is fired when minimizing or maximizing an app by pressing the home button, it appears that a resume event is not fired when "closing" or "staring" the app by pressing the power button at least in IOS.
A possible JS-solution might be to check for inactivity. Let's say when an app has not not received any events triggered by an user for some time(30 seconds and if no real pause-event has been fired since) for instance click/touch-events then it can be assumed that the app can still execute some code(so it's still in foreground) and is "paused":
// threshold for inactivity state
var idleTimeout = 30000;
// variable that holds the time in seconds, which indicates how long the app has not received certain events
var timeInSecondsPassed = 0;
// interval instance
var intervalInstance = null;
// variable to handle the transition from "pause" to "resume" state
var inPauseState = false;
function startPauseListener() {
timeInSecondsPassed = 0;
var resetPassedTime = function(){
timeInSecondsPassed = 0;
// has the app reached the "pause" state and
// currently receiving certain events -> the "resume" state is reached
if(inPauseState){
inPauseState = false;
// the "resume" state is reached here
// so the same code might be executed here as it is in the resume-listener
}
};
document.ontouchstart = resetPassedTime;
document.onclick = resetPassedTime;
document.onscroll = resetPassedTime;
document.onkeypress = resetPassedTime;
intervalInstance = setInterval(checkPauseState,1000);
}
function clearPauseListener() {
clearInterval(intervalInstance);
timeInSecondsPassed = 0;
}
function checkPauseState() {
timeInSecondsPassed += 1000;
if (timeInSecondsPassed >= idleTimeout) {
inPauseState = true;
timeInSecondsPassed = 0;
// run further code here to handle "pause" state
// at this point it is assumed as soon as the app receives click/touch-events again a "resume" state is reached.
}
}
function onDeviceReady() {
// handle android devices so that the interval is stopped when a real pause event is fired and started when a real resume event is fired
document.addEventListener("resume", function(){
startPauseListener();
// your actual code to handle real resume events
}, false);
document.addEventListener("pause", function(){
clearPauseListener();
}, false);
}
It has to be noted that when the app is really paused so a pause event is fired the code above is not run in IOS but in android so that's why you might have to handle this szenario in android differently by taking advandage of both resume and pause-Listener for in android when the app is minimized by the home button the interval would still be executed and consumes CPU in the background.
And please also note that it's only a kind of concept-code and not tested in any device!!!
Hope this helps.
There's an iOS-specific event called active that "detects when users disable the Lock button to unlock the device with the app running in the foreground".
Check the documentation at the bottom of the resume doc page:
https://cordova.apache.org/docs/en/5.1.1/cordova/events/events.resume.html

Cannot tap UIBarButtonItem in Xcode UI Test

My navigation bar as an "Add" button on it, and I need to have Xcode's UI test tap that button to perform tests in the view controller it opens. I add the button programmatically like so:
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(showAddVC)];
self.navigationItem.rightBarButtonItem = addButton;
And in my test I have:
XCUIApplication *app = [[XCUIApplication alloc] init];
XCTAssert([app.buttons[#"Add"] exists]); // <-- This passes, so the test runner does see the button.
But when I try to tap it using either:
// Generated using the test recorder
[app.navigationBars[#"App Title"].buttons[#"Add"] tap];
or:
// Same expression used with the XCTAsset earlier
[app.buttons[#"Add"] tap];
Nothing happens. The action that should take place when the button is tapped does not happen. I tried adding some sleep(5)'s between lines to let the app load, but that didn't help much.
This is the test log:
Test Case '-[xx]' started.
t = 0.00s Start Test
t = 0.00s Set Up
2015-12-22 16:25:02.898 XCTRunner[10978:384690] Continuing to run tests in the background with task ID 1
t = 0.94s Launch xx
t = 1.01s Waiting for accessibility to load
t = 3.45s Wait for app to idle
t = 9.02s Tap "Add" Button
t = 9.02s Wait for app to idle
t = 39.07s Assertion Failure: UI Testing Failure - App failed to quiesce within 30s
xx: error: -[xx] : UI Testing Failure - App failed to quiesce within 30s
None of the above answers worked for me. What finally made it work, after hours of struggle, was repeating the tap. Try this:
[app.navigationBars[#"App Title"].buttons[#"Add"] tap];
[app.navigationBars[#"App Title"].buttons[#"Add"] tap];
While the above worked for me initially, I found that sometimes the first tap worked, which would result in two taps. My solution to this was to instead tap an arbitrary UI element that doesn't trigger any actions at the beginning of the UI test, and then proceed as normal. I think that the first tap works on certain devices, or maybe after the first UI test is run.
Testing for exists does not seem to be sufficient in your case. Wait for the button to be hittable before trying to tap it.
expectationForPredicate(predicate, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(timeoutSeconds, handler: nil)
Where in your case it would be:
expectationForPredicate(NSPredicate(format: "hittable == YES"), evaluatedWithObject: [app.buttons[#"Add"], handler: nil)
waitForExpectationsWithTimeout(15, handler: nil)
[app.buttons[#"Add"] tap];
This will pause execution of code after the waitForExpectationWithTimeout until that predicate has been satisfied with the given element.
Otherwise, in extreme cases I have found that sometimes errors occur when trying to interact with certain components. How, why and when these occur is a bit of a mystery, but they seem to be somewhat consistent to certain components, and things involving UINavigationBars seem to have them happen more often.
To overcome these, I have found that using this extension will sometimes work.
extension XCUIElement {
/* Sends a tap event to a hittable/unhittable element. Needed to get past bug */
func forceTapElement() {
if hittable {
tap()
}
else {
let coordinate: XCUICoordinate = coordinateWithNormalizedOffset(CGVectorMake(0.0, 0.0))
coordinate.tap()
}
}
}
For those for whom Alex's answer is not working, try this:
extension XCUIElement {
func forceTap() {
coordinate(withNormalizedOffset: CGVector(dx:0.5, dy:0.5)).tap()
}
}
I just had an issue with UIWebView which is hittable but the tap didn't work until done it via coordinate
A possibility is your bar button item pushes a new view controller that has a refresh control - you might especially encounter this on iPads only where your bar button item pushes onto the detail part of a split view controller. After fetching your items, you might stop your refresh control that's not even refreshing in the first place. Xcode's automation does throw hissy fits about this and sleeps/timeouts won't help there.
So to fix this issue, on your main app, always check your refresh control is refreshing before ending its refresh. I also like to check it's not nil in the first place: if you're using Swift, you can check this in one fell swoop:
if let refresh = self.refreshControl, refresh.refreshing {
refresh.endRefreshing()
}

Resources