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?
Related
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
I made an app in which I access the calendar, everything works great - until I ubdated the iOS from 13.6 to 14.1 on the iPhone. I still have iOS 13.6 on the iPad and there it works as planned.
On the iPhone with 14.1 the app starts with the launch screen, but stays there a long time - there is no error message and no output in the debugger - after what feels like an eternity, the app starts and runs as usual.
What did I miss during the update?
Anyone know anything about what explains this pause?
Yes, I deleted and reinstalled the app on the iPhone.
I guess it's because of the authorization query that I changed as described at Apple. Still nothing.
Nothing happens in xCode - no message and no debug output. Nothing in the display of: CPU, memory, energy impact, disk or network.
I'll write my old authorization query here again - because I suspect the error there - so what should the correct authorization query be called? Can someone change my code correctly for iOS 14.1? I have not yet found any examples on the Internet - only available before iOS 14 (uTube etc.)
I am grateful for anyone who can make it clear to me what happens to changes in permissions. Sorry if I missed something fundamental. Many Thanks!
(I orientated myself to "https://medium.com/better-programming/handling-location-permissions-in-ios-14-2cdd411d3cca".)
func checkCalendarAuthorizationStatusAndLoadCalendars() {
logger(s: #function, f: #file)
if locationManager == nil {
setUpLocationManager()
}
/// here I need Help!
let status = EKEventStore.authorizationStatus(for: EKEntityType.event) /// <---- old!
switch (status) {
case EKAuthorizationStatus.notDetermined:
/// This happens on first-run
requestAccessToCalendar()
case EKAuthorizationStatus.authorized:
/// Things are in line with being able to show the calendars in the table view
/// Start and end date of the interval of 'weekOfYear's
let temp = 60 * 60 * 24 * Int(365 / 2)
let loadDuration = TimeInterval(temp)
/// Creates a date value initialized relative to the current date and time by a given number of seconds.
....
loadCalendars(loadCalendarFrom: &loadCalendarFrom, loadCalendarTo: &loadCalendarTo, eventStore: eventStore)
refreshTableView()
case EKAuthorizationStatus.restricted, EKAuthorizationStatus.denied:
/// We need to help them give us permission
needPermissionView.fadeIn()
#unknown default:
fatalError()
}
}
the call is here:
override
func viewWillAppear(_ animated: Bool) {
logger(s: #function, f: #file)
//-----------------------
formatter!.dateStyle = .medium
formatter!.timeStyle = .short
let dateString = formatter!.string(from: Date()) // "2020-06-03T01:43:44.888Z"
let date:Date? = formatter!.date(from: dateString)!
var localTimeZoneAbbreviation = String(TimeZone.current.abbreviation()!)
let refereceTimeZoneAbbreviation = "CET"
let cetTimeZone = TimeZone(abbreviation: refereceTimeZoneAbbreviation)!
if localTimeZoneAbbreviation == "MESZ" {
localTimeZoneAbbreviation = "UTC+2"
}
if localTimeZoneAbbreviation == "MEZ" {
localTimeZoneAbbreviation = "UTC+1"
}
checkCalendarAuthorizationStatusAndLoadCalendars()
}
I've followed this steps and the device has no more the slow deploy.
Maybe could help you.
Unpair your phone from xcode. The Xcode will crash but continue with the steps.
Launch xcode with your phone not plugged in
plug your phone in
Under devices in xcode, unpair your phone.
stop xcode
run these on terminal
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport
rm -rf ~/Library/Developer/Xcode/iOS\ Device\ Logs
rm -rf ~/Library/Developer/Xcode/DerivedData
start xcode
plug phone in
WKInterfaceMap sometimes renders blank in a very simple app I am developing. The map appears completely empty (no grid of lines or anything similar). It looks as though the thread responsible for drawing the tiles gets blocked.
In order to reproduce the issue just add the code below to the extension delegate.
func applicationDidEnterBackground() {
let watchExtension = WKExtension.shared()
// Schedule the background refresh task.
watchExtension.scheduleBackgroundRefresh(withPreferredDate: Date().addingTimeInterval(15.0*60.0), userInfo: nil) { (error) in
// Check for errors.
if let error = error {
print("ExtensionDelegate: \(error.localizedDescription)")
return
}
print("ExtensionDelegate: Background Task scheduled successfuly")
}
}
Add a WKInterfaceMap to the main view and run once on the simulator. Close the app using the crown and stop it from XCode. Wait for at least 15 minutes and open the app again directly from the simulator.
The map renders then as in the image below.
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
}
In my iOS i'm implementing UITests using XCUITest. It worked great since I had Swift 2.3, but after updating the app to Swift 3 basic actions like tap() don't work anymore.
Just a simple code that doesn't work anymore:
XCUIApplication().buttons["orgMenu"].tap()
throws
Assertion Failure: <unknown>:0: UI Testing Failure - Failure getting snapshot Error Domain=XCTestManagerErrorDomain Code=9 "Error -25204 getting snapshot for element <AXUIElement 0x7f8297d15a50> {pid=32375}" UserInfo={NSLocalizedDescription=Error -25204 getting snapshot for element <AXUIElement 0x7f8297d15a50> {pid=32375}}
The name of the button is correct: if I record the test and tap the button the line above is exactly what I get.
The button is in the view since I'm waiting for the existence of it (tried both manually, through a breakpoint, and programmatically with this:
let exists = NSPredicate(format: "exists == 1")
expectation(for: exists, evaluatedWith: XCUIApplication().buttons["orgMenu"], handler: nil
waitForExpectations(timeout: time, handler: nil)
)
And anyway, it worked before Swift 3.
Any idea? Thanks in advance!
I'll just answer to my own question, since I found a workaround.
Since Swift3, for some reason, UI tests are not able to manage views with a loadMore pattern. If I have in my view a TableView and somewhere in the code a manage the loadMore pattern manually, and in a test I tap a button, the loadMore is called in a sort of infinite loop, and that overheads the app's resources, making the test fail.
Workaround: just deactivate any loadMore if a UI Test is running.
In the tests' setup:
override func setUp() {
super.setUp()
let app = XCUIApplication()
continueAfterFailure = false
app.launchEnvironment = ["isUITest":"YES"]
app.launch()
}
In the view with the loadData:
-(void)loadMore
{
if ([Utils isRunningUITest]) {
return;
}
// My actual function.
}
and my Utils
+(BOOL)isRunningUITest {
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
return environment[#"isUITest"];
}
Sorry for Swift and Objective-C mix, hope this is helpful to someone.
You can try and do something like:
let app = XCUIApplication()
XCTAssert(app.buttons["orgMenu"].waitForExistence(timeout: 10))
app.buttons["orgMenu"].tap()