XCUIElement tap() not working
Asked Answered
W

7

17

I have a very simple XCTestCase implementation that tests a tap on a button and expects an Alert controller to show up. The problem is that the tap() method doesn't work. Placing a breakpoint in the associated button's IBAction I realise the logic doesn't even get called.

class uitestsampleUITests: XCTestCase {

    var app: XCUIApplication!

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }

    func testButton() {
        let button = app.buttons["Button"]
        button.tap()

        expectationForPredicate(NSPredicate(format: "exists == 1"), evaluatedWithObject: button, handler: nil)
        waitForExpectationsWithTimeout(5.0, handler: nil)
    }
}

Also, duplicating the button.tap() instruction makes the test pass, like this:

    func testButton() {
        let button = app.buttons["Button"]
        button.tap()
        button.tap()

        expectationForPredicate(NSPredicate(format: "exists == 1"), evaluatedWithObject: button, handler: nil)
        waitForExpectationsWithTimeout(5.0, handler: nil)    
    }

I am facing this issue in Xcode 7.3.1 Am I missing something? Is it a bug?

Workaday answered 17/5, 2016 at 12:44 Comment(9)
I have reported a bug to Apple: openradar.appspot.com/26320475Workaday
Could that be a timing issue? What happens when you add 1 sec wait time before trying to tap? Unfortunately, the UI automation framework, especially the event generation, is full of problems.Palpitation
I am afraid not. It doesn't work even if I wait for this expectation expectationForPredicate(NSPredicate(format: "hittable == 1"), evaluatedWithObject: button, handler: nil)Workaday
What duration did you wait for that expectation? Did you instead try a predicate format "exists == true"? Did you also try a sleep(5) before the tap()?Dasteel
check if this works: > /*Sends a tap event to a hittable/unhittable element.*/ public extension XCUIElement { func forceTapElement() { if self.hittable { self.tap() } else { let coordinate: XCUICoordinate = self.coordinateWithNormalizedOffset(CGVectorMake(0.0, 0.0)) coordinate.tap() } } }Cerracchio
@Dasteel hittable == 1 is more restrictive than exists == 1. With expectationForPredicate sleep is not needed (not recommended either)Workaday
@Cerracchio I tried that but wouldn't work either. Besides I am already calling tap() twice as a workaround.Workaday
Could that be there is some animation that is moving your button? Or interaction is disabled using UIApplication.sharedApplication.beginIgnoringInteractionEvents? There is an enormous number of problems that could happen. You could log all events in UIApplication.sendEvent and print your view hierarchy at that time. That should give you some good info.Palpitation
@XaviGil The comment from the Apple engineer on your Radar (adding sleep(1)) did it for me. Thanks!Pipsqueak
W
10

So an Apple engineer responded to my bug report saying:

The second possibility is that you are running into a problem that sometimes occurs where the application finishes launching but the splash screen doesn't immediately disappear and events dispatched to the app are not handled properly.

To try to work around that issue, consider placing a small delay at the beginning of your test (sleep(1) should be enough).

So I did it and now it works:

override func setUp() {
    super.setUp()
    continueAfterFailure = false
    app = XCUIApplication()
    app.launch()
    sleep(1)
}
Workaday answered 19/5, 2016 at 10:26 Comment(2)
For me the mentioned issue happened not necessarily right after the app's launch. That's why this solution wouldn't cover all cases.Brunildabruning
did you solved this issue? I am having same issue plz help if you solved.Violinist
U
9

For UIWebView which is hittable, the tap didn't work until I've done it via coordinate:

extension XCUIElement {
    func forceTap() {
        coordinate(withNormalizedOffset: CGVector(dx:0.5, dy:0.5)).tap()
    }
}

Also works for items that are not hittable, like labels and such.

Usance answered 9/9, 2017 at 7:32 Comment(1)
Had to use this when I was using a view that floats above the screen (scrollview in this case, although don't know if it matters). It was visible, and existed but was NOT hittable so tap() wasn't working.Monophthong
B
5

I had something similar. For me the problem was that elements, which I tried to tap, sometimes were not hittable for some reason.

From the Apple's documentation:

Sends a tap event to a hittable point computed for the element.

So if an element is not hittable, the tap action doesn't do much, which breaks the logic of your test case.

In order to fix this, before I tap something, I wait until the appropriate element becomes hittable. Pretty straightforward.

#import <XCTest/XCTest.h>

@interface XCUIElement (Tap)

- (void)tapInTestCase:(XCTestCase *)testCase;

@end

@implementation XCUIElement (Tap)

- (void)tapInTestCase:(XCTestCase *)testCase
{
    // wait until the element is hittable
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hittable == true"];
    [testCase expectationForPredicate:predicate evaluatedWithObject:element handler:nil];
    [testCase waitForExpectationsWithTimeout:5.0f handler:nil];

    // and then tap
    [self tap];
}

@end
Brunildabruning answered 18/5, 2016 at 17:56 Comment(3)
Given the answer I got from Apple, your solution might work. However in the very simple test sample I provided the button is always there so it should not have to wait to be hittable (the test should start when the UI is ready). Also, Apple's Record UI Test Xcode feature doesn't populate code for the button to wait to be hittable. I'd rather add the sleep(1) solution until they fix this.Workaday
Great, I'm happy you've a solution. But I should say, it's far from perfect.Brunildabruning
For me both solutions don't work always. Sometimes they do, sometimes don't. Still searching for a way to tap the button correctly... Also have a problem with typeText:Brunildabruning
F
5

You 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)
Fencer answered 29/5, 2019 at 16:10 Comment(0)
G
1

For me the most reliable way was to add sleep(1) in the places where the elements are not being tapped properly. Using usleep function with values less than 1000 caused unreliable behavior such as correct tests randomly failing.

Golanka answered 9/1, 2019 at 23:47 Comment(0)
C
0

In my case problem was in button style. I'm using SUI and set buttonStyle to .borderless. In my list (I guess) system make tap on non-hittable zone. But XCTAssertTrue(app.buttons["myButton"].isHittable) do not throw error.

Cornerstone answered 28/4, 2023 at 17:42 Comment(0)
C
0

My button, inside a LabeledContent, isHittable but my taps weren't registering. I guessed that the system was tapping at the centerpoint of the LabeledContent, not the button itself. The following fixed the issue for my layout:

button.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.95)).tap()
Crackbrained answered 4/1 at 1:17 Comment(4)
The solution using coordinate was given already 6 years ago.Bikol
But that solution did not address the case where LabeledContent was mutilating the event. Tapping the 0.5x0.5 (center) point - on the button - did not work for me because the system was targeting the empty space between the button and its label for reasons I haven't quite figured out. Possibly the order of my method chaining somewhere.Crackbrained
LabeledContent is not part of the question.Bikol
Not explicitly, but it's a common enough wrapper that cases like mine will come up. I don't see the problem with providing a supplementary answer.Crackbrained

© 2022 - 2024 — McMap. All rights reserved.