iOS 15 XCUITest addUIInterruptionMonitor - ios

Had a few tests that broke after updating to iOS 15 and Xcode 13 because they are no longer able to dismiss alerts that pop up.
I have been using addUIInterruptionMonitor and had it working from the beginning of testing on my project.
addUIInterruptionMonitor(withDescription: "System Dialog") { alert -> Bool in
let dontAllowButton = alert.buttons.element(boundBy: 0)
if dontAllowButton.exists {
dontAllowButton.tap()
}
return true
}
app.tap()

Related

INVoiceShortcutCenter.shared.setShortcutSuggestions(suggestions) crashes in iOS 13 Beta. Xcode 11 beta

My code is pretty straight forward.
I am not sure what is the issue and I am not able to find a solution.
var suggestions = [INShortcut]()
for component in components
{
if let userActivity = UserActivityGenerator.getUserActivity(component: component)
{
let shortCut = INShortcut.init(userActivity: userActivity)
suggestions.append(shortCut)
}
}
INVoiceShortcutCenter.shared.setShortcutSuggestions(suggestions)
The app crashes in setShortcutSuggestions.
It works fine in iOS 12 with same Xcode 11 Beta.
Execute the function in the main queue.
DispatchQueue.main.async {
INVoiceShortcutCenter.shared.setShortcutSuggestions(suggestions)
}

XCUITest Springboard Assertion Failure

I'm writing a framework around XCUITest that adds all kinds of convenient features (such as local HTML reports, Testrail integration, etc.) and I found that for our purpose it makes sense to have one class that extends XCTestCase and run all our tests from that (instead of having every test case class extend from XCTestCase and launch it via schemes which is very awkward). Our actual test classes (called Features) are then started sequentially from the one XCUITest method invocation.
This all works well to the point when I want to delete and re-install the tested app in between test cases.
I'm using the class from Is there a way to reset the app between tests in Swift XCTest UI in Xcode 7? to control the springboard via XCUITest.
The app is re-installed before every test case and it succeeds before the first test but then before the second test case I always get an error and the test runner quits:
t = 47.84s Find the Application "com.apple.springboard" 0x6080000ac2a0 (retry 2)
t = 47.84s Snapshot accessibility hierarchy for com.apple.springboard
t = 47.90s Assertion Failure: Springboard.swift:46: (null)
The error happens when resolve() is called a second time:
// Resolve the query for the springboard rather than launching it
springboard.resolve()
My related framework method which deletes and reinstalls the app:
func reinstallApp()
{
Springboard.deleteApp()
app.launchArguments.append("--uitesting")
app.launch()
}
Does anyone know a workaround that would prevent this error?
Update for Xcode 9:
I was hoping to fix the issue with Xcode 9 and Swift 4 and the new XCUIApplication.activate() method. My Springboard proxy class looks like this:
class Springboard
{
static let springboard:XCUIApplication? = XCUIApplication(bundleIdentifier: "com.apple.springboard")
static let settings:XCUIApplication? = XCUIApplication(bundleIdentifier: "com.apple.Preferences")
class func deleteApp()
{
XCUIApplication().terminate()
if let springboard = springboard
{
springboard.activate()
/* Force delete the app from the springboard. */
let icon = springboard.icons["appname"]
if icon.isHittable
{
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 1.3)
/* Tap the little "X" button at approximately where it is. The X is not exposed directly. */
springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX, dy: (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
springboard.alerts.buttons["Delete"].tap()
/* Press home once to make the icons stop wiggling. */
XCUIDevice.shared.press(.home)
/* Press home again to go to the first page of the springboard. */
XCUIDevice.shared.press(.home)
/* Wait some time for the animation to end. */
Thread.sleep(forTimeInterval: 0.5)
if let settings = settings
{
let settingsIcon = springboard.icons["Settings"]
if settingsIcon.isHittable
{
settingsIcon.tap()
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
settings.buttons["Reset Warnings"].tap()
settings.terminate()
}
else
{
XCUIDevice.shared.press(.home)
let settingsIcon = springboard.icons["Settings"]
if settingsIcon.isHittable
{
settingsIcon.tap()
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
settings.buttons["Reset Warnings"].tap()
settings.terminate()
}
}
}
else
{
print("iOS Settings app not found!")
}
}
else
{
print("App icon not found!")
}
}
else
{
print("Springboard not found!")
}
}
}
But now I'm getting the following error:
Non-critical error encountered: Automatic screenshot failure.
Linked XCTest.framework from /Users/user/Library/Developer/CoreSimulator/Devices/B9AC848C-37C8-46D6-8322-ED78E7201CFE/data/Containers/Bundle/Application/5AB3F814-9DF8-4F1C-BC46-478C42951E60/UITests-Runner.app/Frameworks/XCTest.framework/XCTest, built with Xcode 0900(9A221a), modified on Thursday, September 28, 2017 at 3:10:06 PM Japan Standard Time
XCTest.framework bundle version: 13201
Executing on OS: Version 11.0 (Build 15A372)
Screenshot request returned nil with no additional error information.
Please file a bug and attach the details above.
Does anyone know how to solve this new issue?

dismiss location services request dialog

at the start of my UI test I have
addUIInterruptionMonitor(withDescription: "Location Dialog") { (alert) -> Bool in
let button = alert.buttons["Allow"]
if button.exists {
snapshot("request location service")
button.tap()
return true
}
return false
}
which should dismiss the location services request dialog, but it does nothing and it never reaches the handler. I have also tried to set this code in setUp() but it didn't work either.
I think the problem might be that the first thing that happens in the app is that the dialog is being shown, it may be too soon (it may happen before addUIInterruptionMonitor is called)
How can I solve this issue?
You have to interact with the app right after adding the UIInterruptionMonitor. This can be a simple tap:
addUIInterruptionMonitor(withDescription: "Location Dialog") { (alert) -> Bool in
let button = alert.buttons["Allow"]
if button.exists {
button.tap()
return true
}
return false
}
// interact with the app
app.tap()
If app.tap() interferes with your test you could also use app.swipeUp()
Be aware that the location service permission dialog changed in iOS11. There are now 3 Buttons, so you have to use alert.buttons["Always Allow"] to dismiss the dialog.

addUIInterruptionMonitor(withDescription:handler:) not working on iOS 10 or 9

The following tests works fine on iOS 11. It dismisses the alert asking permissions to use the locations services and then zooms in in the map. On iOS 10 or 9, it does none of this and the test still succeeds
func testExample() {
let app = XCUIApplication()
var handled = false
var appeared = false
let token = addUIInterruptionMonitor(withDescription: "Location") { (alert) -> Bool in
appeared = true
let allow = alert.buttons["Allow"]
if allow.exists {
allow.tap()
handled = true
return true
}
return false
}
// Interruption won't happen without some kind of action.
app.tap()
removeUIInterruptionMonitor(token)
XCTAssertTrue(appeared && handled)
}
Does anyone have an idea why and/or a workaround?
Here's a project where you can reproduce the issue: https://github.com/TitouanVanBelle/Map
Update
Xcode 9.3 Beta's Changelogs show the following
XCTest UI interruption monitors now work correctly on devices and simulators running iOS 10. (33278282)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.waitForExistence(timeout: 10) {
allowBtn.tap()
}
Update .exists to .waitForExistence(timeout: 10), detail please check comments.
I had this problem and River2202's solution worked for me.
Note that this is not a fix to get the UIInterruptionMonitor to work, but a different way of dismissing the alert. You may as well remove the addUIInterruptionMonitor setup. You'll need to have the springboard.buttons["Allow"].exists test anywhere the permission alert could appear. If possible, force it to appear at an early stage of the testing so you don't need to worry about it again later.
Happily the springboard.buttons["Allow"].exists code still works in iOS 11, so you can have a single code path and not have to do one thing for iOS 10 and another for iOS 11.
Incidentally, I logged the base issue (that addUIInterruptionMonitor is not working pre-iOS 11) as a bug with Apple. It has been closed as a duplicate now, so I guess they acknowledge that it is a bug.
I used the #River2202 solution and it works better than the interruption one.
If you decide to use that, I strongly suggest that you use a waiter function. I created this one in order to wait on any kind of XCUIElement to appear:
Try it!
// function to wait for an ui element to appear on screen, with a default wait time of 20 seconds
// XCTWaiter was introduced after Xcode 8.3, which is handling better the timewait, it's not failing the test. It uses an enum which returns: 'Waiters can be used with or without a delegate to respond to events such as completion, timeout, or invalid expectation fulfilment.'
#discardableResult
func uiElementExists(for element: XCUIElement, timeout: TimeInterval = 20) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == true"), object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
guard result == .completed else {
return false
}
return true
}

iOS/Swift test for UIAlertController/UIAlertView

I'm developing an app that needs to run on iOS 7 as well as 8. I want to use the UIAlertController if possible, if not fall back to UIAlertView.
I use this test:
let gotUIAlertController:AnyClass? = NSClassFromString("UIAlertController")
if( gotUIAlertController != nil )
{
// Do UIAlertController
}
else
{
// DO UIAlertView
}
This appears to work on iOS8 simulator, in debug mode but when in release mode (or running debug mode with instruments), gotUIAlertController is not nil, so the UIAlertController is trying to present and the app crashes. The deployment target is 7.1 with the bas SDK set to 8.1
Can anyone enlighten me on why this code is executing in such a way on iOS7.1?
I don't know why, but the workaround I found is:
let gotUIAlertController: AnyObject? = objc_getClass("UIAlertController".UTF8String)
if( gotUIAlertController != nil ) {
// Do UIAlertController
}
else {
// DO UIAlertView
}

Resources