Repository navigation
Conversation
ElementNotFound becomes an Error subclass that explains itself. Right
after it is created it runs one script in the page through the active
web helper's executeScript and appends hints to its message and stack:
a match inside a same-origin iframe (with the within({ frame }) call),
matches excluded by within() or hidden, and up to 3 similar ids, classes
or element names. explain() returns the pending work for callers that
need to wait, and MCP run_code awaits it.
No helper, hook or core change: works for Playwright, Puppeteer,
WebDriver, CDPBrowser and their subclasses as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DavertMik
force-pushed
the
feat/playwright-explain-not-found
branch
from
November 13, 2026 10:15
06ec8c7 to
4197668
Compare
This branch has not been deployed
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.
What / why
When a step fails with
ElementNotFound, the error now says what the page has instead. This works in every web helper: Playwright, Puppeteer, WebDriver and CDPBrowser. Lightpanda, Obscura, Kitesurf and Moli inherit it from CDPBrowser.Hints are collected only after a lookup has already failed, so passing steps cost nothing.
Hints, in order (lines with nothing to say are left out):
within({ frame }).within()context.visibleLocatoris in effect. Only Playwright has that option.#id/.classlocators: page ids or class namesThe first line of the message doesn't change. Hints are appended as indented lines.
Design
lib/helper/extras/NotFoundExplainer.jsis the one shared module:NotFoundExplainer.collectis a plain in-page function. It returns the match count, hidden count, same-origin iframe counts, ids, classes and element names.explain()races the work against a 300 ms timer that is cleared infinally. Any error yields[], so the original error is never masked.format()ranks the candidates with fuse.js (searchWithFusejs) and builds the hint lines.Each helper has a small
_explainNotFound(locator)that runs the collector through its own raw evaluate in the current document and passes its ownwithin()andvisibleLocatorstate:frame.evaluateframe.evaluatebrowser.executeRuntime.evaluateNone of them goes through
I.*or the recorder.In Playwright, Puppeteer and WebDriver, the module-level
assertElementExistsbecame the async helper method_assertElementExists, and every call site awaits it. Directthrow new ElementNotFound(...)sites pass hints too.ElementNotFoundtakes an optional 4thhintsargument (string[]).The option is
explainNotFound(boolean, defaulttrue), with the same JSDoc@propin each helper's config.It's documented once, in
docs/locators.md("When an element is not found").What each helper supports
within({ frame })can't be suggestedAppium extends WebDriver, but its own
_validateConfigdoesn't merge WebDriver defaults.explainNotFoundis therefore off for Appium unless it is set explicitly, and native apps are unaffected.Example output
Config
Measured cost
Measured with local headless chromium. Each figure is the median of 9 calls to
_explainNotFoundon a warm page, in ms:/form/example4/form/field/loginA whole failing
fillField('#emial')step in Playwright takes about 1 ms with hints off and about 2 ms with hints on. That is far below the 300 ms cap, so the plan's "skip ranking under autoRetries" fallback isn't needed.Tests
Shared
#explainNotFoundcases intest/helper/webapi.js:The iframe case has a documented skip for CDPBrowser (no iframe support).
Playwright-only case (hidden matches with
visibleLocator) intest/helper/Playwright_test.js.hintscase intest/unit/helper/element_not_found_test.js.Ran locally, each with
--grep explainNotFound:Regression runs:
--grep "explainNotFound|#fillField|#click|within|iframe|visibleLocator|switchTo|Frame": 148 passing.--grep "switchTo|within|#focus": 41 passing.npm run def,npm run dtslintandnpx eslinton the changed files.WebDriver runs in CI only (it needs Selenium).
🤖 Generated with Claude Code