How to delete/reset an app from iOS 13 with XCTest? - xcode

Recently I started testing an iOS app using XCTest but I found some difficulties, the main difficulty was deleting or resetting the app content in each test class.
I'm currently using XCode 11 and trying to delete/reset an app from iOS 13 for each test class, I've already tried:
Delete app through springboard
Delete app by going to the app settings
This step is really important in my tests because in each test I need to create a profile and log in, so in the next test I need to have the app just installed from scratch

iOS 14
The working solution for iOS 14
import XCTest
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
func deleteMyApp() {
XCUIApplication().terminate()
let bundleDisplayName = "MyApp"
let icon = springboard.icons[bundleDisplayName]
if icon.exists {
icon.press(forDuration: 1)
let buttonRemoveApp = springboard.buttons["Remove App"]
if buttonRemoveApp.waitForExistence(timeout: 5) {
buttonRemoveApp.tap()
} else {
XCTFail("Button \"Remove App\" not found")
}
let buttonDeleteApp = springboard.alerts.buttons["Delete App"]
if buttonDeleteApp.waitForExistence(timeout: 5) {
buttonDeleteApp.tap()
}
else {
XCTFail("Button \"Delete App\" not found")
}
let buttonDelete = springboard.alerts.buttons["Delete"]
if buttonDelete.waitForExistence(timeout: 5) {
buttonDelete.tap()
}
else {
XCTFail("Button \"Delete\" not found")
}
}
}
class HomeUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
deleteMyApp()
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
}

Try to press the app icon a little longer than in previous iOS versions.
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
func deleteMyApp() {
XCUIApplication().terminate()
let icon = springboard.icons["YourAppName"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 5)
// 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()
}
}

Updated Roman's answer to support iOS 13 on iPad.
By inserting otherElements["Home screen icons"], the code works both of iPhone and iPad.
class Springboard {
static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
class func deleteMyApp() {
XCUIApplication().terminate()
// Insert otherElements["Home screen icons"] here
let icon = springboard.otherElements["Home screen icons"].icons["Workoutimer"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 5)
// 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()
XCUIDevice.shared.press(XCUIDevice.Button.home)
}
}
}
Misc.
On iPad, "recent launched app" will be added to the right side of the Dock. This iPad specific behavior leads XCUITest finds two elements if you search icons only by its identifier.
Your app will be added here, too.
To handle that, specify otherElements["Home screen icons"] and it excludes elements of the "Dock".

enum Springboard {
static let springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")
static let appName = "appName"
static func deleteApp() {
XCUIApplication().terminate()
let icon = springboardApp.icons[appName]
if icon.exists {
icon.press(forDuration: 3)
icon.buttons["DeleteButton"].tap()
springboardApp.alerts.buttons["Delete"].tap()
}
}
}

This is a quick and dirty solution for iOS 14:
let appName = "You app name as it appears on the home screen"
// Put the app in the background
XCUIDevice.shared.press(XCUIDevice.Button.home)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
if springboard.icons[appName].waitForExistence(timeout: 5) {
springboard.icons[appName].press(forDuration: 1.5);
}
if springboard.collectionViews.buttons["Remove App"].waitForExistence(timeout: 5) {
springboard.collectionViews.buttons["Remove App"].tap()
}
if springboard.alerts["Remove “\(appName)”?"].scrollViews.otherElements.buttons["Delete App"].waitForExistence(timeout: 5) {
springboard.alerts["Remove “\(appName)”?"].scrollViews.otherElements.buttons["Delete App"].tap()
}
if springboard.alerts["Delete “\(appName)”?"].scrollViews.otherElements.buttons["Delete"].waitForExistence(timeout: 5) {
springboard.alerts["Delete “\(appName)”?"].scrollViews.otherElements.buttons["Delete"].tap()
}

you can simply do this via springboard same as you uninstall build by yourself.
Here is the class with deleteApp function which will uninstall build at anytime you need.
class Springboard {
static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
class func deleteApp () {
//terminate app and activate Springboard
XCUIApplication().terminate()
springboard.activate()
//tap on app icon
let appIcon = springboard.icons.matching(identifier: "App Display Name").firstMatch
if appIcon.exists {
appIcon.press(forDuration: 2.0)
//Access first alert button (Remove App)
let _ = springboard.alerts.buttons["Remove App"].waitForExistence(timeout: 1.0)
springboard.buttons["Remove App"].tap()
//Access second alert button (Delete App)
let _ = springboard.alerts.buttons["Delete App"].waitForExistence(timeout: 1.0)
springboard.buttons["Delete App"].tap()
//Access second alert button (Delete)
let _ = springboard.alerts.buttons["Delete"].waitForExistence(timeout: 1.0)
springboard.buttons["Delete"].tap()
}
}
}
Usgae:
func test_YourTestName() {
Springboard.deleteApp()
}

