How to speed up UI test cases in Xcode?
Asked Answered
G

9

27

Since Xcode 7 we have a nice API for UI testing. Mostly I'm satisfied with it. The only concern is related to the speed.

In the beginning an ordinary UI test case (about 15 actions) ran approximately 25 seconds. Then I mocked networking completely. Now it takes 20 seconds. Considering the fact that the time is taken only by animations and a launch time (1 second or even less), I assume, there must be a way to speed it up.

Goblet answered 17/5, 2016 at 17:4 Comment(0)
V
37

Try setting this property when your UI tests run:

UIApplication.shared.keyWindow?.layer.speed = 100

Here's how I set it:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    if ProcessInfo.processInfo.arguments.contains("UITests") {
        UIApplication.shared.keyWindow?.layer.speed = 100
    }
}

And in my UI tests:

class MyAppUITests: XCTestCase {

    // MARK: - SetUp / TearDown

    override func setUp() {
        super.setUp()

        let app = XCUIApplication()
        app.launchArguments = ["UITests"]
        app.launch()
    }
}

There's a few more handy tips in this blog post.

Valgus answered 18/5, 2016 at 3:28 Comment(8)
Thanks for the answer. It works! In order to improve it a bit: is it possible to increase the animation speed from the UI testing process?Goblet
No unfortunately. The ui test process is meant to be entirely separate from your app (and only interact through accessibility and the launch arguments).Valgus
@ArtemStepanenko you can increase the animation speed from the UI testing process by using SBTUITestTunnel. We developed this library to enable intercommunication between the app and test target.Johnettajohnette
@Valgus Hi! Where do you put this? In your AppDelegate, or inside the UI Test before launching the app? Thanks.Paba
I pass an environment variable to my app from my UI test target, and in didFinishLaunching, I check for that variable, then call the above code. I can update my answer with more context.Valgus
Sorry, launch argument, not environment variable.Valgus
why you set speed to 100 and not to 10 000?Spoil
Just an arbitrary value. 100 speeds it up enough for my needs.Valgus
G
12

Another possibility is to disable animations at all:

[UIView setAnimationsEnabled:NO];

Swift 3:

UIView.setAnimationsEnabled(false)
Goblet answered 24/5, 2016 at 15:1 Comment(2)
This is awesome! Thank you so much for sharing. My test suite runs at the speed of light now.Silverpoint
You should not disable animations altogether as you might fail to catch some bugs specifically linked to animations. Check this great blog post for more info.Johnettajohnette
C
6

Following @Mark answer, the Swift 3 version:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    if ProcessInfo.processInfo.arguments.contains("UITests") {
        UIApplication.shared.keyWindow?.layer.speed = 200
    }
}

On you ui test file:

