preface
Recently I am learning iOS automated testing (XCTest comes with the system), so I will summarize some knowledge of automated testing. If you want to see the code directly, skip to the last column and download the project
How do I create automated tests
There are two creation cases
- New project
To create a project, simply Include UI Tests
- You have projects, but you don’t have automated tests
It’s just adding target
We click on the ‘+’,
Select the UI Testing Bundle
Where are the automated tests created?
After creating the test, the default name is project name + UITests. If you have written an App Extension, you should know that this automated test is an App extension. You can delete and create it at will. Even creating more than one is fine
How to use automated tests
There are two ways to use it
- Use in Show the Test Navigator
If you mouse over a function like testExample(), you’ll see a little arrow on the right that plays, click on that little arrow, So we’re going to run testExample(). If we run capital T, that means we’re going to run all the functions in this test module. Run an automated test
TestLaunchPerformance () and testExample() were the only two functions I created. I’ll write the rest later
- Select run in the.swift file of the UITest module
The testExample() function above is actually the function associated with this file. If you click on the.swift file of the UITest module, you’ll see
Click on the quadrilateral to run the automated test. Click on run next to class to run all the functions in the entire class.
Attention! A function that can be executed by itself must start with test (i.e. with a quadrilateral), such as testA(), which works. MethodA () does not have a quadrilateral.
Life cycle (running process)
Cycle is as follows
- setUp()
- Custom functions to execute
- tearDown()
There is a point to note here, such as the testA() and testB() functions in your current test module. Then you click run the entire test module, and the order in which it executes is.
- setUp()
- testA()
- tearDown()
And then execute
- setUp()
- testB()
- tearDown()
You’ll see the APP start and close twice.
Specific usage introduction
Initialize the App
// Initialize XCUIApplication
let app = XCUIApplication(a)/ / start the app
app.launch()
// By default, the bundleIdentifier is left blank to initialize the current project APP
// If you want to work with other apps, you can pass in the bundleIdentifier initializer to get an example
let sefariApp = XCUIApplication.init(bundleIdentifier: "com.apple.mobilesafari")
Copy the code
Let app = XCUIApplication(
Access to elements
Here I give examples of several commonly used on the line, others, you can study by yourself
Take elements by type
Can view the system XCUIElementTypeQueryProvider directly
For example, you can get all the buttons under the app
app.buttons
Copy the code
But we can’t use XCUIElementQuery directly until we get something specific
And just to make it clear, let me do an example here, where the UI looks like this
Here is the output from print(app.debugDescription). Again, the debugDescription property is very comfortable for fetching the element hierarchy. Remember to use debugDescription
→Application, 0x2819E7720, PID: 4843, label: 'XQUITestDemo' Window (Main), 0x2819e78e0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e79c0, {{0.0, 0.0}, {375.0, 667.0} 667.0}} Other, 0 x2819e7aa0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0 x2819e7b80, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e7c60, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e7d40, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e7e20, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e7e20, {{0.0, 0.0}, {375.0, 667.0}} 0.0}, {375.0, 667.0}} NavigationBar 0x2819e7F00, {{0.0, 20.0}, {375.0, 44.0}}, identifier: 'My' StaticText, 0x2819E8000, {{170.0, 32.0}, {35.0, 20.5}}, label: 'my' Other, 0 x2819e80e0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0 x2819e81c0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819E82A0, {{0.0, 64.0}, {375.0, 603.0}} Button, 0x280C8Eae0, {{30.0, 214.0}, {60.0, 60.0}}, identifier: 'touchMe', label: 'StaticText ', 0x2819e8460, {{41.5, 233.0}, {37.0, 22.0}}, label: TabBar, 0x2819e8540, {{0.0, 618.0}, {375.0, 49.0}} Button, 0x2819e8620, {{2.0, 619.0}, {184.0, 48.0}}, label: Button, 0x2819e1b20, {{190.0, 619.0}, {183.0, 48.0}}, label: 0x2819e0b60, {{0.0, 0.0}, {375.0, 667.0}}, Disabled Other, 0x2819e0b60, {{0.0, 0.0}, {375.0, 0.0}, {375.0, 667.0}}, identifier: 'SVProgressHUD' Window, 0x2819e1ce0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2819e1f80, {{0.0, 0.0}, {375.0, 667.0}} Other, 0 x2819e2060, {{0.0, 0.0}, {375.0, 667.0}}Copy the code
Notice that Other corresponds to UIView
So what we’re going to do is we’re going to click on the front page of Tabbar to go to the front page so how do we do that? It’s easy. The code is two sentences
/ / element
let homePageBtn = app.tabBars.buttons["Home page"]
// Click the element
homePageBtn.tap()
Copy the code
So let me just parse the first one and take the element
App. tabBars takes the following data
TabBar, 0x2835c3800, {{0.0, 618.0}, {375.0, 49.0}} Button, 0x2835c38e0, {{2.0, 619.0}, {184.0, 48.0}}, label: Button, 0x2835c39c0, {{190.0, 619.0}, {183.0, 48.0}}, label: 'my ', SelectedCopy the code
And then app.tabbars. Buttons gets to
Button, 0x2835C38e0, {{2.0, 619.0}, {184.0, 48.0}}, label: Button, 0x2835c39c0, {{190.0, 619.0}, {183.0, 48.0}}, label: 'my ', SelectedCopy the code
So at this point, we’re at the end of fetching elements by type and there’s a couple of ways to get the home button, so keep going
Fetch the element according to label
Get the button based on the label
app.tabBars.buttons["Home page"]
Copy the code
Of course, with the current UI, the simplest thing is app.buttons[” home “], but don’t be stingy with code to get the elements right
Take the element by subscript
Because of the printed data, the elements are in order. So we can also get elements by subscript
// Pass in a specific subscript and fetch the button
app.tabBars.buttons.element(boundBy: 0)
// Select the first one
app.windows.tabBars.firstMatch
Copy the code
Fetch elements according to identifier
However, you need to set up the identifier in the project code, or in the XIB
// This is not the first tabbar button, but the yellow button
app.tabBars.element(matching: .button, identifier: "touchMe")
Copy the code
- Set in code
The accessibilityIdentifier is the identifier for setting up automated tests
let btn = UIButton()
btn.frame = CGRect.init(x: 30, y: 150, width: 60, height: 60)
btn.setTitle("点我", for: .normal)
btn.backgroundColor = UIColor.orange
btn.accessibilityIdentifier = "touchMe"
Copy the code
- Xib or storyboard
Operations on elements
The following button represents a button element (XCUIElement class)
Click on the
button.tap()
Copy the code
Double click on the
button.doubleTap()
Copy the code
Long press
// Hold for three seconds
button.press(forDuration: 3)
Copy the code
sliding
/ /
button.swipeUp()
/ / the sweep
button.swipeDown()
/ / left Saul
button.swipeLeft()
/ / right
button.swipeRight()
Copy the code
kneading
button.pinch(withScale: 1.5, velocity: 1)
Copy the code
rotating
button.rotate(0.5, withVelocity: 1)
Copy the code
The actual cases
Here are some examples of actual use cases that I haven’t found for a long time in my study. I put them in the project. If you are interested, you can directly download the project and run it
Click on a row in UITableView
Here is an example UI
The following data is printed for app.debugDescription
→Application, 0x2838DA060, PID: 1007, label: 'XQUITestDemo' Window (Main), 0x2838db560, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838DB640, {{0.0, 0.0}, {375.0, 667.0}} Other, 0 x2838db720, {{0.0, 0.0}, {375.0, 667.0}} Other, 0 x2838db800, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838db8e0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838db9c0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838dBaa0, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838dBaa0, {{0.0, 0.0}, {375.0, 667.0}} NavigationBar 0x2838dbb80, {{0.0, 20.0}, {375.0, 44.0}}, identifier: Button, 0x2838DBc60, {{0.0, 20.0}, {62.0, 44.0}}, label: StaticText, 0x2838DBd40, {{147.0, 32.0}, {81.0, 20.5}}, label: 'TableView' Other, 0x2838dbe20, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838DBF00, {{0.0, 0.0}, {375.0, 667.0}} Other, {{0.0, 0.0}, {375.0, 667.0}} Other, 0x2838DE760, {{0.0, 64.0}, {375.0, 603.0}} Table, 0x2838Debc0, {{0.0, 64.0}, {375.0, 611.0}} Cell, 0x2838DEA00, {{0.0, 64.0}, {375.0, 43.5}} Image, 0 x2838d0000, {{15.0, 73.5}, {24.0, 24.0}} StaticText, 0 x2838d00e0, {{54.0, 64.0}, {306.0, 43.5}}, label: ' 0' Other, 0x2838D01C0, {{54.0, 107.0}, {321.0, 0.5}} Button, 0x2838D02A0, {{281.0, 69.0}, {74.0, 34.0}}, Button, 0x2838D02A0, {{281.0, 69.0}, {74.0, 34.0}}, label: StaticText, 0x2838D0380, {{281.0, 75.0}, {74.0, 22.0}}, label: Cell, 0x2838d0460, {{0.0, 107.5}, {375.0, 43.5}} Image, 0x2838D0540, {{15.0, 117.0}, {24.0, 24.0}} StaticText, 0 x2838d0620, {{54.0, 107.5}, {306.0, 43.5}}, label: 'test: 1' Other, 0x2838D0700, {{54.0, 150.5}, {321.0, 0.5}} Button, 0x2838D07E0, {{281.0, 112.5}, {74.0, 34.0}}, label: 'I am a button' StaticText, 0x2838D08c0, {{281.0, 118.5}, {74.0, 22.0}}, label: 'I am a button'... TabBar, 0x2838F0fc0, {{0.0, 618.0}, {375.0, 49.0}} Button, 0x2838F10A0, {{2.0, 619.0}, {184.0, {{190.0, 619.0}, {183.0, 48.0}}, label: 'my ', Selected Button, 0x2838f1180, {{190.0, 619.0}, {183.0, 48.0}}, label:' my 'Copy the code
The logic is pretty simple, just find the cell and click on it and the key field is ==isHittable==, look at the code below, although it’s a little long. But please be patient
class XQUITestDemoUITests: XCTestCase {
/ / / test tableView
func testTableView(a) {
// Initialize and open APP
let app = XCUIApplication()
app.launch()
// Read the tableView element
let tables = app.tables.firstMatch
// Get the cell with subscript 30
let cell = tables.cells.element(boundBy: 30)
// Call the encapsulated method and scroll to the cell
if tables.xq_scrollToElement(element: cell) {
// If the cell has been found, click the cell
cell.tap()
}
}
}
// Encapsulate the TableView
extension XCUIElement {
/// Scroll to an element
/// Scroll down by default
// you can scroll up, up, down, etc.
/// -parameter element: UI element
/// -parameter isAutoStop: true
///
/// Returns true to indicate that the element passed in was found
///
func xq_scrollToElement(element: XCUIElement.isAutoStop: Bool = true) -> Bool {
// Check if it is tableView
if self.elementType ! = .table {
return false
}
// Scroll until an element is clickable
while !element.isHittable {
// Roll to the end and stop
if isAutoStop {
// Get the last element
let lastElement = self.cells.element(boundBy: self.cells.count - 1)
// Roll to the end, then stop
if lastElement.isHittable {
return false}}self.swipeUp()
}
return true}}Copy the code
Open other apps directly
Here’s an example of opening Safari.
// Pass in the bundle ID to initialize an app
let safariApp = XCUIApplication.init(bundleIdentifier: "com.apple.mobilesafari")
safariApp.launch()
Copy the code
And of course, once we open Safari, we can also get the element on Safari and manipulate it
System Desktop App(SpringBoard)
If we want to get the information on the current status bar. For example, the power, whether the battery is properly charged, the signal strength and so on can actually be obtained by initializing the desktop APP
// Launch (
let springboard = XCUIApplication.init(bundleIdentifier: "com.apple.springboard")
// Get desktop element information for the first time, sometimes very slow... So it's not stuck. Please be patient
// I don't know why, but I feel a little metaphysical
// I usually wait 3 ~ 20 seconds anyway
print(safariApp.debugDescription)
Copy the code
Of course, we could call the Home button, go back to the desktop, and based on the information we’ve got, click on the desktop APP, and that would work, too. This Springboard can do a lot of things, see my project, and there are some actual use cases in it
To apply for system permissions, click the System Permissions dialog box (for example, notification permissions).
The following code
class XQUITestDemoUITests: XCTestCase {
/// Test system button automatically click, notification permission
func testSystemAlertNotification(a) {
// Initialize and open APP
let app = XCUIApplication()
app.launch()
// Click the cell inside the app to apply for notification permission
let view = app.windows.cells.element(boundBy: 9)
view.tap()
// Call the wrapped method and click the system Alert button at subscript 1
// subscript 1 is the right agree button
self.xq_tapSystemAlert(index: 1)
// Wait a moment
let _ = app.wait(for: .notRunning, timeout: 3)}}extension XCTestCase {
/// Click the system dialog box
/// -parameter index: indicates the index of a button.
/// the index starts from the left, with 0 as the starting index. For example, if you want to say yes, you pass in 1
func xq_tapSystemAlert(index: Int) {
let springboard = XCUIApplication.init(bundleIdentifier: "com.apple.springboard")
springboard.xq_tapAlert(index: index)
}
}
extension XCUIApplication {
///
/// Note that there is no way to call this click in the actionSheet's popbox.
// Because the actionSheet is made up of two scrollViews... And the system doesn't think it's an alert...
///
/// Click the popbox
/// -parameter index: indicates the index of a button.
/// the index starts from the left, with 0 as the starting index.
func xq_tapAlert(index: Int) {
let alerts = self.windows.alerts
if alerts.count > 0 {
let _ = self.wait(for: .notRunning, timeout: 1)
alerts.buttons.element(boundBy: index).tap()
let _ = self.wait(for: .notRunning, timeout: 1)}}}Copy the code
Home button
Currently, there is no way to double click the Home button. If you know, please leave a message and tell me
// Click the Home button
XCUIDevice.shared.press(.home)
Copy the code
System volume button
// Adjust the volume, +
XCUIDevice.shared.press(.volumeUp)
// Adjust the volume, -
XCUIDevice.shared.press(.volumeDown)
Copy the code
Siri
Suddenly wake up Siri, will speak very loudly, in the company to play, it is suggested that the first small tone 😁
// Wake up Siri and type the statement
XCUIDevice.shared.siriService.activate(voiceRecognitionText: "Am I handsome?");
Copy the code
Equipment turn
Note that the phone must be allowed to turn first
// Adjust the direction
XCUIDevice.shared.orientation = .landscapeLeft
Copy the code
The project address
Unconsciously, writing so much… A little wordy 😑 here to give the project address, want to see the code, go to download it