Disabling waiting for idle state in UI testing of iOS apps
Asked Answered
M

7

17

Basically the problem is the same as this one: XCTestCase: Wait for app to idle

I am using perpetually repeating "background animations" in my views. The UI testing of Xcode/iOS wants to wait for all UIView animations to end before it considers the app idle and goes on with stuff like tapping buttons etc. It just doesn't work with the way we've designed the app(s). (Specifically, we have a button that is animated with UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse options, so it never stops.)

But I'm thinking there might be some way to turn off and/or shorten the state "Wait for app to idle". Is there? How? Is there any other way around this?

Mitchell answered 22/12, 2016 at 6:30 Comment(0)
G
14

Unfortunately using Apple's UI Testing you can't turn 'wait for app to idle' or poll other network activity, however you can use environment variables to disable animations in your app to make the tests more stable. In your setup method before your test set an environment variable like this.

override func setUp() {
    super.setUp()
    continueAfterFailure = false
    let app = XCUIApplication()
    app.launchEnvironment = ["UITEST_DISABLE_ANIMATIONS" : "YES"]
    app.launch()
}

Now in your source code:

if (ProcessInfo.processInfo.environment["UITEST_DISABLE_ANIMATIONS"] == "YES") {
    UIView.setAnimationsEnabled(false)
}

You can place that check in a specific view if you only want it to disable animations for that specific view or in a delegate file to disable animations throughout the app.

Guardant answered 22/12, 2016 at 16:13 Comment(3)
Thanks, this is what I ended up doing for now. It's not perfect but might be the only way for now. Animations could be told to only run one iteration etc.Mitchell
Just to clarify, it should be let app = XCUIApplication() app.launchEnvironment = ["UITEST_DISABLE_ANIMATIONS" : "YES"] app.launch()Alleluia
I have been trying to figure what is happening for so long and this was the problem, thank you!!Atticism
O
37

You actually can disable wait for app to idle. This is a hack and may not be stable. With animations disabled, and this hack enabled, I am seeing about a 20% performance gain (on top of the performance boost from disabling animations).

All you have to do is swizzle out the method that is called to idle the app and no-op it. That method is XCUIApplicationProcess waitForQuiescenceIncludingAnimationsIdle:

Here is my working solution in swift 3 - there is likely a better way but this works for a proof of concept.

Extend the XCTestCase class. I'll call mine MyTestCase

static var swizzledOutIdle = false

override func setUp() {
    if !MyTestCase.swizzledOutIdle { // ensure the swizzle only happens once
        let original = class_getInstanceMethod(objc_getClass("XCUIApplicationProcess") as! AnyClass, Selector(("waitForQuiescenceIncludingAnimationsIdle:")))
        let replaced = class_getInstanceMethod(type(of: self), #selector(MyTestCase.replace))
        method_exchangeImplementations(original, replaced)
        MyTestCase.swizzledOutIdle = true
    }
    super.setUp()
}

@objc func replace() {
    return
}

Note wait for app to idle will no longer appear in the logs.

Orbiculate answered 4/8, 2017 at 20:37 Comment(7)
Works like a charm! In addition you could mention that due to this in some cases it is necessary to make use of waitForExistence function – because of the speed ;)Neutralism
oh my god this is awesome. The test had the "wait for app to idle" crap for 80s now it does what I wanted it to do in 3sShipping
Working well on iOS 13.1 iPad with XCode 11.1, thank you.Jit
@Orbiculate is there a way to do the same for androidNicaragua
I meet the same issue, one of our UI test stuck in idle state around 1 minute and continue to execute following test step. This fixes our test. But if disable all animations, how could we know if any animations is failed in UI Test?Ventose
Is there a solution for the 'Check for interrupting elements affecting...' task? Sometimes it seems to take minutes to set values just because of thisFavus
What a workaround! Genius!!! I was running some tests on website in Safari browser on iPhone. This solution increased the execution speed. Thanks!Galicia
G
14

Unfortunately using Apple's UI Testing you can't turn 'wait for app to idle' or poll other network activity, however you can use environment variables to disable animations in your app to make the tests more stable. In your setup method before your test set an environment variable like this.

override func setUp() {
    super.setUp()
    continueAfterFailure = false
    let app = XCUIApplication()
    app.launchEnvironment = ["UITEST_DISABLE_ANIMATIONS" : "YES"]
    app.launch()
}

Now in your source code:

if (ProcessInfo.processInfo.environment["UITEST_DISABLE_ANIMATIONS"] == "YES") {
    UIView.setAnimationsEnabled(false)
}

You can place that check in a specific view if you only want it to disable animations for that specific view or in a delegate file to disable animations throughout the app.

Guardant answered 22/12, 2016 at 16:13 Comment(3)
Thanks, this is what I ended up doing for now. It's not perfect but might be the only way for now. Animations could be told to only run one iteration etc.Mitchell
Just to clarify, it should be let app = XCUIApplication() app.launchEnvironment = ["UITEST_DISABLE_ANIMATIONS" : "YES"] app.launch()Alleluia
I have been trying to figure what is happening for so long and this was the problem, thank you!!Atticism
G
4

I used gh123man answer in Objective-C in case anyone needs it:

- (void)disableWaitForIdle {

    SEL originalSelector = NSSelectorFromString(@"waitForQuiescenceIncludingAnimationsIdle:");
    SEL swizzledSelector = @selector(doNothing);

    Method originalMethod = class_getInstanceMethod(objc_getClass("XCUIApplicationProcess"), originalSelector);
    Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);
}


- (void)doNothing {
    // no-op
}
Gean answered 13/2, 2018 at 12:21 Comment(0)
C
0

I translated to Objective-C, and successfully used, h.w.powers' Swift solution, in case anyone needs it.

For setting up:

XCUIApplication *app = [[XCUIApplication alloc] init];
app.launchEnvironment = @{@"UITEST_DISABLE_ANIMATIONS":@"YES"}; 
[app launch];

and then in your code

if ([[[NSProcessInfo processInfo] environment][@"UITEST_DISABLE_ANIMATIONS"] isEqualToString:@"YES"]) {
// do something like stopping the animation
}
Castiglione answered 21/4, 2018 at 10:4 Comment(0)
H
0

I was using gh123man's solution in setUp() of a couple of test classes and it worked like a charm until updating to iOS 13.3. Since then app gets stuck in launching state.

Found that it still works if I move it to methods like disableWaitForIdle() and enableWaitForIdle() and call them only in the most granular manner (before and after the tap where I know the app will never become idle), e.g. like this:

@discardableResult func selectOption() -> Self {
    disableWaitForIdle()
    app.cells["Option"].firstMatch.waitAndForceTap(timeout: 20)
    enableWaitForIdle()
    return self
}
Hamlin answered 26/2, 2020 at 8:10 Comment(2)
This does not provide an answer to the question. You can search for similar questions, or refer to the related and linked questions on the right-hand side of the page to find an answer. If you have a related but different question, ask a new question, and include a link to this one to help provide context. See: Ask questions, get answers, no distractionsTrouveur
Thank you for the comment U10-Forward. I found a way to make it work and updated my answer.Discriminating
F
0

For anyone who intermittently runs into this wait for app to idle problem, I also experienced it a few times while running local XCUITests. Quitting and re-opening the simulator has done the trick for me, not sure exactly why. Maybe some system UIKit stuff getting wacky after the simulator has been running for 2 weeks.

Folie answered 19/8, 2022 at 19:5 Comment(0)
P
0

Using wdio/appium solution which helped me was to add capability 'appium:waitForIdleTimeout': 0, without it each action (like click) took 20 seconds.

Psychro answered 20/8, 2023 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.