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()andlocator.allInnerTexts(), matching Playwright on whitespace: raw for the former, normalised for the latter.Locator.tsalready has anormalize()helper used by the text matchers — reuse it rather than adding a second definition.page.$eval/page.$$evalare 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 withcount().allInnerTexts()collapses runs of whitespace andallTextContents()does not, asserted against the same markup.
Found while assessing whether wildloop's Playwright specs could port here.