How can I increase the failure timeout of an apple Pay operation? - ios

In the current implementation, my payment takes a long time in some cases. Often users have an error like "Apple pay not completed". The question says that in iOS 11 this happens after 15-20 seconds, can I increase this time, if so, how ? If the payment has time to process during this time, the payment in apple pay is successful.

Unfortunately this is not possible from what i know and what i found ,
the onpaymentauthorized method has to be called within 30 seconds , if not the payment is declined . Refer to this , in most cases you only have as much as 30 seconds .
https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778020-onpaymentauthorized

While changing the failure timeout is impossible, there is still a workaround to avoid the "Apple pay not completed" message for cases when, for example, your API call processing the payment has extended timeout. (At the moment of writing this answer, on iOS 13, the ApplePay dialog would timeout itself in around 30 secs).
The trick is to set a one-time timer, which would dismiss the ApplePay dialog just before it decides to give up. Of course your app must always give user proper feedback when the purchase process ends (was it success or failure), 'cause ApplePay dialog won't be able to show anything after you dismiss it.
Example timer:
_ = Timer.scheduledTimer(withTimeInterval: 25, repeats: false) { _ in
guard self.applePayBeingProcessed == true else { return }
if let applePayVC = AppUtil.shared.topMostController() as? PKPaymentAuthorizationViewController {
self.applePayHasTimeouted = true
applePayVC.dismiss(animated: true)
}
}
*applePayBeingProcessed is set to true in paymentAuthorizationViewController(_:didAuthorizePayment:handler:) and set back to false right after calling handler(PKPaymentAuthorizationResult(status: paymentStatus, errors: [error])) - so that the routine called by timer would be skipped when ApplePay dismissed in a normal way via paymentAuthorizationViewControllerDidFinish(_:)
** applePayHasTimeouted is later checked inside a completion of your payment processing API call, if true it means we need to perform actions, that are normally supposed to be performed inside paymentAuthorizationViewControllerDidFinish(_:) (because the latter will never be called after manually closing ApplePay dialog)
*** topMostController() method finds the controller from the top of hierarchy. How to do this is out of scope of current question, there lots of ways to do this, my favorite one is in this answer.

Related

How to limit number of requests to a database

