Skip to content

Instantly share code, notes, and snippets.

@KoCMoHaBTa
Last active July 13, 2022 00:22
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save KoCMoHaBTa/5d2cecfc17db5f3944bc98bcd6fcde55 to your computer and use it in GitHub Desktop.
Save KoCMoHaBTa/5d2cecfc17db5f3944bc98bcd6fcde55 to your computer and use it in GitHub Desktop.
UITesting Utilities

UITesing Utilities

Contents

How to use

  • add the contents to your UITesting target
  • import XCTest-Private.h into your UITesting target bridging header
  • in your XCTestCase tearDown() method use the Springboard utility to delete the app and/or reset Location and Privacy Settings

Example tearDown()

  override func tearDown() {
    
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    Springboard.shared.deleteApp(withName: "YourAppName")
    Springboard.shared.resetLocationAndPrivacySettings()
    
    super.tearDown()
  }

References

http://stackoverflow.com/questions/33107731/is-there-a-way-to-reset-the-app-between-tests-in-swift-xctest-ui-in-xcode-7

//
// Springboard.swift
// https://gist.github.com/KoCMoHaBTa/5d2cecfc17db5f3944bc98bcd6fcde55
//
// Created by Milen Halachev on 2/15/17.
//
// MIT License
//
// Copyright (c) 2017 Milen Halachev
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
import XCTest
class Springboard {
static let shared = Springboard()
let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!
let settings = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.Preferences")!
func deleteApp(withName name: String) {
XCUIApplication().terminate()
let springboard = self.springboard
springboard.resolve()
let icon = springboard.icons[name]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
//Tap & Hold the app icon in order to go to edit mode
Thread.sleep(forTimeInterval: 0.5)
icon.press(forDuration: 1.3)
//Tap the little "X" button at approximately where it is. The X is not exposed directly
Thread.sleep(forTimeInterval: 0.5)
springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX, dy: (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
//tap the delete alert button
Thread.sleep(forTimeInterval: 0.5)
springboard.alerts.buttons["Delete"].tap()
}
//Press home once make the icons stop wiggling
Thread.sleep(forTimeInterval: 0.5)
XCUIDevice.shared.press(.home)
}
func resetLocationAndPrivacySettings() {
XCUIApplication().terminate()
let springboard = self.springboard
springboard.resolve()
let settingsIcon = springboard.icons["Settings"]
if settingsIcon.exists {
//Press home button twise slowly in order to go to the first page of the springboard
Thread.sleep(forTimeInterval: 0.5)
XCUIDevice.shared.press(.home)
//Press home button twise slowly in order to go to the first page of the springboard
Thread.sleep(forTimeInterval: 0.5)
XCUIDevice.shared.press(.home)
//tap the settings icon
Thread.sleep(forTimeInterval: 0.5)
settingsIcon.tap()
let settings = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.Preferences")!
//go to `General` -> `Reset` and tap `Reset Location & Privacy`
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
//tap the `Reset Warnings` button
Thread.sleep(forTimeInterval: 0.5)
settings.buttons["Reset Warnings"].tap()
settings.terminate()
}
}
}
//
// XCTest-Private.h
// https://gist.github.com/KoCMoHaBTa/5d2cecfc17db5f3944bc98bcd6fcde55
//
// Created by Milen Halachev on 2/15/17.
//
// MIT License
//
// Copyright (c) 2017 Milen Halachev
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef XCTest_Private_h
#define XCTest_Private_h
#import <XCTest/XCUIApplication.h>
@interface XCUIApplication (Private)
- (id)initPrivateWithPath:(id)arg1 bundleID:(id)arg2;
@end
@interface XCUIElement (Private)
- (void)resolve;
@end
#endif /* XCTest_Private_h */
@merkury
Copy link

merkury commented Aug 1, 2017

appDelete only works in portrait orientation. To avoid this change the orientation to portrait by setting: XCUIDevice.shared().orientation = .portrait

@joaofranco
Copy link

joaofranco commented May 23, 2018

Convert to Swift 4.1 (i don't know the best practices to add as comment thread)

https://gist.github.com/joaofranco/6862ad802fc5988074315cb9ec7d6319#file-springboard-swift

@asam139
Copy link

asam139 commented Jul 9, 2018

I have needed to increase the offset to 5 to find X button.

let vector = CGVector(dx: (iconFrame.minX + 5) / springboardFrame.maxX, dy: (iconFrame.minY + 5) / springboardFrame.maxY)

@PWrzesinski
Copy link

Works like a charm. Could you add some license note?

@KoCMoHaBTa
Copy link
Author

Updated for swift 5
@PWrzesinski Added a license to the files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment