Skip to content

feat: ElementNotFound explains what the page has instead - #5798

Open
DavertMik wants to merge 2 commits into
4.xfrom
feat/playwright-explain-not-found
Open

DavertMik wants to merge 2 commits into
4.xfrom
feat/playwright-explain-not-found

Conversation

@DavertMik

@DavertMik DavertMik commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

When a locator matches nothing, ElementNotFound now adds hints about what the page has instead:

Field "#emial" was not found by text|CSS|XPath
  similar on page: #email, #email-label, #email-wrapper
Clickable element "#first-link" was not found by text|CSS|XPath
  found 1 match inside iframe [name="content"], use within({ frame: '[name="content"]' })

Hints cover:

  • a match inside a same-origin iframe, with the within({ frame }) call to use
  • matching elements excluded by the current within() context, or hidden ones
  • up to 3 similar ids, classes or element names (fuse.js)

Design

All the logic lives in lib/helper/errors/ElementNotFound.js:

  • ElementNotFound becomes an Error subclass.
  • When it's created, it runs one in-page script through the active web helper's executeScript (found via the container), capped at 500 ms.
  • It appends the hints to its own message and stack, about 10 ms after the throw, while the page is still open.
  • err.explain() returns that pending work for callers that need to wait. MCP run_code awaits it (one line in bin/mcp-server.js).
  • Errors from the page script, a closed page, or no web helper mean no hints. The original error is never masked.

No helper, hook, recorder, step or CLI changes. It works as-is for Playwright, Puppeteer, WebDriver and CDPBrowser (and Lightpanda, Obscura, Kitesurf, Moli, Appium through inheritance).

Not named fetchDetails, on purpose: the CLI awaits fetchDetails() after mocha's end, and nothing waits for that work to finish. With it, the whole failure report was lost under run-workers. The same race already affects MultipleElementsFound on 4.x: its failure details don't print, even in a single-process run. That's left for a separate fix.

Limitations

  • Cross-origin iframes are not scanned.
  • The first line of the error is unchanged. The hints are extra lines below it.

Tested locally

  • test/unit/helper/element_not_found_test.js: 9 passing (message format, hints from page info, page script failure, unanswered page).
  • #explainNotFound in the shared test/helper/webapi.js: Playwright 4/4, Puppeteer 4/4, CDPBrowser (Chrome) 4/4. Plus one Playwright hidden-match test with visibleLocator.
  • A real codecept run and run-workers 2 both print the hint once, with a clean stack.
  • Full unit suite: 950 passing. npm run def and eslint pass.
  • WebDriver and the wider helper suites: CI. The local regression greps were stopped by a low-memory condition before they finished, so they have no result.

🤖 Generated with Claude Code

@DavertMik DavertMik changed the title feat(playwright): explain element not found errors feat: explain element not found errors in all web helpers Nov 13, 2026
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
DavertMik force-pushed the feat/playwright-explain-not-found branch from 06ec8c7 to 4197668 Compare November 13, 2026 10:15
@DavertMik DavertMik changed the title feat: explain element not found errors in all web helpers feat: ElementNotFound explains what the page has instead Nov 13, 2026
…ame hint

ElementNotFound is now an Error subclass, so Scenario().fails() and
throws(new Error()) must match subclasses instead of comparing
constructor names exactly. Lightpanda returns no iframe contentDocument,
so the iframe hint test is skipped there. Restores the
MultipleElementsFound test that still uses fetchDetails().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant