Repository navigation
refactor!: identify WebdriverIO objects by the wdio.kind brand - #2305
Merged
Merged
Conversation
WebdriverIO v10 brands its browsers, browsing contexts, elements,
element lists and mocks with `Symbol.for('wdio.kind')`, and a
not-awaited `$()` with `Symbol.for('wdio.chainable')`. `getWdioKind()`
and `isChainable()` read them without a dependency on `@wdio/utils`.
Refs webdriverio#2296
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mocks of the browser, the elements, the element lists and the network mocks now have the `wdio.kind` brand, and a not-awaited `$()` has `wdio.chainable`, as in WebdriverIO 10.0.0. The not-awaited `$$()` mock was a Proxy of a Promise, as in v9. In v10 it is the element list itself: `Array.isArray()` is true, `'getElements' in` is true, it has `then`, `catch` and `finally` until it is awaited, its `length` is a Promise until then, and awaiting it gives the same list. The tests that expected the v9 shape now expect the v10 one, so the unit tests take the path that production takes. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`isElement()`, `isStrictlyElementArray()`, `isMultiRemoteElement()` and `isMultiRemoteElementArray()` now read the WebdriverIO v10 `wdio.kind` brand, not the shape of the object (`parent`, `foundWith`, `getElement`, `getElements`, `selector`). A frame browsing context also has a `parent`, and a fake with the same properties is not a WebdriverIO object. A not-awaited `$()` has the `element` brand, but it is a Promise, so `isElement()` also checks `wdio.chainable`. The failure message walks the parents of an element while they are awaited elements or lists, by their brand. An unbranded copy of a list (`[...elements]`, `elements.map()`) is still an `Element[]`, because its items have the brand. BREAKING CHANGE: an object without the WebdriverIO v10 `wdio.kind` brand is not an element or an element list. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`isBrowser()` read the constructor name (`Browser`, `MultiRemoteDriver`), an internal name of `@wdio/utils`. It now reads the WebdriverIO v10 `wdio.kind` brand: `browser`, or `browsing-context`, which also has the `Browser` constructor name. So the failure message of a browsing context does not change (TODO(webdriverio#2298)). `isMultiRemoteBrowser()` also checks the `browser` brand: a multi-remote element has the multi-remote flag too. `isMock()` and `isMultiRemoteMock()` read the `mock` brand, not the `calls` array or the `instances` and `getInstance` of the object. BREAKING CHANGE: an object without the WebdriverIO v10 `wdio.kind` brand is not a browser or a mock. An array of such fake mocks is not checked per instance anymore. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The matchers identify WebdriverIO objects by the WebdriverIO v10 `wdio.kind` brand, so a hand-made fake needs it. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In WebdriverIO v10, a not-awaited `$$()` is the element list itself, not a Promise, and its `length` is a Promise until the list is loaded. A not-awaited multi-remote `$$()` was used as is, so it counted 0 elements: `toBeElementsArrayOfSize(0)` passed on a page with elements, and the element matchers failed with `wait: 0`. `awaitElementOrArray()` and `awaitElementArray()` now also await an element list that still has `then` (`isNotAwaitedElementList()`). Awaiting it gives the same list, so a refetch still updates the user's list in place. The list mocks load later, like the `load()` of WebdriverIO v10, and the multi-remote `$$()` mock is a not-awaited list, not a Promise. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The matchers call `getElement()` on an element, so the example threw a TypeError. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…driverIO 10.0.0 `multiRemoteBrowser.$()` and the `$()` of a multi-remote element are now Promises with the `element` brand and `wdio.chainable`, and the `$$()` of a multi-remote element is a list that is not loaded yet. Before they are loaded, the list mocks now throw on `for...of` and spread, as WebdriverIO 10.0.0 does. New tests cover these not-awaited multi-remote values in `toBeDisplayed` and `toBeElementsArrayOfSize`. Without the await of a not-loaded list, the `$$()` tests fail. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d element
In WebdriverIO v10, a chained `$('a').$$('b')` or a custom `$$` command
before `await` is a Promise proxy with the `element-array` brand. Only
`Array.isArray()` keeps `isStrictlyElementArray()` from accepting it, and
no test failed when it was removed: a new test now does.
The comment of `isElement()` now says that an item of an awaited `$$()`
and the result of `getElement()` are elements too.
Refs webdriverio#2296
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`awaitElementOrArray()` and `awaitElementArray()` checked for a Promise or for a list with the `element-array` brand and a `then` before they awaited the received value. A plain `await` is simpler and does the same: it gives back a value that is not a thenable, and it awaits each pending value, a not-awaited `$$()` included. `isNotAwaitedElementList()` is not used anymore, so it is removed. Refs webdriverio#2296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`isElement()`, `isMultiRemoteElement()` and the parent walk of the
failure message excluded a not-awaited element with the
`wdio.chainable` flag. In WebdriverIO 10.0.0, each element with that
flag is a Promise, and a loaded element has no `then`. So
`getLoadedWdioKind()`, the same rule as `getLoadedWdioKind()` of
`@wdio/utils`, gives the brand of a value only when it has no `then`.
The same rule as the plain `await` now applies everywhere, and
`isChainable()` and `WDIO_CHAINABLE` are removed from `src/`. The mocks
still set the flag, as WebdriverIO 10.0.0 does.
A new test checks that the failure message stops its walk at a parent
that is not loaded (a chained `$('form').$('input')` before `await`).
It fails if the walk accepts that parent.
Refs webdriverio#2296
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dprevost-LMI
marked this pull request as ready for review
October 6, 2026 10:59
Contributor
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2296
Proposed changes
WebdriverIO v10 brands its objects with
Symbol.for('wdio.kind'):browser,browsing-context,element,element-arrayormock. The matchers now identify objects by this brand, not by their shape ('getElements' in,parent,calls) or their constructor name.src/util/wdioKind.ts:getWdioKind()reads the brand withSymbol.for(), without a dependency on@wdio/utils.getLoadedWdioKind()gives the brand only when the value has nothen, the same rule as in@wdio/utils: a not-awaited$()is a Promise with theelementbrand, and a not-awaited$$()is a list with theelement-arraybrand and athen.isElement(),isStrictlyElementArray(),isMultiRemoteElement(),isMultiRemoteElementArray(),isBrowser(),isMultiRemoteBrowser(),isMock()andisMultiRemoteMock()keep their names and signatures, so their callers do not change.isStrictlyElementArray()also checksArray.isArray(): a chained$('a').$$('b')or a custom$$command beforeawaitis a Promise with the same brand.BrowsingContexthas the constructor nameBrowser, soisBrowser()accepted it before. It still does, so the failure message does not change. [v8] feat: Support a WebdriverIO v10BrowsingContext(tab, window, frame) in the browser matchers #2298 decides this (TODO(#2298)).Fix: false pass with a not-awaited multi-remote
$$()In v10, a not-awaited
$$()is the list itself, not a Promise. Until the list is loaded, it hasthen, and itslengthis a Promise. The oldinstanceof Promisecheck did not await it, so a not-awaited multi-remote$$()counted 0 elements:toBeElementsArrayOfSize(0)passed on a page with elements, and the element matchers failed withwait: 0.awaitElementOrArray()andawaitElementArray()now alwaysawaitthe received value.awaitgives back a value that is not a thenable, and it gives the same list, so a refetch still updates the user's list in place. This bug was there before this PR. WebdriverIO docs issue: webdriverio/webdriverio#15935.Unit test mocks
The mocks now match WebdriverIO 10.0.0. Each mock has its brand. A not-awaited
$()is a Promise with theelementbrand. A not-awaited$$(), multi-remote or not, is a list that loads later and throws onfor...ofuntil it is loaded. The tests that expected the v9 shape (a$$()that is a Promise) now expect the v10 shape.Breaking change
An object without the
wdio.kindbrand is not a browser, element, element list or mock. Real WebdriverIO v10 objects have the brand. A hand-made fake in a user's own unit tests needs it: seedocs/Migrations.md, "WebdriverIO objects are identified by their brand". An array of unbranded fake mocks now throws aTypeError, as any other value that is not a mock.How you tested
pnpm run checks:all: 2574 passed, 40 skipped (the same skips as onmain).parentandgetElement, failed before the brand change.$$()tests (toBeElementsArrayOfSize2 and 0,toBeDisplayedwithwait: 0) failed before the fix.Array.isArray()inisStrictlyElementArray(), the chained$().$$()test fails. If the failure message accepts a parent that is not loaded, the parent-walk test fails.pnpm run test:types: jasmine 37, jasmine-global-expect-async 40, jest 61 and mocha 123 passed.ts:declarationsandts:packagepassed.pnpm run playgrounds:checks:allpassed for the 5 playgrounds: browser-runner 1/1, jasmine 7/7, jest 39/39, mocha 7/7, and multi-remote 6/6 (Chrome + Firefox). The new multi-remote test "should count the elements of a not-awaited $$() at once, without a retry" failed with the build before the fix, and passes with it.🤖 Generated with Claude Code