override func setUp() {
    super.setUp()

    // Put setup code here. This method is called before the invocation of each test method in the class.

    let app = XCUIApplication()
    app.launchArguments = ["UITests"]
    app.launch()
Cultch answered 24/2, 2017 at 12:47 Comment(0)
S
3

Add it in didFinishLaunch

[UIApplication sharedApplication].keyWindow.layer.speed = 2;

The default value is 1, make it 2 to double its speed.

Squamation answered 9/6, 2017 at 8:3 Comment(1)
Can you explain why your solution is better than the others?Accept
U
3

Run them in parallel.

EDIT: This may be outdated since my original answer in 2019, since Xcode allows now testing on multiple simulators within one machine:

If you have only 1 build machine, you can use Bluepill: https://github.com/linkedin/bluepill

EDIT: However, I don't use either of these (bluepill / Xcode), so I'll keep mention of the bluepill in this answer, maybe it has some uses.

If you have multiple machines, you can use Emcee: https://github.com/avito-tech/Emcee (it also works for a single machine setup)

We have 50 hours of UI tests, and Emcee allows us to run them in 1 hour. There are several tricks to make UI tests faster, but it is pointless if you are not running them in parallel. E.g. you can't make your 25 seconds tests run in 0.5 second.


Tricks:

  1. Run XCUIApplication without reinstalling it:
XCUIApplication(
    privateWithPath: nil,
    bundleID: "your.bundle.id"
)

Note: you should launch app with XCUIApplication().launch() at least once per launching XCUI test runner to install the app. This requires usage of private API.

  1. You can move something to background thread. E.g.
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish

You can make the code look like there is no asynchronous things, it would be easier for QA. We want to implement this, but we didn't, so it is just an idea.

  1. Switch to EarlGrey and make tests that lasts few seconds (but they will not be black box).

  2. Open screens via deep links if you have deep links.

  3. Use a lot of private API and other hacks. E.g.: instead of going via UI to Settings app and then reset privacy settings you can call some private API.

  4. Never use long sleeps. Use polling.

  5. Speed up animations. Do not disable them! (we use layer.speed = 100 on every view and we got severe problems with the value of 10000, full code: https://pastebin.com/AnsZmzuQ)

  6. Pass some flags from UI tests to app to skip initial alerts/tutorials/popups. Can save a lot of time. Be sure to have at least 1 test that checks that those alerts work.

Advertisement: most of these is implemented in https://github.com/avito-tech/Mixbox. Disclaimer: I'm the main contributor in this test framework. Its tedious to set up, but at least you can reuse some code if you don't want to reuse whole framework.

Ultrastructure answered 3/2, 2019 at 15:9 Comment(1)
Hi from 2020, this is a good answer and I started setting up my bluepill setup, however it would save me some time to know that parallel testing is now supported natively in Xcode, no need for 3rd party tools, at least of 1 machine setup.Bridesmaid
T
2

I wanted to disable ALL animations during Snapshot testing. I was able to able to achieve this by disabling both Core Animation and UIView animations as below.

Note because my app used storyboards UIApplication.shared.keyWindow was nil at launch so I access the UIWindow by referring to the window property directly.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
        // Disable Core Animations
        window?.layer.speed = 0
        // Disable UIView animations
        UIView.setAnimationsEnabled(false)
    }
    return true
}
Thursday answered 28/1, 2019 at 14:17 Comment(0)
H
1

I decrease my UITests time in 30%, follow all steps:

When you run your app, add the argument:

let app = XCUIApplication()

override func setUp() {
    super.setUp()

    continueAfterFailure = false
    app.launchArguments += ["--Reset"]
    app.launch()
}

Now, in your AppDelegate add:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    setStateForUITesting()
}

static var isUITestingEnabled: Bool {
    get {
        return ProcessInfo.processInfo.arguments.contains("--Reset")
    }
}

private func setStateForUITesting() {
    if AppDelegate.isUITestingEnabled {
        // If you need reset your app to clear state
        UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)

        // To speed up your tests
        UIApplication.shared.keyWindow?.layer.speed = 2
        UIView.setAnimationsEnabled(false)
    }
}

In your code, to verify if is in test mode, you can use:

if AppDelegate.isUITestingEnabled {
    print("Test Mode")
}

Additionally, to can wait while the element load I created this extension:

import XCTest

extension XCUIElement {
    func tap(wait: Int, test: XCTestCase) {
        if !isHittable {
            test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
            test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
        }
        tap()
    }
}

Use like this:

app.buttons["start"].tap(wait: 20, test: self)
Henbit answered 29/5, 2019 at 16:3 Comment(1)
Thanks for sharing! I'm sure it took some time and effort. It would be even more useful for everyone if you read other answers first to make sure you don't repeat what others have already said. Cheers. Good job!Goblet
I
0

Note that keyWindow is deprecated as of iOS 13. Specifically, UIApplication.sharedApplication.keyWindow and self.window.keyWindow within the AppDelegate both return nil. This answer says to loop through UIApplication.shared.windows and find the one with isKeyWindow true, but in my test using ios14, even that return false for my only window. In the end, setting self.window.layer.speed worked for me.

Insignificant answered 7/6, 2021 at 15:31 Comment(0)
M
0

You can increase the typing speed (and so speed up the test) by setting the following integer UserDefault in your UI test code:

UserDefaults.standard.set(-1, forKey: "com.apple.xctest.iOSMaximumTypingFrequency")

I was originally having issues with characters being skipped, so was looking for a way to slow down the typing. I found anything below 80 fixed my issue. But also found that -1 enters the text instantly, also very high numbers like 6000.

This isn't documented anywhere, so the behavior might change with new versions of Xcode/iOS. I ran this on iOS 16 on a simulator with Xcode 14.3

Maidenly answered 14/6, 2023 at 23:7 Comment(1)
This is interesting we have also noticed sometimes typing text misses characters. How did you find this key?Whyalla

© 2022 - 2024 — McMap. All rights reserved.