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

  1. New project

To create a project, simply Include UI Tests

  1. 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

  1. 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

  1. 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

  1. setUp()
  2. Custom functions to execute
  3. 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.

  1. setUp()
  2. testA()
  3. tearDown()

And then execute

  1. setUp()
  2. testB()
  3. 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