ReviewOS

stacks/very-happy-dom

Locator.evaluate(), evaluateAll(), allTextContents() and allInnerTexts() are missing

#1607
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

Locator has no way to run code against the element it resolved:

const loc = page.locator('.chart')
typeof loc.evaluate          // 'undefined'
typeof loc.evaluateAll       // 'undefined'
typeof loc.allTextContents   // 'undefined'
typeof loc.allInnerTexts     // 'undefined'

page.evaluate exists and already takes an argument (BrowserFrame.ts:246), so the element has nowhere to ride in:

// Playwright
const rows = await page.locator('tbody tr').evaluateAll(els => els.map(e => e.dataset.id))

// here — drop out of the locator API entirely
const rows = (await page.locator('tbody tr').elementHandles()).map(e => e.dataset.id)

wildloop's specs use .evaluate( 18 times.

Why it matters

elementHandles() is the documented escape hatch and it does work, so this is a convenience gap rather than a capability gap — but it is the gap that makes a ported spec stop looking like a Playwright spec, and every rewritten line is a line that can be rewritten wrong.

allTextContents() is the one that bites hardest, because the obvious hand-rolled version is subtly different: Playwright normalises whitespace in allInnerTexts() and does not in allTextContents(), and a spec asserting against a hand-mapped array quietly starts depending on the raw text nodes.

Proposed scope

  • locator.evaluate(fn, arg) — resolve one element (strict, as the other single-element actions are) and pass it as the first argument.
  • locator.evaluateAll(fn, arg) — pass the whole array, no strictness check, which is the point of it.
  • locator.allTextContents() and locator.allInnerTexts(), matching Playwright on whitespace: raw for the former, normalised for the latter. Locator.ts already has a normalize() helper used by the text matchers — reuse it rather than adding a second definition.
  • page.$eval / page.$$eval are the same thing over a selector and come almost free once the above exist.

BrowserFrame.evaluate compiles the function with new Function(...) and a with (window) wrapper, so the element has to be threaded through as the argument rather than closed over — worth a test asserting the element reaching the callback is the same object elementHandle() returns, not a copy.

Acceptance

  • locator.evaluate(el => el.tagName) returns the tag of the resolved element, and throws the strict-mode error on two matches.
  • locator.evaluateAll(els => els.length) agrees with count().
  • allInnerTexts() collapses runs of whitespace and allTextContents() does not, asserted against the same markup.

Found while assessing whether wildloop's Playwright specs could port here.

Sign in to comment on this issue.