Xcode UITest sometimes does not find property of XCUIElement
Asked Answered
C

3

8

In my UI tests, the frame property of some XCUIElement are found, but not of others.
The accessibility identifiers used below are set in storyboard, and app is initialised in setUp() as XCUIApplication().

Here is the storyboard layout:

enter image description here

The two UI elements used in the test are Text Field and Add Button.

Here is the relevant code:

func test() {
    // given
    let mainViewNavigationBar = app.navigationBars[„NavBar“]
    let navBarHeight = mainViewNavigationBar.frame.size.height
    print("navBarHeight: \(navBarHeight)") // is printed out correctly

    let addShoppingItemTextField = app.textFields["TextField"]
    let textFieldHeight = addShoppingItemTextField.frame.size.height // error breakpoint here
    print("textFieldHeight: \(textFieldHeight)")
}

The test stops at an error breakpoint at the second last line with the following message:

No matches found for Find: Descendants matching type TextField from input {(
    Application, 0x60000019f070, pid: 13114, label: ‚xxx‘
)}

I do not understand why the frame property, which should be defined for all XCUIElement, is found in the first case, but not in the second.

EDIT

Oletha pointed out below, that my constant addShoppingItemTextField is an XCUIElementQuery that should be resolved when I try to read the frame property of the textField.
Indeed, when the program stops at the test error breakpoint and I print its description, I get

Printing description of addShoppingItemTextField:
Query chain:
 →Find: Target Application 0x6080000a6ea0
  ↪︎Find: Descendants matching type TextField
    ↪︎Find: Elements matching predicate '"TextField" IN identifiers'  

But the find fails, although Accessibility is enabled, and the Accessibility Identifier is set to TextField:

enter image description here

I also inserted in the app

print(textField.accessibilityIdentifier!)

in viewDidLoad(), and it printed out TextField correctly.

As a workaround, I set the test to recording, and tapped the textField. This created code for the access to the textField. I then replaced let addShoppingItemTextField = app.textFields["TextField"] by (the right side was generated by the recording):

let addShoppingItemTextField = app.otherElements.containing(.navigationBar, identifier:"WatchNotOK")
.children(matching: .other).element.children(matching: .other).element
.children(matching: .other).element

And now the code works without errors.

So it seems to me that the query for the accessibility identifier of a textField does not work correctly.

EDIT 2

I give up: Without changing anything in the storyboard, the test now stops with the same test error (No matches found for Find: Elements matching predicate '"WatchNotOK" IN identifiers‘) at the line let navBarHeight = mainViewNavigationBar.frame.size.height. This did work all the time.
This indicates to me that Xcode UI tests are broken.

Cloister answered 3/1, 2018 at 10:45 Comment(4)
As I can see, you wrote TextField in your test (without space). Did you try writing Text Field with space? You wrote it with space in your question, and it is also with space on the image you provided.Interminable
@lagoman "TextField" is my Accessibility id, which is assigned to my UITextField. The TextField mentioned in the log is the same as UITextField, which is for some reason displayed in the storyboard as "Text Field". I confess that this is confusing, both the accessibility id is completely independent of the UITextField type. Actually, I my real code I used a different, much longer accessibility id. Here, I simplified it for better readability.Snob
When you write subscript like this app.textFields["Some text"] you are querying views that have Text Field trait and have accessibility label Some text. Accessibility label in UITextField view is by default set to string that is being displayed in that view, so you should try querying that way, or if this doesn't work and you know position order of that view, you can use app.staticTexts.element(boundBy: 1) (this finds 2nd Text Field).Interminable
By Accessibility label you probably mean the accessibility identifier, and the query that I used indeed searched for this identifier (see my edit above). By default, my textField is empty, so I cannot use your workaround, but thanks anyway! I found another workaround, see above.Snob
C
28

I contacted Apple, and they found my bug:
The view of my main view controller had its accessibility property set to true. This was wrong; it must be set to false:

enter image description here

The explanation is found in the docs to isAccessibilityElement:

The default value for this property is false unless the receiver is a standard UIKit control, in which case the value is true.
Assistive applications can get information only about objects that are represented by accessibility elements. Therefore, if you implement a custom control or view that should be accessible to users with disabilities, set this property to true. The only exception to this practice is a view that merely serves as a container for other items that should be accessible. Such a view should implement the UIAccessibilityContainer protocol and set this property to false.

As soon as I set accessibility of the main view to false, the UI test succeeded.

Cloister answered 20/1, 2018 at 10:9 Comment(4)
Thanks for finding the fix, but I still don't see how the docs apply here. Does it mean that by disabling isAccessibilityElement for UI testing purposes, it will no longer be accessible when running the app in a non-testing environment?Gamba
Up to now I used accessibility only for UI testing. But my understanding is that if accessibility is disabled for a custom view, it will no longer be accessible during normal (non UI testing) operation. However, views embedded in this custom „container“ view can still be accessible. So this should not be a limitation.Snob
In my case, it may be a limitation. I have four UISwitches, each of which has an associated UILabel. I want to test the state of the switches, but I have to turn off their accessibility. Now, if I want them to be accessible at runtime, I can turn on accessibility for the view that contains each switch and its label, but then I have to manually update the accessibility label when the switch state changes. It would be better to keep the accessibility on for the user, so I may have to drop tests for the switches.Gamba
@ReinhardMänner: I'm not using Storyboard. Designed UI using thirdparty framework (similar to Swift UI) and also programmatically not setting any accessibility enable/disable.:Buzzell
K
1

In addition with above answers... I would like to add one point This may happen because the XCUIElement you are accessing is not available on screen. Suppose you are executing test case for login screen and simulator is launching with dashboard not with login screen. This happen with my case. I tried to logout and then execute test case. Error disappears

Kinsella answered 28/5, 2019 at 11:20 Comment(0)
R
0

The problem is not that the frame property is not found on the element, it's that the element itself could not be found.

Every XCUIElement is derived from an XCUIElementQuery. The first attempt to resolve the query is not, as you might expect, when you assign the value of addShoppingItemTextField, but the first time you access a property (other than exists) on addShoppingItemTextField.

Therefore, when you try to access the frame property on the XCUIElement object, the query for finding that element is resolved, but the element is not found - so you get the error saying 'No matches found...' on the line where you access frame. This can be a bit misleading, but the problem you're encountering is that the element could not be found. Try adjusting your query.

Rajah answered 4/1, 2018 at 10:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.