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:
- Using
containing(_:identifier:)
let parentElement = app.otherElements.containing(.textField, identifier: "test").firstMatch
orlet parentElement = app.otherElements.containing(.textField, identifier: "test").element(boundBy: 0)
- Using
containing(_ predicate: NSPredicate)
let parentElement = app.otherElements.containing(NSPredicate(format: "label CONTAINS[c] 'test'").firstMatch
orlet 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:
- 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)
- 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
:
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
Default Uifont Size and Weight But Also Support Preferredfontfortextstyle
iOS 14 Pickerview Cutting Off Text
Passing Values from One View Controller to Another in Swift
How to Implement a "Rate Us" Feature in a Phonegap App
Force Wkwebview to Show Mobile Version
How to Animate a Nslayoutconstraint in Swift
iOS 8 [Uiapplication Sharedapplication].Scheduledlocalnotifications Empty
Allow Zooming Within Iframe But Not on Page in iOS
Contentview with UItableview Not Scrolling Within UIscrollview
Cocoapods Page Redirecting to Github
Is Localstorage on iPad Safari Guaranteed to Be Persistent
Can't Pan Image Taken from Camera with Uiimagepicker
Passing Arguments to @Selector Method
Collide Type Source Error - Spritekit Swift Game
iOS Where to Put Custom Cell Design? Awakefromnib or Cellforrowatindexpath