14

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?

Losiowaty
  • 7,911
  • 2
  • 32
  • 47
Jonny
  • 15,955
  • 18
  • 111
  • 232

7 Answers7

34

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.

auspicious99
  • 3,902
  • 1
  • 44
  • 58
gh123man
  • 1,372
  • 1
  • 14
  • 24
  • 1
    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 ;) – inf1783 Oct 20 '17 at 10:09
  • 1
    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 3s – Michael McKenna Jul 23 '18 at 22:42
  • Working well on iOS 13.1 iPad with XCode 11.1, thank you. – xaphod Oct 21 '19 at 01:17
  • @gh123man is there a way to do the same for android – user2237529 Mar 23 '21 at 22:08
  • 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? – Zhou Haibo Feb 16 '22 at 06:02
  • Is there a solution for the 'Check for interrupting elements affecting...' task? Sometimes it seems to take minutes to set values just because of this – themihai Jan 23 '23 at 14:36
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.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44
h.w.powers
  • 820
  • 8
  • 10
  • 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. – Jonny Dec 25 '16 at 01:37
  • Just to clarify, it should be `let app = XCUIApplication() app.launchEnvironment = ["UITEST_DISABLE_ANIMATIONS" : "YES"] app.launch()` – Jeremy Jul 18 '17 at 17:15
  • I have been trying to figure what is happening for so long and this was the problem, thank you!! – Jason Silberman Aug 09 '18 at 19:16
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
}
tagy22
  • 1,353
  • 17
  • 26
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
}
auspicious99
  • 3,902
  • 1
  • 44
  • 58
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
}
  • This does not provide an answer to the question. You can [search for similar questions](//stackoverflow.com/search), 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](//stackoverflow.com/questions/ask), and include a link to this one to help provide context. See: [Ask questions, get answers, no distractions](//stackoverflow.com/tour) – U13-Forward Feb 26 '20 at 08:29
  • Thank you for the comment U10-Forward. I found a way to make it work and updated my answer. – Aistė Stikliūtė Feb 26 '20 at 09:13
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.

Trev14
  • 3,626
  • 2
  • 31
  • 40
0

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