iOS 14 and below
func deleteMyApp() {
XCUIApplication().terminate()
let bundleDisplayName = "App Name"
XCUIDevice.shared.press(.home)
let icon = springboard.icons[bundleDisplayName]
if icon.waitForExistence(timeout: 5) {
XCUIDevice.shared.press(.home)
let value = springboard.pageIndicators.element(boundBy: 0).value as? String
let f = value!.last?.wholeNumberValue
for _ in (1...f!){
if(icon.isHittable){
break
}
else{
springboard.swipeLeft()
}
}
let systemVersion = UIDevice.current.systemVersion.prefix(2)
switch systemVersion {
case "13":
icon.press(forDuration: 1)
let rrd = springboard.buttons["Delete App"]
XCTAssert(rrd.waitForExistence(timeout: 5))
rrd.tap()
let rrd2 = springboard.buttons["Delete"]
XCTAssert(rrd2.waitForExistence(timeout: 5))
rrd2.tap()
case "14":
icon.press(forDuration: 1)
let rrd = springboard.buttons["Remove App"]
XCTAssert(rrd.waitForExistence(timeout: 5))
rrd.tap()
let rrd2 = springboard.buttons["Delete App"]
XCTAssert(rrd2.waitForExistence(timeout: 5))
rrd2.tap()
let rrd3 = springboard.buttons["Delete"]
XCTAssert(rrd3.waitForExistence(timeout: 5))
rrd3.tap()
default:
XCTFail("Did not handle")
}
}}

Related

In IOS 11, DeviceMotion in background stopped working

My app reports and records location, altitude, rotation and accelerometer data (DeviceMotion) while in the background. This works fine on ios 10.3.3. On IOS 11, I no longer have access motion data while the device is locked. Altitude data and location data is still streaming to the console, though.
Has something changed in IOS 11 that prevents me from accessing motion data or am I doing trying to access it in a way that Apple now blocks like OperationQueue.main
Here is how I'm starting motion updates. If the phone is unlocked, all works fine. If I locking the phone, no more updates.:
let motionManager = self.motionManager
if motionManager.isDeviceMotionAvailable {
motionUpdateInterval = 0.15
motionManager.deviceMotionUpdateInterval = motionUpdateInterval
motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: OperationQueue.main) {deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
I can't find anything about Motion background modes changing but it seems there must be a way otherwise RunKeeper, Strava will break. Can someone help me get this working again before IOS11 launch?
Thanks!
Also came across this problem.
Our solution was to ensure we have another background mode enabled and running (in our case location updates + audio) and restart core motion updates when switching background/foreground.
Code sample:
import UIKit
import CoreMotion
final class MotionDetector {
private let motionManager = CMMotionManager()
private let opQueue: OperationQueue = {
let o = OperationQueue()
o.name = "core-motion-updates"
return o
}()
private var shouldRestartMotionUpdates = false
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(appDidEnterBackground),
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(appDidBecomeActive),
name: .UIApplicationDidBecomeActive,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidBecomeActive,
object: nil)
}
func start() {
self.shouldRestartMotionUpdates = true
self.restartMotionUpdates()
}
func stop() {
self.shouldRestartMotionUpdates = false
self.motionManager.stopDeviceMotionUpdates()
}
#objc private func appDidEnterBackground() {
self.restartMotionUpdates()
}
#objc private func appDidBecomeActive() {
self.restartMotionUpdates()
}
private func restartMotionUpdates() {
guard self.shouldRestartMotionUpdates else { return }
self.motionManager.stopDeviceMotionUpdates()
self.motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: self.opQueue) { deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
print(deviceMotion)
}
}
}
The official 11.1 release fixed the issue and I've heard from iPhone 8 users that the original implementation is working for them.
The 11.2 beta has not broken anything.

Xcode Apple Watch project, local notification not being presented?

I am trying to present a local notification on an Apple Watch simulator with a button. This is the code:
#IBAction func buttonOne() {
print("button one pressed")
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "Notified!", arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "This is a notification appearing!", arguments: nil)
// Deliver the notification in five seconds.
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5,
repeats: false)
// Schedule the notification.
let request = UNNotificationRequest(identifier: "Notify", content: content, trigger: trigger)
center.add(request) { (error : Error?) in
if let theError = error {
print(theError.localizedDescription)
} else {
print("successful notification")
}
}
}
The console is successfully printing "successful notification," but the notification never appears on the watch simulator. I have no idea why this is
1) Before scheduling a notification, request permissions. Use requestAuthorization method, for example:
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization
}
2) Notifications will not appear on watch face if the application is running in foreground. Considering this you may use cmd + H to hide the app.

How to properly add global NSEvent listener for pressure events in Swift OS X application?