I have an app with a settings page where the settings of each user are stored in a MySQL database. I was wondering what is the best way to update the database for every setting the user changes while sending the minimal number of requests as I'm worried that it will crash if it sends too many( it has happened before).
I was thinking about setting a timer for ~5 seconds when the user first changes a setting, and then reset the timer to 5 seconds again if another setting is changed. Once that timer is finished it will send a request to the server to update all the settings at once. It would also constantly store the new values locally to the app, so if the user closes the app before the 5 seconds are up it will send the request once/if the app loads up again.
Is this viable/what's the best way to go about this?
You need to make some logic functions in your app, so i will try make an pseudo codes below. Hope it will give you an idea. I don`t know the MySQL details but i am trying to explain native Swift way.
First of all you should fetch data partly, I mean if you try to fetch all data at the same time your app can work very slow.. That is why we are doing pagination in the app.
As well as pagination you want to refresh the data fresh 5 seconds so i will use Timer object, which will trigger every 5 seconds the API function and catch data based on pagination. Check your below codes and implement step by step to your project. Happy Coding.
var timer: Timer?
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "loadNewDataAutomatically" with the interval of 5 seconds
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.loadNewDataAutomatically), userInfo: nil, repeats: true)
}
#objc func loadNewDataAutomatically(_ pageNumber: Int, _ pageSize: Int, onSuccess: ((Bool) -> Void)?, onError: ((Error) -> Void)?){
// Call your api here
// Send true in onSuccess in case new data exists, sending false will disable pagination
// If page number is first, reset the list
if pageNumber == 1 { self.list = [YourDataModel]() }
// else append the data to list
self.list.append(apiResponseList)
// If Api responds with error
onError?(apiError)
// Else end success with flag true if more data available
let moreDataAvailable = !apiResponseList.isEmpty
onSuccess?(moreDataAvailable)
}
Assuming that the Database (MySQL) is on a server
You can try using WorkManager for this requirement.
When the user changes their settings, save them locally (which you are already doing)
enqueue a Unique Periodic Work Request using WorkManager & set up the time at what interval should the response be sent to the server.
Minimum time interval is around 15 min. but not guaranteed at exactly 15 minutes,
the system fires it when it seems fit, also according to the Constraints that you set on your Work Request.
You can also use a OneTimeWorkRequest, if you don't need periodic, with an Initial Delay of whatever time you need.
Edit: This question was later edited and ios, swift tags were added where previously it was for android.
If anyone comes here searching for something similar for Android, this would work.

How to test Webview is loaded or not without staticTexts in XCTest in swift

first time I try to write unit test case. Here am blocking with the following scenario.
When I tap a button, it navigates to a next screen and webview will load. I need to wait for webview loading.
I tried to wait till the staticTexts is visible by below code,
let rewards = self.app.staticTexts[“Rewards”]
let exists = NSPredicate(format: “exists == 1”)
expectationForPredicate(exists, evaluatedWithObject: rewards, handler: nil)
waitForExpectationsWithTimeout(10, handler: nil)
XCTAssert(rewards.exists)
XCTAssert(app.staticTexts[“Rewards Overview”].exists)
But In my scenario, when I tap the button it will navigate to next screen and webview will starts to load. But the webview content is always dynamic one. So I need to wait till the webviewDidFinishLoad.
You can use waitForExistence(timeout:) to return a boolean if an element enters existence within the given timeout - I'd recommend using this for your use case.
https://developer.apple.com/documentation/xctest/xcuielement/2879412-waitforexistence
I would recommend writing your own function for waiting, since with waitForExistence(timeout:), you can only wait for existence, but it ignores other states of element(.isHittable, .isEnabled etc) or simply wait for any other boolean.
Plus waitForExistence(timeout:) is painfully slow, if you have a lot of tests with a lot of waits (we stopped using it after we wrote our own waiting). It is caused by the system method waiting for additional 1-2s before returning true, if the object exists.
This might help you, however its not Swift, its ObjC: https://pspdfkit.com/blog/2016/running-ui-tests-with-ludicrous-speed/

How to wait for UI element without failing test case

I have a dialog that displays on first execution of my app. I therefore want my test case to handle responding to the dialog - but only if it appears.
The waitForExpectations method will error out if the timeout is reached.
What is the best way to wait for this element to appear for a short time without failing the test case if it does not appear?
You will have to reimplement waiting. As terrible as it sounds, I recommend using Sleep and If statements; you want to lock your test thread while it waits for the app to finish presenting content, then evaluate.
Unfortunately, as far as I'm aware none of the options for waitForExpectationsWithTimeout permit you to not fail the test if the expectationForPredicate is not fulfilled at the end of the timeout time.
Try the following code:
addUIInterruptionMonitorWithDescription("SYSTEM_DESCRIPTION") { (alert) -> Bool in
alert.buttons["BUTTON_TITLE"].tap()
return true
}
app.buttons["BUTTON_TITLE"].tap()
app.tap()
SYSTEM_DESCRIPTION is the title or description of system alert.
BUTTON_TITLE is like "OK", "Allow", ...
Find out more in this answer:
https://stackoverflow.com/a/32228033/6657951

Xcode 7 UI Testing: how to dismiss a series of system alerts in code

I am writing UI test cases using the new Xcode 7 UI Testing feature. At some point of my app, I ask the user for permission of camera access and push notification. So two iOS popups will show up: "MyApp Would Like to Access the Camera" popup and "MyApp Would Like to Send You Notifications" popup. I'd like my test to dismiss both popups.
UI recording generated the following code for me:
[app.alerts[#"cameraAccessTitle"].collectionViews.buttons[#"OK"] tap];
However, [app.alerts[#"cameraAccessTitle"] exists] resolves to false, and the code above generates an error: Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202".
So what's the best way of dismissing a stack of system alerts in test? The system popups interrupt my app flow and fail my normal UI test cases immediately. In fact, any recommendations regarding how I can bypass the system alerts so I can resume testing the usual flow are appreciated.
This question might be related to this SO post which also doesn't have an answer: Xcode7 | Xcode UI Tests | How to handle location service alert?
Thanks in advance.
Xcode 7.1
Xcode 7.1 has finally fixed the issue with system alerts. There are, however, two small gotchas.
First, you need to set up a "UI Interuption Handler" before presenting the alert. This is our way of telling the framework how to handle an alert when it appears.
Second, after presenting the alert you must interact with the interface. Simply tapping the app works just fine, but is required.
addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Request Location"].tap()
app.tap() // need to interact with the app for the handler to fire
The "Location Dialog" is just a string to help the developer identify which handler was accessed, it is not specific to the type of alert.
I believe that returning true from the handler marks it as "complete", which means it won't be called again. For your situation I would try returning false so the second alert will trigger the handler again.
Xcode 7.0
The following will dismiss a single "system alert" in Xcode 7 Beta 6:
let app = XCUIApplication()
app.launch()
// trigger location permission dialog
app.alerts.element.collectionViews.buttons["Allow"].tap()
Beta 6 introduced a slew of fixes for UI Testing and I believe this was one of them.
Also note that I am calling -element directly on -alerts. Calling -element on an XCUIElementQuery forces the framework to choose the "one and only" matching element on the screen. This works great for alerts where you can only have one visible at a time. However, if you try this for a label and have two labels the framework will raise an exception.
Objective - C
-(void) registerHandlerforDescription: (NSString*) description {
[self addUIInterruptionMonitorWithDescription:description handler:^BOOL(XCUIElement * _Nonnull interruptingElement) {
XCUIElement *element = interruptingElement;
XCUIElement *allow = element.buttons[#"Allow"];
XCUIElement *ok = element.buttons[#"OK"];
if ([ok exists]) {
[ok tap];
return YES;
}
if ([allow exists]) {
[allow tap];
return YES;
}
return NO;
}];
}
-(void)setUp {
[super setUp];
self.continueAfterFailure = NO;
self.app = [[XCUIApplication alloc] init];
[self.app launch];
[self registerHandlerforDescription:#"“MyApp” would like to make data available to nearby Bluetooth devices even when you're not using app."];
[self registerHandlerforDescription:#"“MyApp” Would Like to Access Your Photos"];
[self registerHandlerforDescription:#"“MyApp” Would Like to Access the Camera"];
}
Swift
addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
alert.buttons["Allow"].tap()
alert.buttons["OK"].tap()
return true
}
Gosh.
It always taps on "Don't Allow" even though I deliberately say tap on "Allow"
At least
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
allows me to move on and do other tests.
For the ones who are looking for specific descriptions for specific system dialogs (like i did) there is none :) the string is just for testers tracking purposes. Related apple document link : https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor
Update : xcode 9.2
The method is sometimes triggered sometimes not. Best workaround for me is when i know there will be a system alert, i add :
sleep(2)
app.tap()
and system alert is gone
God! I hate how XCTest has the worst time dealing with UIView Alerts. I have an app where I get 2 alerts the first one wants me to select "Allow" to enable locations services for App permissions, then on a splash page the user has to press a UIButton called "Turn on location" and finally there is a notification sms alert in a UIViewAlert and the user has to select "OK". The problem we were having was not being able to interact with the system Alerts, but also a race condition where behavior and its appearance on screen was untimely. It seems that if you use the alert.element.buttons["whateverText"].tap the logic of XCTest is to keep pressing until the time of the test runs out. So basically keep pressing anything on the screen until all the system alerts are clear of view.
This is a hack but this is what worked for me.
func testGetPastTheStupidAlerts() {
let app = XCUIApplication()
app.launch()
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
app.buttons["TURN ON MY LOCATION"].tap()
}
The string "Allow" is completely ignored and the logic to app.tap() is called evreytime an alert is in view and finally the button I wanted to reach ["Turn On Location"] is accessible and the test pass
~Totally confused, thanks Apple.
The only thing I found that reliably fixed this was to set up two separate tests to handle the alerts. In the first test, I call app.tap() and do nothing else. In the second test, I call app.tap() again and then do the real work.
On xcode 9.1, alerts are only being handled if the test device has iOS 11. Doesn't work on older iOS versions e.g 10.3 etc. Reference: https://forums.developer.apple.com/thread/86989
To handle alerts use this:
//Use this before the alerts appear. I am doing it before app.launch()
let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow'")
//1st alert
_ = addUIInterruptionMonitor(withDescription: "Allow to access your location?") { (alert) -> Bool in
let alwaysAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch
if alwaysAllowButton.exists {
alwaysAllowButton.tap()
return true
}
return false
}
//Copy paste if there are more than one alerts to handle in the app
#Joe Masilotti's answer is correct and thanks for that, it helped me a lot :)
I would just like to point out the one thing, and that is the UIInterruptionMonitor catches all system alerts presented in series TOGETHER, so that the action you apply in the completion handler gets applied to every alert ("Don't allow" or "OK"). If you want to handle alert actions differently, you have to check, inside the completion handler, which alert is currently presented e.g. by checking its static text, and then the action will be applied only on that alert.
Here's small code snippet for applying the "Don't allow" action on the second alert, in series of three alerts, and "OK" action on the remaining two:
addUIInterruptionMonitor(withDescription: "Access to sound recording") { (alert) -> Bool in
if alert.staticTexts["MyApp would like to use your microphone for recording your sound."].exists {
alert.buttons["Don’t Allow"].tap()
} else {
alert.buttons["OK"].tap()
}
return true
}
app.tap()
This is an old question but there is now another way to handle these alerts.
The system alert isn't accessibly from the app context of the app you are launched in, however you can access the app context anyway. Look at this simple example:
func testLoginHappyPath() {
let app = XCUIApplication()
app.textFields["Username"].typeText["Billy"]
app.secureTextFields["Password"].typeText["hunter2"]
app.buttons["Log In"].tap()
}
In a vacuum with a simulator already launched and permissions already granted or denied, this will work. But if we put it in a CI pipeline where it gets a brand new simulator, all of the sudden it won't be able to find that Username field because there's a notification alert popping up.
So now there's 3 choices on how to handle that:
Implicitly
There's already a default system alert interrupt handler. So in theory, simply trying to typeText on that first field should check for an interrupting event and handle it in the affirmative.
If everything works as designed, you won't have to write any code but you'll see an interruption logged and handled in the log, and your test will take a couple seconds more.
Explicitly via interruptionmonitor
I won't rewrite the previous work on this, but this is where you explicitly set up an interruptionmonitor to handle the specific alert being popped up - or whatever alerts you expect to happen.
This is useful if the built-in handler doesn't do what you want - or doesn't work at all.
Explicitly via XCUITest framework
In xCode 9.0 and above, you can switch between app contexts fluidly by simply defining multiple XCUIApplication() instances. Then you can locate the field you need via familiar methods. So to do this explicitly would look like the following:
func testLoginHappyPath() {
let app = XCUIApplication()
let springboardApp = XCUIApplication(bundleidentifier: "com.apple.springboard")
if springboardApp.alerts[""FunHappyApp" would like permission to own your soul."].exists {
springboardApp.alerts.buttons["Allow"].tap()
}
app.textFields["Username"].typeText["Billy"]
app.secureTextFields["Password"].typeText["hunter2"]
app.buttons["Log In"].tap()
}
Sounds like the approach to implementing camera access and notifications are threaded as you say, but not physically managed and left to chance when and how they are displayed.
I suspect one is triggered by the other and when it is programatically clicked it wipes out the other one as well (which Apple would probably never allow)
Think of it you're asking for a users permission then making the decision on their behalf? Why? Because you can't get your code to work maybe.
How to fix - trace where these two components are triggering the pop up dialogues - where are they being called?, rewrite to trigger just one, send an NSNotification when one dialogue has been completed to trigger and display the remaining one.
I would seriously discourage the approach of programatically clicking dialogue buttons meant for the user.

UIAutomation and XCTestCase: how to wait for a button to activate

I'm writing a UIAutomation test case and I need to wait for the user to be activated before continuing. There doesn't seem to be a nice way to check for a button to change to the enabled state.
Whats the best was to wait for something to happen in the UI before checking it's status?
Neither dispatch_after nor NSTimer seem to work. They just block then fail.
It's actually pretty easy if you use NSPredicates and expectations. You can even set a timeout value. This example shows you how to do it with a 5 second timeout.
let exists = NSPredicate(format:"enabled == true")
expectationForPredicate(exists, evaluatedWithObject: app.tables.textFields["MY_FIELD_NAME"], handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)
The better way to wait and check an element isn't the delay() function, but the pushTimeout() one. Apple recommends to use the second function. Here is a code sample:
UIATarget.localTarget().pushTimeout(10)
button.tap()
UIATarget.localTarget().popTimeout()
Apple will repeatedly try to tap the button and will wait up to 10 seconds. Here's a link to the documentation.
Etienne's answer is correct but in my scenario it required something extra.
I'm using React Native and had a <TouchableWithoutFeedback disabled={true}> component. However I could see that whenever XCUI tried to check its state it considered it enabled. Indeed, using a breakpoint and checking element.IsEnabled clearly contradicted what I was seeing in the UI.
Using AccessibilityState will achieve this however, for example:
<TouchableWithoutFeedback
accessibilityState={{
disabled: this.props.disabled,
}}
>
Experienced with RN 0.62.2
You should be able to implement a while loop to check for the condition you want (e.g. button enabled). That will stop the test case progress until the while condition is met and the tests will continue. Build in a delay to slow polling and make sure you have a timeout so you don't get stuck indefinitely.
Pseudocode:
While (/*button is disabled*/) {
if (/*timeout condition met*/) {
/*handle error*/
break;
}
UIATarget.delay(<duration in seconds>);
}

Resources