How to Get Index of Xcuielement in Xcuielementquery

iOS UITests - How to get XCUIElement list which exist in current view

If you want the full list of elements, rather than just otherElements, which does not include buttons, labels and other common view types, you must filter by element type .Any.

Elements which do not exist will not appear in the list by default, but you can use a loop to filter by hittable to restrict the list to only those items which are on-screen.

let app = XCUIApplication()
let allElements = app.descendantsMatchingType(.Any)
var allHittableElements = [XCUIElement]()
for i in 0..<allElements.count {
let element = allElements.elementBoundByIndex(i)
if element.hittable {
allHittableElements.append(element)
}
}

Quite a slow but simple solution.

For a faster solution, you can make XCUIElementQuery conform to Collection and use Collection.filter.

Access last element in XCUIElement

You can do it using :

XCUIApplication().staticTexts.matching(identifier: "SUBMIT ORDER").allElementsBoundByIndex.last

Select parent of XCUIElement

Unfortunately there is no direct named method to access parent elements similar to children() and descendants() provided by Apple but you were actually on the right track with containing(). There are two ways I usually approach it when I need to locate a parent element based on children/descendants:

  1. Using containing(_:identifier:)

    let parentElement = app.otherElements.containing(.textField, identifier: "test").firstMatch

    or

    let parentElement = app.otherElements.containing(.textField, identifier: "test").element(boundBy: 0)
  2. Using containing(_ predicate: NSPredicate)

    let parentElement = app.otherElements.containing(NSPredicate(format: "label CONTAINS[c] 'test'").firstMatch

    or

    let parentElement = app.otherElements.containing(NSPredicate(format: "label CONTAINS[c] 'test'").element(boundBy: 0)

These are just examples with random data/element types because you didn't mention exactly what you want to achieve but you can go from there.

Update:

As usual the Apple documentation doesn't do a good service. They say 'descendants' but what they actually mean is both direct descendants(children) and non-direct descendants(descendants). Unfortunately there is no guarantee and there is no generic solution. It should be based on your current needs and the application implementation. More examples that could be useful:

  1. If you don't want the first element from the query you are better off using element(boundBy: index). So if you know that XCUIElementQuery will give you 5 elements and you know you need the 3rd one:

    let parentElement = app.otherElements.containing(.textField, identifier: "test").element(boundBy: 2)
  2. Fine graining of your element locators. Lets say you have 3 views with identifier "SomeView", these 3 views each contain 2 other subviews and the subviews have a button with identifier "SomeButton".
let parentViews = app.otherElements.matching(identifier: "SomeView")
let subView = parentViews.element(boundBy: 2).otherElements.containing(.button, identifier: "SomeButton").element(boundBy: 1)

This will give you the second subview containing a button with identifier "SomeButton" from the third parent view with identifier "SomeView". Using such an approach you can fine tune until you get exactly what you need and not all parents, grandparents, great-grandparents etc.

I wish Apple provided a bit more flexibility for the locators with XCTest like Xpath does for Appium but even these tools can be sufficient most of the time.

Xcode UITest sometimes does not find property of XCUIElement

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:

Sample Image

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.

Is it possible to operate with the last of matching elements in XCTest?

I managed this problem by writing a little extension

extension XCUIElementQuery {
var lastMatch: XCUIElement { return self.element(boundBy: self.count - 1) }
}

after that, I can simply write code like this

app.buttons.matching(identifier: "play").lastMatch.tap()

Check the position of the XCUIElement on screen while testing iOS Application using XCTest

XCUIElement has a property frame which you can use to find the coordinates of the element in question.

let button = XCUIApplication().buttons["someButton"]
let frame = button.frame
let xPosition = frame.origin.x
let yPosition = frame.origin.y

There are other ways of retrieving different points relative to the frame, which is a CGRect, such as midX and midY, depending on how you want to assert the position of the element.

You should be aware that XCTest is a functional UI testing framework, and if you are using it for asserting the position of an element, the position will probably be different per device/simulator which may make your tests brittle.

Meaning of allElementsBoundByAccessibilityElement

The documentation for allElementsBoundByAccessibilityElement is not so great but I determined the difference from allElementsBoundByIndex by poking around with a debugger.

Calling either allElementsBoundByAccessibilityElement or allElementsBoundByIndex will return an array of XCUIElement objects.

let app = XCUIApplication();
app.launch();

let elementsByAccessibilityElement = app.images.allElementsBoundByAccessibilityElement;

let elementsByIndex = allElementsBoundByIndex;

Immediately after returning this array, the XCUIElement objects themselves are not actually resolved, and keep a reference to the original query.

Try to access a property like .label on the XCUIElement though...

And you will see the element resolve itself by snapshotting the accessibility hierarchy and then re-running the original query to find the element. You can see this in the debug console an output like this:

    t =     5.76s Get all elements bound by index for: Descendants matching type Other
t = 5.84s Snapshot accessibility hierarchy for app with pid 4267
t = 10.79s Find: Descendants matching type Other

The big difference between the two methods when the element is resolving and running the original query. When you call allElementsBoundByIndex, the XCUIElement instance finds itself by running the original query and then getting the result at that index.

That means if the app UI changes between calling allElementsBoundByIndex and actually resolving the XCUIElement objects in the array, you may receive a different set of elements in the array than you originally expected.

When you call allElementsBoundByAccessibilityElement, the XCUIElement instance finds itself by running the original query and then getting the result matching the accessibility identifier that element had at the time the query was created.

If the app UI changes between calling allElementsBoundByAccessibilityElement and actually resolving the XCUIElement objects in the array, and one of the original elements is no longer present, the app will throw an error.



Related Topics



Leave a reply



Submit