fix(@wdio/utils, webdriverio): run the command hooks once for overwritten commands and chained queries by plum117 · Pull Request #16128 · webdriverio/webdriverio · GitHub
Skip to content

fix(@wdio/utils, webdriverio): run the command hooks once for overwritten commands and chained queries - #16128

Draft
plum117 wants to merge 1 commit into
webdriverio:mainfrom
plum117:fix/command-hooks-overwrite-chain
Draft

plum117 wants to merge 1 commit into
webdriverio:mainfrom
plum117:fix/command-hooks-overwrite-chain

Conversation

@plum117

@plum117 plum117 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

Follow-up of #16096: overwritten commands and queries chained on a promise still ran the command hooks 2 to 4 times, and the override got the original command as args[0] of its hooks.

before hook calls, headless Chrome (BiDi and Classic give the same result):

Case main This PR
$('#box').$('span') $ ×3: #box, span, span ×2: #box, span
$('#s').parentElement() / nextElement() / shadow$() ×2 ×1
$('#box').$('span').$('b') ×5 ×3
browser.overwriteCommand('getTitle', …) ×2: [<fn>], [] ×1: []
2 stacked browser overwrites ×3 ×1
override that calls the original twice ×3 ×1
browser.overwriteCommand('$', …) (as the Appium selector optimizer) ×2: [<fn>, '#s'], ['#s'] ×1: ['#s']
elem.overwriteCommand('getTagName', …) ×2 ×1
custom command that calls an overwritten one twice 5 3
overwriteCommand('getText', …, { attachToElement: true }) ×2 ×1
2 stacked attachToElement overrides, also on a $$() item ×3 ×1
attachToElement override that does not call the original ×1 with [<fn>] ×1 with []
multi-remote attachToElement override (each instance) ×2 ×1
Unchanged: $$ chains and overrides, $$()[i], browsing context overrides, multi-remote browser overwrites and chains ×1 ×1

How

  • overwriteCommand (browser, element, multi-remote): the override is wrapped once and gets the user's arguments. Its original is the command without its wrapper, from lift (added and overwritten commands) or from the client's raw commands. The original of a query still chains (chainQuery), so orig(selector).getText() keeps working.
  • attachToElement overrides are applied to the element commands before the element wraps them, so the element wraps the override once, as browsing contexts already do (composeOverrides).
  • List queries ($$, custom$$, shadow$$, react$$) keep the current model: their hooks run when the original loads the list.
  • Chain proxy: a single-element query on a promise chains the element's own command (chainQuery) instead of wrapping it again.
  • Each client keeps one reference to its raw commands (__rawProperties__), outside __propertiesObject__: an element deep-copies __propertiesObject__ on each $(), so a raw command on each descriptor made $() 8–11 % slower.

Changed behavior

  • The hooks of an overwritten command run once, with the arguments of the call; they no longer get the original command.
  • An element command overwritten with attachToElement runs after the implicit wait (and again after a stale element retry), like an element command from addCommand. Before, it ran before the wait.
  • The Appium selector optimizer measures a $ / $$ call once, with its selector (before, it ignored the pair with the function and measured the original).

Docs: CustomCommands.md, the v10 migration guide and the wdio-v10-migration skill.

How you tested

Check Result
overwriteCommandHooks.test.ts (18), multi-remote (3), monad.test.ts 18 fail on main, all pass with the PR; the 3 $$ guards pass on both. The 3 tests that chain on the original of $ also failed on a first version of this PR, where the original did not chain
e2e command-hooks.test.ts (Chrome Classic and BiDi) 20 of 20; the 6 new ones fail on main
Headless Chrome matrix, 26 cases, BiDi and Classic table above
Unit webdriverio, @wdio/utils, webdriver, @wdio/runner, @wdio/cli, @wdio/session, @wdio/appium-service, @wdio/browser-runner 5790 pass
Smoke, each of the 62 suites on its own pass (the full parallel run stops at headlessFlag on main too, locally)
test:component 2 Vue $$() matcher tests fail in Chrome Canary 158, the same on main
bench:runner mocha-lists-quiet, 4 rounds in turns with main wall 686–705 ms on both, execution 90–94 (main) / 90–102 ms (PR): noise
$() and element commands with hooks, in turns with main $() min 0.445 / 0.451 ms, getText 0.065 / 0.065 ms
test:typings, d.ts, test:browser-lib, oxlint, docs:generate:en, docs check pass

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Documentation update (improvements to the project's docs)

Checklist

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have added the necessary documentation (if appropriate)

Backport Request

  • This change is solely for v10 and doesn't need to be back-ported

Reviewers: @webdriverio/project-committers

…tten commands and chained queries

An overwritten command ran its hooks for the override, with the original
command as first argument, and again for the original. A query chained on
a promise ran them in the chain and again in the element.

- overwriteCommand: the override runs the hooks once, with the arguments of
  the call, and gets the original without its wrapper (a query still
  chains). Element overrides (attachToElement) are applied to the commands
  before the element wraps them, so they also run after the implicit wait.
  List queries keep their hooks on the original: they run when it loads.
- The chain proxy builds single-element queries without wrapCommand.
- Each client keeps one reference to its commands without the wrapper
  (__rawProperties__), outside __propertiesObject__, which an element
  deep-copies.
@wdio-bot wdio-bot added the PR: Bug Fix 🐛 PRs that contain bug fixes label Oct 18, 2026
@pkg-pr-new

pkg-pr-new Bot commented Oct 18, 2026

Copy link
Copy Markdown

This branch has not been deployed

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

Labels

PR: Bug Fix 🐛 PRs that contain bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants