How can I change the time required to get a snapshot in Xcode UI Test (XCUITest)? - ios

I'm doing some performance experiments and I keep getting this error with my Xcode UI Tests since no new UI Test statement is hit:
UITesting Failure - Failed to get snapshot within 15.0s
How can I change this 15.0s variable to something longer? Is there some configuration or setting that I can change?

You can use expectations to allocate an arbitrary amount of time to run any test case. You just create an additional expectation right before 15.0s has passed, and repeat this process as many times as necessary. Here's a short code sample to illustrate:
var timeToDelay = 60.0
repeat {
let delay = min(13.0, timeToDelay)
timeToDelay -= delay
let date = Date().addingTimeInterval(delay)
let predicate = NSPredicate(format: "now() > %#", argumentArray: [date])
self.expectation(for: predicate, evaluatedWith: [], handler: nil)
self.waitForExpectations(timeout: 14.0, handler: nil)
} while timeToDelay > 0
Place this right before you see the testing failure and replace timeToDelay with the amount of additional time you need (in seconds).

I don't think this is possible. Apple has a system limit of 20 seconds for any app to start up, if it takes longer than that your app will be killed. Source
Because UI tests require bootstrapping your app, Apple has given itself ~5 seconds to perform all of it's setup as well. So essentially you have a 15 second limit to launch your app.
Because it's not possible to override the system's 20 second limit, it's also not going to be possible to override XCTest's 15 second limit.

Related

How to check an element's existence after a loading animation in a UI Test? Swift

The problem is simple: How to check an element's existence after a loading animation in a UI Test? BUT without using static timeouts.
For example: If animation lasts 3 seconds ui test waits for 3 seconds, if animation lasts 10 seconds ui test waits for 10 seconds or is there another way around it?
I'm trying to wait as long as my api call lasts simply
You can tackle this in one of two two ways: 1) Wait for your element to exist or 2) Wait for the animation to disappear.
In order to wait for your element to exist, you'd use Apple's waitForExistence function with a long timeout on your target element. This returns a boolean so you can simply assert directly on it.
XCTAssertTrue(myElement.waitForExistence(timeout: 15.0)) // wait for 15 seconds maximum
In order to wait for your animation to disappear, you'd identify it, and extend XCUIElement with the following function, which I use extensively and therefore bundle into my XCToolbox Cocoapod. You'd then be able to check the exists property on your target element.
public func waitForDisappearance(timeout: TimeInterval = Waits.short.rawValue) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: UIStatus.notExist.rawValue), object: self)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
switch result {
case .completed:
return true
default:
return false
}
}
This code would look like the following:
_ = animationElement.waitForDisappearance(timeout: 15.0)
XCTAssertTrue(myElement.exists)
Neither solution is wrong. The first is less code and arguably cleaner, the second is more explicit and possibly more readable.

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/

XC UITesting flickering with finding UIElements

I have a section of code that runs if the user needs to re-auth after logging in. During UI tests, this popover is sometimes displayed, so I have a check for it existing
if (XCUIApplication().staticText["authLabel"].exists) {
completeAuthDialog()
}
When this runs locally, it is fine, completes and the framework finds the element no problem. But when the nightly job on the CI is ran, it fails the first time, but once the same build is set to be rebuilt, the test passes. authLabel is the UILabel's accessibility identifier(btw), so I have been trying to figure out what is causing the flickering.
Yesterday I spent time on the issue, and it seems that the framework just doesn't find the elements sometimes? I have used the accessibility inspector in ensure I am query for the same time it sees.
I even expanded that if check with 4 or 5 additional || to check for any element inside of the popover. The Elements all have accessibility identifiers, I have also used the record feature to ensure that it passes back the same element "names" I am using.
I am kind of stuck, I don't know what else to try/could be causing this issue. The worst part is it ran fine for couple of months, but it seems to fail every night now, and as I said when the tests are ran locally inside xcode they pass fine. Could this be a issue with building from command line?
It is often slower when your tests execute on a different machine, this problem seems particularly prevelant with CI machines as they tend to be under-powered.
If you just do a single check for an element existing, the test only has one point in time to get it right and if the app was slow to present the element then the test will fail.
You can defend against having a flaky test by using a waiter to check a few times over a few seconds to ensure that you've given the app enough time to show the authentication dialog before continuing.
let authElement = XCUIApplication().staticText["authLabel"]
let existsPredicate = NSPredicate(format: "exists == true")
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate, object: authElement)
let result = XCTWaiter().wait(for: [expectation], timeout: 5)
if (result == .completed) {
completeAuthDialog()
}
You can adjust the timeout to suit your needs - a longer timeout will result in the test waiting a longer time to continue if the auth dialog doesn't appear, but will give the dialog more time to appear if the machine is slow. Try it out and see how flaky the tests are with different timeouts to optimise.

CloudKit operations execute in development version but sometimes hang in production version

I make calls to the iCloud database in two different ways in my app:
1.
Calling a CKDatabase convenience method works perfectly in development (simulator) and production (device) environments:
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
privateDatabase.fetchRecordWithID(recordID, completionHandler: { (record, error) -> Void in
// handle record and errors
})
2.
Adding CKOperations to the main queue works perfectly in the development environment, and works for a while when I test in the production environment - but then arbitrarily, after a few hours of intermittent testing, this call to the database just starts hanging - no errors are produced, and no completion code is executed. (The fetchRecordWithID call still works perfectly all the time.)
let op1 = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
op1.modifyRecordsCompletionBlock = { (savedRecords, deletedRecordIDs, error) -> Void in
// handle records and errors
}
op1.database = privateDatabase
// let op2, op3...
NSOperationQueue.mainQueue().addOperations([
op1,
op2,
op3,
], waitUntilFinished: false)
In the Usage panel in the CloudKit dashboard, nothing is even remotely approaching a limit. What other variables am I not considering - or what other tests can I run to figure out what's going wrong?
Try setting the op1, op2 and op3.qualityOfService = .UserInitiated
That setting has been set to a 'lower' standard value in iOS 9.
The documentation states for the qualityOfService:
The relative amount of importance for granting system resources to the
operation. Service levels affect the priority with which an operation
object is given access to system resources such as CPU time, network
resources, disk resources, and so on. Operations with a higher quality
of service level are given greater priority over system resources so
that they may perform their task more quickly. You use service levels
to ensure that operations responding to explicit user requests are
given priority over less critical work. This property reflects the
minimum service level needed to execute the operation effectively. The
default value of this property is
NSOperationQualityOfServiceBackground and you should leave that value
in place whenever possible. When changing the service level, use the
minimum level that is appropriate for executing the corresponding
task. For example, if the user initiates a task and is waiting for it
to finish, assign the value NSOperationQualityOfServiceUserInitiated
to this property. The system may give the operation a higher service
level to the operation if the resources are available to do so.

Resources