[🐛 Bug]: An index past the end of a $$([a, b]) list with mixed selectors throws a selector error and leaves an unhandled rejection · Issue #16122 · webdriverio/webdriverio · GitHub
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
{{ message }}
Repository navigation
[🐛 Bug]: An index past the end of a $$([a, b]) list with mixed selectors throws a selector error and leaves an unhandled rejection #16122
An index past the end of a $$([a, b]) list whose elements come from different selectors fails with an error about the selector, not "not found":
// <ul><li>a</li><li>b</li></ul><p>c</p>, waitforTimeout: 500constlist=awaitbrowser.$$([awaitbrowser.$('li:first-child'),awaitbrowser.$('p')])awaitlist[5].isExisting()// throws after 500 ms: waitUntil condition failed with the following reason:// selector needs to be typeof `string` or `function`, but found: `undefined`
Reading the index without await (if (list[5])) gives an unhandled rejection with the same message after waitforTimeout.
List
await list[5].isExisting()
if (list[5]), no await
$$('li') (main)
false after waitforTimeout
no unhandled rejection
$$([li, p]), mixed selectors (10.0.2 and main)
throws the selector error
unhandled rejection
What is your expected behavior?
The same as for a $$(selector) list: isExisting() gives false, a command such as getText() throws "Index out of bounds", and reading the index does not leave an unhandled rejection. A list that cannot be queried again (no common selector) could also give undefined for an index past the end, as a slice() or filter() list does.
How to reproduce the bug.
The code above, in a standalone session with headless Chrome.
We greatly appreciate any contributions that help resolve the bug. While we understand that active contributors have their own priorities, we kindly request your assistance if you rely on this bug being fixed. We encourage you to take a look at our contribution guidelines or join our friendly Discord development server, where you can ask any questions you may have. Thank you for your support, and cheers!
Investigation: confirmed on main — all three reported behaviors traced in code, and a simulation of the elementAt path reproduces the exact error string.
Findings
Why the selector error:$$([elemA, elemB]) normalizes its selector via normalizeSelector (packages/webdriverio/src/element/array.ts:74-94): when the elements come from different selectors the list has no selector (undefined). But for an index past the end, elementAt (array.ts:197-) still treats the list as an original query and calls parent[foundWith](selector, ...props) with selector === undefined. findElements then throws selector needs to be typeof `string` or `function`, but found: `undefined` (utils/index.ts:1347) insidebrowser.waitUntil, which wraps it as "waitUntil condition failed with the following reason: …". The catch at array.ts:229-232 rethrows it because the message isn't the timeoutMsg — so the "not found" branch (array.ts:243-268, via itemOf in utils/findElementAgain.ts:67-79, which correctly returns undefined for a selector-less list) is never reached. That's why you get the selector error instead of isExisting() === false / "Index out of bounds".
Why the unhandled rejection: for a resolved list, readIndex (array.ts:181-195) returns chainElementPromise(elementAt(...)) — a truthy proxy. if (list[5]) sees the truthy proxy while the underlying elementAt promise rejects with no handler attached (handlers are only bound when then/catch/finally are accessed, wdio-utils/src/shim.ts:65,443) → unhandled rejection.
The code comments already acknowledge this gap ("a list of elements or element references … still throws", array.ts:82-84 / findElementAgain.ts:64-67), so this is a real, localized bug rather than intended behavior — the escape hatch exists for unregistered custom strategies, but a $$([...]) element list falls into it by accident and surfaces an internal-looking error.
Suggested fix direction
A list whose elements have no common selector can never be re-queried, so it should behave like a derived (slice/filter) list: either
mark it non-refetchable when normalizeSelector yields undefined for an element-array input (mirroring derivedMetadata's refetch: false, array.ts:116-121), or
short-circuit in elementAt when selector === undefined before entering waitUntil — both make an out-of-range index resolve to undefined (your second expected-behavior option), which also removes the unhandled rejection.
A regression test fits naturally into the existing "not found" describe block in packages/webdriverio/tests/element/array.test.ts:279+.
Actions taken
Labels: kept Bug 🐛, added help wanted + good first pick
State: left open
Next steps
@plum117 — excellent report as always; the repro matrix and the two failure shapes made this quick to pin down. Want to raise a PR for this? The change is localized to packages/webdriverio/src/element/array.ts (+ a unit test), and your expected-behavior table already defines the acceptance criteria. Fork → pnpm install → pnpm run setup → focused tests, per CONTRIBUTING.md.
This looks like a good candidate for an expensed contribution. If you open a PR, a maintainer can apply an Expensable $… label and run the expense workflow after merge.
Automated triage assistant — maintainers may revise.
Activity
wdio-bot commentedon Oct 17, 2026
Thanks for reporting!
We greatly appreciate any contributions that help resolve the bug. While we understand that active contributors have their own priorities, we kindly request your assistance if you rely on this bug being fixed. We encourage you to take a look at our contribution guidelines or join our friendly Discord development server, where you can ask any questions you may have. Thank you for your support, and cheers!
wdio-bot commentedon Oct 17, 2026
Triage summary
Bug 🐛— element lists ($$/element/array.ts,webdriveriopackage)$$(elements),filter()andslice()lists look like the original query, so running it again gives other elements #15971 ($([elements])with a same selector re-running the query — the opposite failure mode) and [v10] Multi-remote$$()gives a lazy "Index out of bounds" element for an instance with fewer elements #15845 (multi-remote lazy out-of-bounds element). This report is about a list that cannot be re-queried at all.main— all three reported behaviors traced in code, and a simulation of theelementAtpath reproduces the exact error string.Findings
Why the selector error:
$$([elemA, elemB])normalizes its selector vianormalizeSelector(packages/webdriverio/src/element/array.ts:74-94): when the elements come from different selectors the list has no selector (undefined). But for an index past the end,elementAt(array.ts:197-) still treats the list as an original query and callsparent[foundWith](selector, ...props)withselector === undefined.findElementsthen throwsselector needs to be typeof `string` or `function`, but found: `undefined`(utils/index.ts:1347) insidebrowser.waitUntil, which wraps it as "waitUntil condition failed with the following reason: …". The catch atarray.ts:229-232rethrows it because the message isn't thetimeoutMsg— so the "not found" branch (array.ts:243-268, viaitemOfinutils/findElementAgain.ts:67-79, which correctly returnsundefinedfor a selector-less list) is never reached. That's why you get the selector error instead ofisExisting() === false/ "Index out of bounds".Why the unhandled rejection: for a resolved list,
readIndex(array.ts:181-195) returnschainElementPromise(elementAt(...))— a truthy proxy.if (list[5])sees the truthy proxy while the underlyingelementAtpromise rejects with no handler attached (handlers are only bound whenthen/catch/finallyare accessed,wdio-utils/src/shim.ts:65,443) → unhandled rejection.The code comments already acknowledge this gap ("a list of elements or element references … still throws",
array.ts:82-84/findElementAgain.ts:64-67), so this is a real, localized bug rather than intended behavior — the escape hatch exists for unregistered custom strategies, but a$$([...])element list falls into it by accident and surfaces an internal-looking error.Suggested fix direction
A list whose elements have no common selector can never be re-queried, so it should behave like a derived (
slice/filter) list: eithernormalizeSelectoryieldsundefinedfor an element-array input (mirroringderivedMetadata'srefetch: false,array.ts:116-121), orelementAtwhenselector === undefinedbefore enteringwaitUntil— both make an out-of-range index resolve toundefined(your second expected-behavior option), which also removes the unhandled rejection.A regression test fits naturally into the existing "not found" describe block in
packages/webdriverio/tests/element/array.test.ts:279+.Actions taken
Bug 🐛, addedhelp wanted+good first pickNext steps
@plum117 — excellent report as always; the repro matrix and the two failure shapes made this quick to pin down. Want to raise a PR for this? The change is localized to
packages/webdriverio/src/element/array.ts(+ a unit test), and your expected-behavior table already defines the acceptance criteria. Fork →pnpm install→pnpm run setup→ focused tests, per CONTRIBUTING.md.This looks like a good candidate for an expensed contribution. If you open a PR, a maintainer can apply an
Expensable $…label and run the expense workflow after merge.Automated triage assistant — maintainers may revise.
fix(webdriverio): give out-of-range indexes of a selector-less elemen…