You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(locator): guard aria-labelledby branch with attr-existence predicate
The wide clickable / labelContains field XPath includes:
.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = X]/@id]
That predicate forces every element to evaluate the inner //*[@id] subquery,
which is O(N²) on any non-trivial document for pure-JS XPath engines (xpath
npm: 7641ms on a 2k-line page; fontoxpath: 7057ms on the same branch).
Browser engines optimize via join-pushdown.
Adding [@aria-labelledby] as a left-to-right filter predicate first cuts
the slow comparison to only elements that actually have the attribute:
.//*[@aria-labelledby][@aria-labelledby = //*[@id][...]/@id]
7641ms → 52ms (147×). Semantics identical: in XPath, [A][B] and [A and B]
produce the same result-set, but predicates are evaluated left-to-right,
so the cheap attr-existence check filters out the bulk first.
This is a single-character XPath change — codeceptq goes from 9000ms →
325ms on test/data/github.html with no special-case code. Reverted the
per-strategy reimplementation in lib/command/query.js (back to using
Locator.clickable.wide / Locator.field.byText directly).
Added two unit tests for the aria-labelledby branch in
Locator.clickable.wide (positive + negative).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.//*[@role='tab' or @role='link' or @role='menuitem' or @role='menuitemcheckbox' or @role='menuitemradio' or @role='option' or @role='treeitem'][contains(normalize-space(string(.)), ${lit})]`,
145
-
].filter(Boolean)
146
-
}
147
-
148
-
functionfieldByText(text,doc){
149
-
constlit=xpathLocator.literal(text)
150
-
constfieldGuard=`[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`
151
-
152
-
constlabelFors=labelForsByContainsText(doc,text)
153
-
constidMatch=labelFors.length ? ` or ${anyAttrEquals('./@id',labelFors)}` : ''
154
-
155
-
return[
156
-
`.//*${fieldGuard}[((./@name = ${lit}) or ./@placeholder = ${lit}${idMatch})]`,
`.//*[@role='tab' or @role='link' or @role='menuitem' or @role='menuitemcheckbox' or @role='menuitemradio' or @role='option' or @role='treeitem'][contains(normalize-space(string(.)), ${literal})]`,
595
595
]),
@@ -632,7 +632,7 @@ Locator.field = {
632
632
`.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
0 commit comments