XCUITest; how to find StatusBar - ios

I am trying to create a XCUITest where I like to verify that iOS' Status Bar is either:
hidden; or
visible.
But I cannot 'find' it... for example:
func testExample() throws {
let app = XCUIApplication()
app.launch()
let statusBar1 = app.descendants(matching: .statusBar)
let statusBar2 = app.statusBars.element(boundBy: 0)
}
When do po statusBar1 in the console I get an empty result:
Find: Target Application 'com.domain.TestStatusBar'
↪︎Find: Descendants matching type StatusBar
Any clue of how to find it?
Thanks!

It's now accessible as a springboard descendant:
class Springboard {
static let shared = XCUIApplication(bundleIdentifier: "com.apple.springboard")
static var statusBar: XCUIElement {
// There are always two statusBars,
// but only one of them is accessible when we need to tap it
if XCUIDevice.shared.orientation.isPortrait {
return Springboard.shared.statusBars.firstMatch
}
else {
return Springboard.shared.statusBars.element(boundBy: 1)
}
}
}

As of iOS 12, the status bar is no longer accessible through XCTest as it is a part of the system application.
If you check in iOS 11 & below, app.descendants(matching: .statusBar).element should have the expected result.
In other words, you can access only those XCUIElements that live within your own application window.
Credits*Applies to XCTest

Related

CallKit error 7 when I perform a call for the first time

I've done a call kit + twilio IOS app. The problem is (as far as I can tell) with ios 12.
When I run the app on a device with IOS 11 the call start as normal. When I run the app on a device with IOS 12, when I try to make the first call I get this error :
StartCallAction transaction request failed: The operation couldn’t be
completed. (com.apple.CallKit.error.requesttransaction error 7.)
This error represent this: The requested transaction contains actions that, if performed, would exceed the maximum number of call groups for the provider. But i set the callGroupMax number to 1 ( I tried to set it 2,3 but still the same)
I found just one thread with this error on google but no solution was provided. Pleas give me a hint on what causes this error because I'm stuck on this.
This error appears only when the first call is made after a fresh install. Then I can make calls as it was intended.
This is the callkitManager class:
class CallKitManager: NSObject {
class var shared: CallKitManager {
struct Static {
static let instance: CallKitManager = CallKitManager()
}
return Static.instance
}
fileprivate let callKitProvider: CXProvider
override init() {
callKitProvider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
callKitProvider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let localizedName = NSLocalizedString("NAME", comment: "Name of application")
let configuration = CXProviderConfiguration(localizedName: localizedName)
configuration.supportsVideo = false
configuration.maximumCallsPerCallGroup = 1
configuration.ringtoneSound = "myringtone"
configuration.supportedHandleTypes = [.generic]
if let callKitIcon = UIImage(named: "callKitIcon") {
configuration.iconTemplateImageData = callKitIcon.pngData()
}
return configuration
}
I expect that the call to connect from the first time, but the result is that in the performStartCallAction() method I get the error from above.
So after 2 days I figure it out. The problem was that I use the callKit as a singleton which is wrong. You need "to mimic" a singleton using AppDelegate. See this tutorial https://www.raywenderlich.com/701-callkit-tutorial-for-ios and look in the AppDelegate and se how this was implemented.

Compile-time test for availability of UIApplication.shared?

I have some shared code that needs to work in both iOS apps and app extensions, and needs to set UIApplication.shared.isNetworkActivityIndicatorVisible — but only if the code is used in an app.
In an app extension, UIApplication.shared gives this compile error:
'shared' is unavailable: Use view controller based solutions where appropriate instead
That’s fine; I don’t want to use it in the app extension. However, I’m unable to find a way to disable that code at compile time. Sadly, if #available doesn’t seem to do the trick; it shuts off the code path, but the compiler still doesn’t like it:
if #available(iOSApplicationExtension 0, *) {
print("This is an extension")
} else {
print("This is an app")
print(UIApplication.shared) // Unreachable in extension, but still doesn’t compile
}
I don’t see any #if check that handles this.
Is there any way in Swift to conditionally compile the code that requires UIApplication.shared?
A possible solution is to avoid explicit usage of UIApplication.shared and use Objective-C selector wrap instead.
Here is an extension that might help (based on https://github.com/ephread/Instructions/issues/21)
extension UIApplication {
static var safeShared: UIApplication? {
guard UIApplication.responds(to: Selector(("sharedApplication"))) else {
return nil
}
guard let unmanagedSharedApplication = UIApplication.perform(Selector(("sharedApplication"))) else {
return nil
}
return unmanagedSharedApplication.takeRetainedValue() as? UIApplication
}
}
Usage
if let app = UIApplication.safeShared {
result = app.applicationState == .active
}
Happy Coding 👨‍💻

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?

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
}

NSLocale.current.description crashing in Xcode 9

I created a brand new single view application and added one line to the viewDidLoad method of the ViewController.swift file:
override func viewDidLoad() {
super.viewDidLoad()
_ = NSLocale.current.description
}
The NSLocale.current.description line crashes in Xcode 9 with no stack trace (just a EXC_BAD_ACCESS code=EXC_I386_GPFLT error message). The same project runs fine in Xcode 8.3.3. Anyone have any ideas why this is happening?
Here is my simulator region settings:
Based on the stack trace, it looks like it's trying to treat description as an ObjC property rather than a Swift property. Based on the source code, this shouldn't be happening. It's likely a bug in the latest Swift compiler that is producing the Swift libraries because it crashes on an iOS 9 device as well.
Be sure to file a bug with Swift since this seems to be a language bug. I've verified that it is still broken in the latest Swift 4 toolchain. In the meantime, you can get the same behavior of description using your own extension by simply duplicating the intended implementation like I've shown here.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let brokenDescription = NSLocale.current.description
// let otherBrokenDescription = Locale.current.description
let objcDescription = (Locale.current as NSLocale).debugDescription //"<__NSCFLocale: 0x1c00dbc10> \'en_US\'}"
let myDescription = Locale.current.myDescription // "en_US (current)"
}
}
extension Locale {
private var _kindDescription : String {
if self == Locale.autoupdatingCurrent {
return "autoupdatingCurrent"
} else if self == Locale.current {
return "current"
} else {
return "fixed"
}
}
public var myDescription: String {
return "\(identifier) (\(_kindDescription))"
}
public var myDebugDescription : String {
return "\(identifier) (\(_kindDescription))"
}
}
This was a bug fixed in Xcode 9 Beta 5.

Resources