如何使用 XCTest delete/reset 来自 iOS 13 的应用程序?
How to delete/reset an app from iOS 13 with XCTest?
最近我开始使用 XCTest 测试一个 iOS 应用程序,但我发现了一些困难,主要困难是在每个测试中删除或重置应用程序内容 class。
我目前正在使用 XCode 11 并尝试 delete/reset 来自 iOS 13 的应用程序用于每个测试 class,我已经尝试过:
- 通过跳板删除应用程序
- 转到应用设置删除应用
这一步在我的测试中非常重要,因为在每次测试中我都需要创建配置文件并登录,所以在下一次测试中我需要从头开始安装应用程序
尝试按应用程序图标的时间比以前的 iOS 版本长一点。
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()
}
}
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()
}
}
}
更新了 Roman 在 iPad.
上支持 iOS 13 的回答
通过插入 otherElements["Home screen icons"]
,代码适用于 iPhone 和 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)
}
}
}
其他
iPad,“最近启动的应用程序”将添加到 Dock 的右侧。如果您仅按标识符搜索图标,此 iPad 特定行为会导致 XCUITest 找到两个元素。
Your app will be added here, too.
要处理该问题,请指定 otherElements["Home screen icons"]
并排除“Dock”的元素。
这是 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()
}
iOS14
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()
}
}
}
}
iOS 14岁及以下
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")
}
}}
您可以简单地通过跳板执行此操作,就像您自己卸载构建一样。
这里是 class 带有 deleteApp 功能,它会在你需要的时候随时卸载构建。
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()
}
}
}
乌斯盖:
func test_YourTestName() {
Springboard.deleteApp()
}
最近我开始使用 XCTest 测试一个 iOS 应用程序,但我发现了一些困难,主要困难是在每个测试中删除或重置应用程序内容 class。
我目前正在使用 XCode 11 并尝试 delete/reset 来自 iOS 13 的应用程序用于每个测试 class,我已经尝试过:
- 通过跳板删除应用程序
- 转到应用设置删除应用
这一步在我的测试中非常重要,因为在每次测试中我都需要创建配置文件并登录,所以在下一次测试中我需要从头开始安装应用程序
尝试按应用程序图标的时间比以前的 iOS 版本长一点。
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()
}
}
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()
}
}
}
更新了 Roman 在 iPad.
上支持 iOS 13 的回答通过插入 otherElements["Home screen icons"]
,代码适用于 iPhone 和 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)
}
}
}
其他
iPad,“最近启动的应用程序”将添加到 Dock 的右侧。如果您仅按标识符搜索图标,此 iPad 特定行为会导致 XCUITest 找到两个元素。
Your app will be added here, too.
要处理该问题,请指定 otherElements["Home screen icons"]
并排除“Dock”的元素。
这是 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()
}
iOS14
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()
}
}
}
}
iOS 14岁及以下
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")
}
}}
您可以简单地通过跳板执行此操作,就像您自己卸载构建一样。 这里是 class 带有 deleteApp 功能,它会在你需要的时候随时卸载构建。
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()
}
}
}
乌斯盖:
func test_YourTestName() {
Springboard.deleteApp()
}