I have a layered UI that I'm trying to create a UI test for. I'm working without a deep understanding of how the UI is constructed, but I do have full access to the source code.
I'm trying to tap a button that pops up to "Confirm" deletion of a particular entry. However, I'm not able to craft a query in Swift that would give me the correct button to tap. Here's the full dump from the error:
UI Test Activity:
Assertion Failure: ProfileScreen.swift:77: Failed to get matching snapshot: Multiple matching elements found for <XCUIElementQuery: 0x6000007c4d20>.
Sparse tree of matches:
→Application, pid: 92707, label: '******'
↳Window (Main)
⋅ ↳Other
⋅ ↳Other
⋅ ↳Button, identifier: 'Remove', label: 'Remove'
↳Window
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳Other
↳CollectionView
↳Cell, Selected
⋅ ↳Other
⋅ ↳Button, label: 'Remove'
↳Cell
↳Other
↳Button, label: 'Remove'
Possibly caused by runtime issues:
Automation type mismatch: computed Other from legacy attributes vs StaticText from modern attribute. Input attributes and values: {
"XC_kAXXCAttributeAutomationType" = 48;
"XC_kAXXCAttributeElementBaseType" = UILabel;
"XC_kAXXCAttributeElementType" = UILabel;
"XC_kAXXCAttributeTraits" = 8590000128;
}
See test report attachments for more detail.
The query that I have to generate that error is this:
XCUIApplication().buttons["Remove"].tap()
And the actual UIHierarchy:
Window, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
NavigationBar, {{0.0, 44.0}, {414.0, 44.0}}, identifier: 'Payment Information'
Button, {{0.0, 44.0}, {44.0, 44.0}}, label: 'Settings'
StaticText, {{139.5, 58.5}, {135.0, 17.0}}, label: 'Payment Information'
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 88.0}, {414.0, 808.0}}
Other, {{0.0, 88.0}, {414.0, 808.0}}
CollectionView, {{0.0, 88.0}, {414.0, 808.0}}
Cell, {{0.0, 88.0}, {414.0, 72.0}}, Selected
Other, {{0.0, 88.0}, {414.0, 72.0}}
Image, {{24.0, 115.0}, {18.0, 18.0}}
Image, {{58.0, 110.0}, {49.5, 28.0}}
StaticText, {{123.5, 114.5}, {36.0, 19.5}}, label: '1111'
Button, {{331.0, 108.5}, {59.0, 31.0}}, label: 'Remove'
StaticText, {{331.0, 114.5}, {59.0, 19.0}}, label: 'Remove'
Other, {{0.0, 159.5}, {414.0, 0.5}}
Cell, {{0.0, 160.0}, {414.0, 72.0}}
Other, {{0.0, 160.0}, {414.0, 72.0}}
Image, {{24.0, 187.0}, {18.0, 18.0}}
Image, {{58.0, 182.0}, {49.5, 28.0}}
StaticText, {{123.5, 186.5}, {36.0, 19.5}}, label: '2222'
Button, {{331.0, 180.5}, {59.0, 31.0}}, label: 'Remove'
StaticText, {{331.0, 186.5}, {59.0, 19.0}}, label: 'Remove'
Other, {{0.0, 231.5}, {414.0, 0.5}}
Cell, {{0.0, 232.0}, {414.0, 72.0}}
Other, {{0.0, 232.0}, {414.0, 72.0}}
Image, {{24.0, 259.0}, {18.0, 18.0}}
Other, {{58.0, 254.0}, {49.5, 28.0}}
Button, {{64.2, 257.5}, {37.1, 21.0}}, label: 'Buy with Touch Pay'
StaticText, {{64.2, 257.5}, {0.0, 0.0}}
StaticText, {{123.5, 258.5}, {75.5, 19.5}}, label: 'Touch Pay'
Other, {{0.0, 303.5}, {414.0, 0.5}}
Cell, {{0.0, 304.0}, {414.0, 166.0}}
Other, {{0.0, 304.0}, {414.0, 166.0}}
Button, {{16.0, 328.0}, {382.0, 60.0}}, label: 'Add New Item'
StaticText, {{107.5, 348.0}, {199.0, 20.0}}, label: 'Add New Item'
Other, {{381.0, 105.5}, {30.0, 755.5}}, label: 'Vertical scroll bar, 1 page', value: 0%
Other, {{35.0, 863.0}, {344.0, 30.0}}, label: 'Horizontal scroll bar, 1 page', value: 0%
Other, {{0.0, 840.0}, {414.0, 56.0}}
Other, {{0.0, 840.0}, {414.0, 0.5}}
Other, {{0.0, 840.0}, {414.0, 56.0}}
Button, {{0.0, 840.0}, {103.5, 56.0}}, identifier: 'tab_bar_title-main', label: 'Main'
Button, {{103.5, 840.0}, {103.5, 56.0}}, identifier: 'tab_bar_title-secondary', label: 'Secondary'
Button, {{207.0, 840.0}, {103.5, 56.0}}, identifier: 'tab_bar_title-todo', label: 'Todo'
Button, {{310.5, 840.0}, {103.5, 56.0}}, identifier: 'tab_bar_title-whatsup', label: 'Whatsup', Selected
Window, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 896.0}, {414.0, 243.0}}
Window (Main), {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{67.0, 350.0}, {280.0, 196.5}}
StaticText, {{101.0, 384.0}, {212.0, 24.0}}, label: 'Remove Item'
StaticText, {{101.0, 424.0}, {212.0, 38.0}}, label: 'Do you want to remove the Item?'
Other, {{67.0, 486.0}, {280.0, 0.5}}
Button, {{67.0, 486.5}, {140.0, 60.0}}, identifier: 'Cancel', label: 'Cancel'
StaticText, {{112.0, 507.0}, {50.0, 19.0}}, label: 'Cancel'
Other, {{207.0, 486.0}, {0.5, 60.5}}
Button, {{207.5, 486.5}, {139.5, 60.0}}, identifier: 'Remove', label: 'Remove'
StaticText, {{247.0, 506.5}, {61.0, 20.0}}, label: 'Remove'
Other, {{0.0, 0.0}, {414.0, 896.0}}
Other, {{0.0, 0.0}, {414.0, 896.0}}
So it's obviously finding both of those buttons. There are two buttons with the same staticText and Label. Is there a way I can easily have it tap whichever one is enabled or has current focus?
Also I'm curious if there's a way to convert what that UI Hierarchy dump is telling me into a workable query.
You can typically select the only, the first, the Nth, or the last button.
For the first use:
let app = XCUIApplication()
app.buttons["Remove"].tap() // the only
app.buttons["Remove"].element.tap() // the only
app.buttons["Remove"].firstMatch.tap() // the first
app.buttons["Remove"].element(boundBy: 3) // the fourth
app.buttons.matching(identifier: "Remove").lastMatch.tap() // the last
The last query requires extension:
extension XCUIElementQuery {
var lastMatch: XCUIElement { return self.element(boundBy: self.count - 1) }
}
Read more about other queries here: https://developer.apple.com/documentation/xctest/xcuielementquery
You can also try tools like https://github.com/forqa/skeleton that generates lists of elements for you