microsoft / microsoft/playwright

[Bug]: page.evaluate with exposeFunctions and world: "utility" fails with an internal TypeError

Open
#42,686 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs-triage-mac
Dominant language
TypeScript
Stars
96.3k
Forks
6.5k
Avg merge
1d 6h
Merged PRs (30d)
180

Description

Filing this as an issue as asked in #42649.

Version

1.64.0-next. Originally reproduced at aeebee1d2; packages/playwright-core/src/client/jsHandle.ts has not been touched since that commit, so it still applies.

Steps to reproduce

exposeFunctions and the world option added in #42635 cannot be combined, but passing both crashes inside the injected code rather than saying so.

await page.evaluate(async ({ cb }) => await cb(17), { cb: x => x * 2 },
    { exposeFunctions: true, world: 'utility' });
// page.evaluate: TypeError: Cannot read properties of undefined (reading 'callBinding')

Same error from frame.evaluate and locator.evaluate. Reproduced on chromium, firefox and webkit.

Why it happens

PageBinding.createInitScript installs the bindings controller as an init script, and init scripts only run in the main world, so globalThis[kBindingsControllerProperty] is undefined in the utility world. Runtime.addBinding is registered without a world, so the transport is there but the controller that dispatches through it is not.

That also makes a documented claim false. docs/src/api/params.md says an exposed function is "technically accessible from all frames and worlds of the page", in both js-evaluate-expose-functions and js-init-script-expose-functions:

await page.exposeFunction('f', x => x * 2);
await page.evaluate(() => window.f?.(21) ?? 'NOT PRESENT');                                   // 42
await page.evaluate(() => window.f?.(21) ?? 'NOT PRESENT', undefined, { world: 'utility' });  // 'NOT PRESENT'
Possible directions

Either make bindings reachable from the utility world, or reject the combination with a clear error the way JSHandleDispatcher.evaluateExpression already does for a world it cannot serve. Either way the two doc sentences want correcting, since they promise worlds today.

I had a patch for the second option in #42649 with the guard in assertEvaluateOptions, which every evaluate entry point already calls, plus the doc correction and one test. Happy to reopen that, rework it toward the first option, or leave it entirely, whichever you prefer. I have no view on which layer you want this solved at.

I am a freshman in college trying to make myself useful on real projects, so if I have misread the intent here just say and I will drop it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/playwright-core/src/client/jsHandle.ts and the assertEvaluateOptions path used by page, frame, and locator evaluate calls; compare it with JSHandleDispatcher.evaluateExpression. Reproduce the utility-world case across the mentioned browsers, then review docs/src/api/params.md and the existing test added in the earlier patch. Done means the chosen behavior is consistent, clearly reported, documented accurately, and covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, documentation, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.