I'm pretty new in OS X programming and I'm trying to write an application that will capture Force Click event at system-wide context.
Based on various sources I wrote down code listed below:
var lastClickStage = -1
func checkAssistanceSettings () -> Bool {
let checkOptPrompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
let options = [checkOptPrompt: true]
let accessEnabled = AXIsProcessTrustedWithOptions(options)
return accessEnabled == 1
}
func processForceClick(incomingEvent: NSEvent!) -> Void {
let clickStage = incomingEvent.stage
if clickStage == lastClickStage {
return
}
if (lastClickStage == 2 && clickStage != 2) {
lastClickStage = clickStage
return
}
if (clickStage == 2 && lastClickStage != 2) {
let applicationClicked = NSWorkspace.sharedWorkspace().frontmostApplication?.bundleIdentifier
if (applicationClicked != nil) {
NSLog("ForceClicked in \(applicationClicked!)!")
}
lastClickStage = clickStage
}
}
func processForceClickLocally(incomingEvent: NSEvent!) -> NSEvent {
processForceClick(incomingEvent)
return incomingEvent
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
NSLog("\(checkAssistanceSettings())")
NSEvent.addLocalMonitorForEventsMatchingMask(NSEventMaskFromType(NSEventType.EventTypePressure), handler: processForceClickLocally)
NSEvent.addGlobalMonitorForEventsMatchingMask(NSEventMaskFromType(NSEventType.EventTypePressure), handler: processForceClick)
}
When I run my application local event listener seems to work like a charm, but global event listener never calls his handler, even if XCode or a specific built application gets grant to accessibility in System Settings (AXIsProcessTrustedWithOptions(options) evaluates as "true").
Can anyone point out what's wrong with it?
EDIT: Even strange thing discovered by me: seems like global listener not working with this NSEventMask. Got no problem with NSEventMask.LeftMouseDownMask for example.
So now seems like question is transformed to "What wrong with NSEventMask.EventMaskPressure in global listeners?".

How to use NSWindowOcclusionState.Visible in Swift

I am trying to implement window toggling (something I've done many times in Objective-C), but now in Swift. It seams that I am getting the use of NSWindowOcclusionState.Visible incorrectly, but I really cannot see my problem. Only the line w.makeKeyAndOrderFront(self) is called after the initial window creation.
Any suggestions?
var fileArchiveListWindow: NSWindow? = nil
#IBAction func tougleFileArchiveList(sender: NSMenuItem) {
if let w = fileArchiveListWindow {
if w.occlusionState == NSWindowOcclusionState.Visible {
w.orderOut(self)
}
else {
w.makeKeyAndOrderFront(self)
}
}
else {
let sb = NSStoryboard(name: "FileArchiveOverview",bundle: nil)
let controller: FileArchiveOverviewWindowController = sb?.instantiateControllerWithIdentifier("FileArchiveOverviewController") as FileArchiveOverviewWindowController
fileArchiveListWindow = controller.window
fileArchiveListWindow?.makeKeyAndOrderFront(self)
}
}
Old question, but I just run into the same problem. Checking the occlusionState is done a bit differently in Swift using the AND binary operator:
if (window.occlusionState & NSWindowOcclusionState.Visible != nil) {
// visible
}
else {
// not visible
}
In recent SDKs, the NSWindowOcclusionState bitmask is imported into Swift as an OptionSet. You can use window.occlusionState.contains(.visible) to check if a window is visible or not (fully occluded).
Example:
observerToken = NotificationCenter.default.addObserver(forName: NSWindow.didChangeOcclusionStateNotification, object: window, queue: nil) { note in
let window = note.object as! NSWindow
if window.occlusionState.contains(.visible) {
// window at least partially visible, resume power-hungry calculations
} else {
// window completely occluded, throttle down timers, CPU, etc.
}
}

Class Not Being Called Swift iOS

I'm working on my first app for OSX 10.8 using Swift. I want to be able to have the state of the battery dictate the text in Today pulldown menu. That aspect of the code works, but I am very frustrated as my class never gets called. It is in a separate supporting script called 'DeviceMonitor.swift'. Thanks for the help!
Code:
import Foundation
import UIKit
class BatteryState {
var device: UIDevice
init() {
self.device = UIDevice.currentDevice()
println("Device Initialized")
}
func isPluggedIn(value: Bool) {
let sharedDefaults = NSUserDefaults(suiteName: "group.WidgetExtension")
let batteryState = self.device.batteryState
if (batteryState == UIDeviceBatteryState.Charging || batteryState == UIDeviceBatteryState.Full){
let isPluggedIn = true
println("Plugged In")
sharedDefaults?.setObject("Plugged In", forKey: "stringKey")
}
else {
let isPluggedIn = false
println("Not Plugged In")
sharedDefaults?.setObject("Not Plugged In", forKey: "stringKey")
}
sharedDefaults?.synchronize()
}
}
Unless you have done it somewhere else in your code, it looks like you haven't registered to receive UIDeviceBatteryLevelDidChangeNotification events. You should also ensure that the current UIDevice has batteryMonitoringEnabled as YES.

Resources