Skip to content

fix: Optimize class chain lookup - #1194

Open
mykola-mokhnach wants to merge 2 commits into
masterfrom
chain-lookup
Open

fix: Optimize class chain lookup#1194
mykola-mokhnach wants to merge 2 commits into
masterfrom
chain-lookup

Conversation

@mykola-mokhnach

Copy link
Copy Markdown

Summary

  • fb_descendantsMatchingClassChain: previously resolved a live XCUIElement (and paid a fresh accessibility round trip) for every indexed segment of a class chain locator, just to obtain a query root for the next segment — expensive for multi-segment indexed chains (e.g. /A[2]//B[3]/C).
  • Replaced the XCUIElementQuery-building approach with a single upfront snapshot of self, walked entirely in memory for type/predicate/position filtering across all segments. A live element is now resolved only once, for the final match(es), via the existing uid-predicate-based fb_filterDescendantsWithSnapshots:onlyChildren: helper.
  • Found and fixed a related correctness pitfall along the way: XCTest's private descendantsByFilteringWithBlock: includes the receiver itself when it matches the filter (unlike XCUIElementQuery's descendantsMatchingType:), so the root snapshot is now explicitly excluded from descendant matches.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes class chain lookup by avoiding repeated live XCUIElement resolution for intermediate class-chain segments, instead walking a single upfront snapshot in-memory and resolving live elements only for the final matches.

Changes:

  • Replaces incremental XCUIElementQuery building with snapshot-based traversal for class chain segment evaluation.
  • Applies type/predicate filtering and position selection on snapshot candidates, then resolves final XCUIElement instances via existing snapshot→UID lookup.
  • Explicitly excludes the root snapshot from descendant matches to align behavior with descendantsMatchingType: semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 78 to +82
}
if (item.predicates) {
for (FBAbstractPredicateItem *predicate in item.predicates) {
if ([predicate isKindOfClass:FBSelfPredicateItem.class]) {
query = [query matchingPredicate:predicate.value];
} else if ([predicate isKindOfClass:FBDescendantPredicateItem.class]) {
query = [query containingPredicate:predicate.value];
for (FBAbstractPredicateItem *predicateItem in item.predicates) {
if ([predicateItem isKindOfClass:FBSelfPredicateItem.class]) {
typeMatches = [[typeMatches filteredArrayUsingPredicate:predicateItem.value] mutableCopy];
} else if ([predicateItem isKindOfClass:FBDescendantPredicateItem.class]) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 924a231 by de-duplicating typeMatches (via pointer identity) whenever more than one root is being walked, before predicates/position are applied.

Comment on lines 37 to 39
if (nil != chainItem.position) {
// It is necessary to resolve the query if intermediate element index is not zero or one,
// It is necessary to resolve the position if intermediate element index is not zero or one,
// because predicates don't support search by indexes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, the old wording didn't match reality (the parser never allows position 0, and this branch runs for any explicit position including 1). Reworded in 924a231 to explain why an indexed segment has to collapse to a single root immediately instead of staying folded into candidates.

De-duplicate overlapping-root matches before positional selection, and
reword a stale comment about when intermediate positions get resolved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Dan-Maor

Dan-Maor commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Similarly to #1193 there is a major performance impact here when comparing to the current master.

In the deep nested application I mentioned in #1193 an object lookup using the following selector:

**/XCUIElementTypeTextView[`value BEGINSWITH "View 19"`]/XCUIElementTypeTextView[1]

is significantly slower - lookup when built on master takes on an iPhone 12 Pro ~650ms on average, whereas with these changes lookup takes roughly twice that time (~1200ms on average), tested directly via POST commands to WDA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants