From a5296408ff3fc1ccc32b7c3e7e30590ae8b67abb Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 20 Jul 2026 13:31:11 -0700 Subject: [PATCH 1/2] Revert "docs(evaluate): clarify exposeFunctions scope and lifetime (#41856)" This reverts commit 37b39b798589fcca525cb71ffa1a3191a6d2fd91. --- docs/src/api/params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index bdce5366586ce..6a92a8474ab89 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -588,7 +588,7 @@ Function to be evaluated in the page context. * langs: js - `exposeFunctions` <[boolean]> -When set to `true`, functions passed inside [`param: arg`] are exposed in the page and can be called from the page function. Calling one returns a [Promise] of its result. Under the hood, each function is exposed via [`method: Page.exposeFunction`], so it is technically accessible from all frames and worlds of the page. Exposed functions are cleared upon the top-level navigation. Defaults to `false`, in which case functions are not serializable and passing one throws an error. +When set to `true`, functions passed inside [`param: arg`] are exposed in the page and can be called from the page function. Calling one returns a [Promise] of its result. The page-side functions are scoped to the execution context they were passed to and disappear when the page navigates. Defaults to `false`, in which case functions are not serializable and passing one throws an error, as before. ## js-evalonselector-pagefunction * langs: js From 08715d40dc8ae9900a1d253c225e0244a2f3d25b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 20 Jul 2026 13:31:21 -0700 Subject: [PATCH 2/2] Revert "feat(evaluate): allow passing functions as evaluate arguments (#41801)" This reverts commit 4e3c4278d2a32a9f5c32eecf0fe51535a53019ff. --- docs/src/api/class-frame.md | 6 - docs/src/api/class-jshandle.md | 6 - docs/src/api/class-locator.md | 6 - docs/src/api/class-page.md | 6 - docs/src/api/params.md | 6 - packages/injected/src/bindingsController.ts | 36 ++-- .../isomorphic/utilityScriptSerializers.ts | 16 -- packages/playwright-client/types/types.d.ts | 185 ++++++++--------- .../src/client/channelOwner.ts | 10 - .../playwright-core/src/client/channels.d.ts | 3 +- packages/playwright-core/src/client/frame.ts | 23 +-- .../playwright-core/src/client/jsHandle.ts | 41 +--- .../playwright-core/src/client/locator.ts | 9 +- packages/playwright-core/src/client/page.ts | 28 +-- .../playwright-core/src/server/channels.d.ts | 3 +- .../src/server/dispatchers/pageDispatcher.ts | 2 +- packages/playwright-core/src/server/page.ts | 19 +- packages/playwright-core/types/types.d.ts | 185 ++++++++--------- .../src/injected/serializers.ts | 32 ++- packages/playwright-ct-core/src/mount.ts | 18 +- .../playwright-ct-core/types/component.d.ts | 2 + packages/protocol/spec/page.yml | 1 - packages/protocol/spec/serialized.yml | 3 - packages/protocol/src/serializers.ts | 7 +- packages/protocol/src/structs.d.ts | 1 - packages/protocol/src/validator.ts | 2 - tests/page/page-evaluate-callback.spec.ts | 193 ------------------ utils/generate_types/overrides.d.ts | 28 +-- 28 files changed, 266 insertions(+), 611 deletions(-) delete mode 100644 tests/page/page-evaluate-callback.spec.ts diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index 6a1242084837b..770da8c509ced 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -745,9 +745,6 @@ await bodyHandle.DisposeAsync(); Optional argument to pass to [`param: expression`]. -### option: Frame.evaluate.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: Frame.evaluateHandle * since: v1.8 - returns: <[JSHandle]> @@ -860,9 +857,6 @@ await resultHandle.DisposeAsync(); Optional argument to pass to [`param: expression`]. -### option: Frame.evaluateHandle.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: Frame.fill * since: v1.8 * discouraged: Use locator-based [`method: Locator.fill`] instead. Read more about [locators](../locators.md). diff --git a/docs/src/api/class-jshandle.md b/docs/src/api/class-jshandle.md index 5a883bf8892d6..9f38b18447016 100644 --- a/docs/src/api/class-jshandle.md +++ b/docs/src/api/class-jshandle.md @@ -96,9 +96,6 @@ Assert.AreEqual("10 retweets", await tweetHandle.EvaluateAsync("node => node.inn Optional argument to pass to [`param: expression`]. -### option: JSHandle.evaluate.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: JSHandle.evaluateHandle * since: v1.8 - returns: <[JSHandle]> @@ -126,9 +123,6 @@ See [`method: Page.evaluateHandle`] for more details. Optional argument to pass to [`param: expression`]. -### option: JSHandle.evaluateHandle.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: JSHandle.getProperties * since: v1.8 - returns: <[Map]<[string], [JSHandle]>> diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index c252b318fdd91..f9bc16b0d6f94 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1123,9 +1123,6 @@ Console.WriteLine(result); // prints "myId text 56" Optional argument to pass to [`param: expression`]. -### option: Locator.evaluate.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ### option: Locator.evaluate.timeout * since: v1.14 * langs: python, java, csharp @@ -1225,9 +1222,6 @@ See [`method: Page.evaluateHandle`] for more details. Optional argument to pass to [`param: expression`]. -### option: Locator.evaluateHandle.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ### option: Locator.evaluateHandle.timeout * since: v1.14 * langs: python, java, csharp diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 04d0932668163..4db5da98e309c 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -1587,9 +1587,6 @@ await bodyHandle.DisposeAsync(); Optional argument to pass to [`param: expression`]. -### option: Page.evaluate.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: Page.evaluateHandle * since: v1.8 - returns: <[JSHandle]> @@ -1699,9 +1696,6 @@ await resultHandle.DisposeAsync(); Optional argument to pass to [`param: expression`]. -### option: Page.evaluateHandle.exposeFunctions = %%-js-evaluate-expose-functions-%% -* since: v1.62 - ## async method: Page.exposeBinding * since: v1.8 - returns: <[Disposable]> diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 6a92a8474ab89..58b7cb296cd37 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -584,12 +584,6 @@ to a function, the function is automatically invoked. Function to be evaluated in the page context. -## js-evaluate-expose-functions -* langs: js -- `exposeFunctions` <[boolean]> - -When set to `true`, functions passed inside [`param: arg`] are exposed in the page and can be called from the page function. Calling one returns a [Promise] of its result. The page-side functions are scoped to the execution context they were passed to and disappear when the page navigates. Defaults to `false`, in which case functions are not serializable and passing one throws an error, as before. - ## js-evalonselector-pagefunction * langs: js - `pageFunction` <[function]\([Element]\)|[string]> diff --git a/packages/injected/src/bindingsController.ts b/packages/injected/src/bindingsController.ts index 5066e75ee4bcb..7f58b482a5f00 100644 --- a/packages/injected/src/bindingsController.ts +++ b/packages/injected/src/bindingsController.ts @@ -40,32 +40,28 @@ export class BindingsController { this._globalBindingName = globalBindingName; } - addBinding(bindingName: string, noGlobal?: boolean) { + addBinding(bindingName: string) { const data: BindingData = { callbacks: new Map(), lastSeq: 0, removed: false, }; this._bindings.set(bindingName, data); - if (!noGlobal) - (this._global as any)[bindingName] = (...args: any[]) => this.callBinding(bindingName, ...args); - } - - callBinding(bindingName: string, ...args: any[]): Promise { - const data = this._bindings.get(bindingName); - if (!data || data.removed) - throw new Error(`binding "${bindingName}" has been removed`); - const seq = ++data.lastSeq; - const promise = new Promise((resolve, reject) => data.callbacks.set(seq, { resolve, reject })); - const serializedArgs = []; - for (let i = 0; i < args.length; i++) { - serializedArgs[i] = serializeAsCallArgument(args[i], v => { - return { fallThrough: v }; - }); - } - const payload: BindingPayload = { name: bindingName, seq, serializedArgs }; - (this._global as any)[this._globalBindingName](JSON.stringify(payload)); - return promise; + (this._global as any)[bindingName] = (...args: any[]) => { + if (data.removed) + throw new Error(`binding "${bindingName}" has been removed`); + const seq = ++data.lastSeq; + const promise = new Promise((resolve, reject) => data.callbacks.set(seq, { resolve, reject })); + const serializedArgs = []; + for (let i = 0; i < args.length; i++) { + serializedArgs[i] = serializeAsCallArgument(args[i], v => { + return { fallThrough: v }; + }); + } + const payload: BindingPayload = { name: bindingName, seq, serializedArgs }; + (this._global as any)[this._globalBindingName](JSON.stringify(payload)); + return promise; + }; } removeBinding(bindingName: string) { diff --git a/packages/isomorphic/utilityScriptSerializers.ts b/packages/isomorphic/utilityScriptSerializers.ts index 012fa00e14e61..6be0773adb097 100644 --- a/packages/isomorphic/utilityScriptSerializers.ts +++ b/packages/isomorphic/utilityScriptSerializers.ts @@ -16,13 +16,6 @@ type TypedArrayKind = 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64'; -// Name prefix of the page bindings backing the functions passed to evaluate() -// as arguments. Only functions carrying this prefix serialize as { fn }, -// arbitrary functions are dropped as before. -export const kFunctionBindingPrefix = '__pw_fn_'; - -export const kBindingsControllerProperty = '__playwright__binding__controller__'; - export type SerializedValue = undefined | boolean | number | string | { v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } | @@ -35,7 +28,6 @@ export type SerializedValue = { o: { k: string, v: SerializedValue }[], id: number } | { ref: number } | { h: number } | - { fn: string } | { ta: { b: string, k: TypedArrayKind } } | { ab: { b: string } }; @@ -185,11 +177,6 @@ export function parseEvaluationResultValue(value: SerializedValue, handles: any[ } if ('h' in value) return handles[value.h]; - if ('fn' in value) { - const name = value.fn; - // eslint-disable-next-line no-restricted-globals - return (...args: any[]) => (globalThis as any)[kBindingsControllerProperty].callBinding(name, ...args); - } if ('ta' in value) return base64ToTypedArray(value.ta.b, typedArrayConstructors[value.ta.k]); if ('ab' in value) @@ -313,7 +300,4 @@ function innerSerialize(value: any, handleSerializer: (value: any) => HandleOrVa return { o, id }; } - - if (typeof value === 'function' && value.name.startsWith(kFunctionBindingPrefix)) - return { fn: value.name }; } diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 0b3c58906b5b1..8c1ace60a3284 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -87,15 +87,14 @@ export interface Page { * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a - * [Promise], then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait - * for the promise to resolve and return its value. + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], + * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for + * the promise to resolve and return its value. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a * non-[Serializable] value, then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -119,7 +118,7 @@ export interface Page { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate): + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): * * ```js * const bodyHandle = await page.evaluateHandle('document.body'); @@ -132,23 +131,21 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; /** * Returns the value of the * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a - * [Promise], then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait - * for the promise to resolve and return its value. + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], + * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for + * the promise to resolve and return its value. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a * non-[Serializable] value, then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -172,7 +169,7 @@ export interface Page { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate): + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): * * ```js * const bodyHandle = await page.evaluateHandle('document.body'); @@ -185,9 +182,8 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; /** * Returns the value of the @@ -195,17 +191,16 @@ export interface Page { * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * is that - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function passed to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns a [Promise], then - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * would wait for the promise to resolve and return its value. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * a [Promise], then + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would + * wait for the promise to resolve and return its value. * * **Usage** * @@ -221,7 +216,7 @@ export interface Page { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): * * ```js * const aHandle = await page.evaluateHandle(() => document.body); @@ -233,26 +228,24 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; /** * Returns the value of the * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression) invocation as a * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * is that - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function passed to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns a [Promise], then - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * would wait for the promise to resolve and return its value. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * a [Promise], then + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would + * wait for the promise to resolve and return its value. * * **Usage** * @@ -268,7 +261,7 @@ export interface Page { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): * * ```js * const aHandle = await page.evaluateHandle(() => document.body); @@ -280,9 +273,8 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; /** * Adds a script which would be evaluated in one of the following scenarios: @@ -5794,15 +5786,14 @@ export interface Frame { * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a [Promise], then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) would - * wait for the promise to resolve and return its value. + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * [Promise], then [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) + * would wait for the promise to resolve and return its value. * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a non-[Serializable] value, then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * non-[Serializable] value, then + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -5822,7 +5813,7 @@ export interface Frame { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate): + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate): * * ```js * const bodyHandle = await frame.evaluateHandle('document.body'); @@ -5835,23 +5826,21 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a [Promise], then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) would - * wait for the promise to resolve and return its value. + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * [Promise], then [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) + * would wait for the promise to resolve and return its value. * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a non-[Serializable] value, then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * non-[Serializable] value, then + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -5871,7 +5860,7 @@ export interface Frame { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate): + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate): * * ```js * const bodyHandle = await frame.evaluateHandle('document.body'); @@ -5884,9 +5873,8 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; /** * Returns the return value of @@ -5894,16 +5882,15 @@ export interface Frame { * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) - * is that - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) is + * that [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function, passed to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), * returns a [Promise], then - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * would wait for the promise to resolve and return its value. * * **Usage** @@ -5920,7 +5907,7 @@ export interface Frame { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): * * ```js * const aHandle = await frame.evaluateHandle(() => document.body); @@ -5934,25 +5921,23 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression) as a * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) - * is that - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) is + * that [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function, passed to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), * returns a [Promise], then - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * would wait for the promise to resolve and return its value. * * **Usage** @@ -5969,7 +5954,7 @@ export interface Frame { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): * * ```js * const aHandle = await frame.evaluateHandle(() => document.body); @@ -5983,9 +5968,8 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; /** * **NOTE** Use locator-based [frame.locator(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-locator) @@ -11748,8 +11732,7 @@ export interface Worker { /** * JSHandle represents an in-page JavaScript object. JSHandles can be created with the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * method. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) method. * * ```js * const windowHandle = await page.evaluateHandle(() => window); @@ -11762,8 +11745,8 @@ export interface Worker { * * JSHandle instances can be used as an argument in * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector), - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) * methods. */ export interface JSHandle { @@ -11787,9 +11770,8 @@ export interface JSHandle { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). @@ -11810,9 +11792,8 @@ export interface JSHandle { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; /** * Returns the return value of @@ -11828,15 +11809,13 @@ export interface JSHandle { * If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would * wait for the promise to resolve and return its value. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression) @@ -11851,15 +11830,13 @@ export interface JSHandle { * If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would * wait for the promise to resolve and return its value. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; /** * Returns a JSON representation of the object. If the object has a `toJSON` function, it **will not be called**. @@ -11924,8 +11901,7 @@ export interface JSHandle { * * ElementHandle instances can be used as an argument in * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) - * and [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) - * methods. + * and [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) methods. * * The difference between the [Locator](https://playwright.dev/docs/api/class-locator) and ElementHandle is that the * ElementHandle points to a particular element, while [Locator](https://playwright.dev/docs/api/class-locator) @@ -13867,7 +13843,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression). * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; /** * Execute JavaScript code in the page, taking the matching element as an argument, and return a * [JSHandle](https://playwright.dev/docs/api/class-jshandle) with the result. @@ -13893,15 +13869,14 @@ export interface Locator { * If [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression) throws * or rejects, this method throws. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression). * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; /** * Execute JavaScript code in the page, taking all matching elements as an argument. * diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index f6ed9d166f192..e2f2856b53558 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -116,16 +116,6 @@ export abstract class ChannelOwner | undefined { - let parent: ChannelOwner | undefined = this._parent; - while (parent) { - if (parent._type === type) - return parent; - parent = parent._parent; - } - return undefined; - } - _dispose(reason: 'gc' | undefined) { // Clean up from parent and connection. if (this._parent) diff --git a/packages/playwright-core/src/client/channels.d.ts b/packages/playwright-core/src/client/channels.d.ts index ee239a9c51efc..e24253c85e32f 100644 --- a/packages/playwright-core/src/client/channels.d.ts +++ b/packages/playwright-core/src/client/channels.d.ts @@ -4048,10 +4048,9 @@ export type PageEmulateMediaOptions = { export type PageEmulateMediaResult = void; export type PageExposeBindingParams = { name: string, - noGlobal?: boolean, }; export type PageExposeBindingOptions = { - noGlobal?: boolean, + }; export type PageExposeBindingResult = { disposable: DisposableChannel, diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index 2b315dd685642..ad25c267dd8e3 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -27,14 +27,13 @@ import { addSourceUrlToScript } from './clientHelper'; import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle'; import { AbortError, PlaywrightError } from './errors'; import { Events } from './events'; -import { JSHandle, assertEvaluateOptions, assertMaxArguments, parseResult, serializeArgument, serializeArgumentWithCallbacks } from './jsHandle'; +import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle'; import { FrameLocator, Locator, testIdAttributeName } from './locator'; import * as network from './network'; import { kLifecycleEvents } from './types'; import { Waiter } from './waiter'; import { TimeoutSettings, kNoTimeout } from './timeoutSettings'; -import type { EvaluateOptions } from './jsHandle'; import type { LocatorOptions } from './locator'; import type { Page } from './page'; import type { DropPayload, FilePayload, LifecycleEvent, SelectOption, SelectOptionOptions, StrictOptions, TimeoutOptions, WaitForFunctionOptions } from './types'; @@ -96,10 +95,6 @@ export class Frame extends ChannelOwner implements api.Fr this._channel.on('navigated', event => { this._url = event.url; this._name = event.name; - // Main frame navigation destroys all execution contexts in the page, - // erase the bindings backing the functions passed to evaluate(). - if (!this._parentFrame && event.newDocument && this._page) - this._page._eraseEvaluateCallbacks(); this._eventEmitter.emit('navigated', event); if (!event.error && this._page) { this._page.emit(Events.Page.FrameNavigated, this); @@ -206,19 +201,15 @@ export class Frame extends ChannelOwner implements api.Fr return ElementHandle.from((await this._channel.frameElement({}, kNoTimeout)).element); } - async evaluateHandle(pageFunction: structs.PageFunction, arg?: Arg, options?: EvaluateOptions): Promise> { - assertMaxArguments(arguments.length, 3); - assertEvaluateOptions(options); - const serializedArg = options?.exposeFunctions ? await serializeArgumentWithCallbacks(this, this._page, arg) : serializeArgument(arg); - const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializedArg }, kNoTimeout); + async evaluateHandle(pageFunction: structs.PageFunction, arg?: Arg): Promise> { + assertMaxArguments(arguments.length, 2); + const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }, kNoTimeout); return JSHandle.from(result.handle) as any as structs.SmartHandle; } - async evaluate(pageFunction: structs.PageFunction, arg?: Arg, options?: EvaluateOptions): Promise { - assertMaxArguments(arguments.length, 3); - assertEvaluateOptions(options); - const serializedArg = options?.exposeFunctions ? await serializeArgumentWithCallbacks(this, this._page, arg) : serializeArgument(arg); - const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializedArg }, kNoTimeout); + async evaluate(pageFunction: structs.PageFunction, arg?: Arg): Promise { + assertMaxArguments(arguments.length, 2); + const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }, kNoTimeout); return parseResult(result.value); } diff --git a/packages/playwright-core/src/client/jsHandle.ts b/packages/playwright-core/src/client/jsHandle.ts index c0f20cc97965e..aa9841c2e4075 100644 --- a/packages/playwright-core/src/client/jsHandle.ts +++ b/packages/playwright-core/src/client/jsHandle.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { kFunctionBindingPrefix } from '@isomorphic/utilityScriptSerializers'; import { parseSerializedValue, serializeValue } from '@protocol/serializers'; -import { createGuid } from '@utils/crypto'; import { ChannelOwner } from './channelOwner'; import { isTargetClosedError } from './errors'; import { kNoTimeout } from './timeoutSettings'; @@ -24,7 +22,6 @@ import { kNoTimeout } from './timeoutSettings'; import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; import type * as channels from './channels'; -import type { Page } from './page'; export class JSHandle extends ChannelOwner implements api.JSHandle { @@ -40,17 +37,13 @@ export class JSHandle extends ChannelOwner im this._channel.on('previewUpdated', ({ preview }) => this._preview = preview); } - async evaluate(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: EvaluateOptions): Promise { - assertEvaluateOptions(options); - const serializedArg = options?.exposeFunctions ? await serializeArgumentWithCallbacks(this, this._parentOfType('Page') as Page | undefined, arg) : serializeArgument(arg); - const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializedArg }, kNoTimeout); + async evaluate(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { + const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }, kNoTimeout); return parseResult(result.value); } - async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: EvaluateOptions): Promise> { - assertEvaluateOptions(options); - const serializedArg = options?.exposeFunctions ? await serializeArgumentWithCallbacks(this, this._parentOfType('Page') as Page | undefined, arg) : serializeArgument(arg); - const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializedArg }, kNoTimeout); + async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise> { + const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }, kNoTimeout); return JSHandle.from(result.handle) as any as structs.SmartHandle; } @@ -95,7 +88,7 @@ export class JSHandle extends ChannelOwner im // This function takes care of converting all JSHandles to their channels, // so that generic channel serializer converts them to guids. -export function serializeArgument(arg: any, registerCallback?: (callback: Function) => string): channels.SerializedArgument { +export function serializeArgument(arg: any): channels.SerializedArgument { const handles: channels.Channel[] = []; const pushHandle = (channel: channels.Channel): number => { handles.push(channel); @@ -104,30 +97,11 @@ export function serializeArgument(arg: any, registerCallback?: (callback: Functi const value = serializeValue(arg, value => { if (value instanceof JSHandle) return { h: pushHandle(value._channel) }; - if (typeof value === 'function' && registerCallback) - return { fn: registerCallback(value as Function) }; return { fallThrough: value }; }); return { value, handles }; } -export type EvaluateOptions = { exposeFunctions?: boolean }; - -export async function serializeArgumentWithCallbacks(owner: ChannelOwner, page: Page | undefined, arg: any): Promise { - return await owner._wrapApiCall(async () => { - const exposePromises: Promise[] = []; - const serialized = serializeArgument(arg, callback => { - if (!page) - throw new Error('Passing a function is not supported as an argument here'); - const name = kFunctionBindingPrefix + createGuid(); - exposePromises.push(page._exposeEvaluateCallback(name, callback)); - return name; - }); - await Promise.all(exposePromises); - return serialized; - }, { internal: true }); -} - export function parseResult(value: channels.SerializedValue): any { return parseSerializedValue(value, undefined); } @@ -136,8 +110,3 @@ export function assertMaxArguments(count: number, max: number): asserts count { if (count > max) throw new Error('Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.'); } - -export function assertEvaluateOptions(options: any) { - if (options !== undefined && (typeof options !== 'object' || options === null || Array.isArray(options))) - throw new Error('Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.'); -} diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 078234fd9f110..342799550bc40 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -27,7 +27,6 @@ import { DisposableStub } from './disposable'; import { kNoTimeout } from './timeoutSettings'; import type { ExpectResult, Frame } from './frame'; -import type { EvaluateOptions } from './jsHandle'; import type { DropPayload, FilePayload, FrameExpectParams, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types'; import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; @@ -135,16 +134,16 @@ export class Locator implements api.Locator { await this._frame._drop(this._selector, payload, { strict: true, ...options }); } - async evaluate(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions & EvaluateOptions): Promise { - return await this._withElement(h => h.evaluate(pageFunction, arg, options), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); + async evaluate(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise { + return await this._withElement(h => h.evaluate(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); } async evaluateAll(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { return await this._frame.$$eval(this._selector, pageFunction, arg); } - async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions & EvaluateOptions): Promise> { - return await this._withElement(h => h.evaluateHandle(pageFunction, arg, options), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); + async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise> { + return await this._withElement(h => h.evaluateHandle(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); } async fill(value: string, options: channels.ElementHandleFillOptions & TimeoutOptions = {}): Promise { diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 90a199a17f76d..f7f2ba405427b 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -50,7 +50,6 @@ import { TimeoutSettings, kNoTimeout } from './timeoutSettings'; import { mkdirIfNeeded } from './fileUtils'; import { ConsoleMessage } from './consoleMessage'; import type { BrowserContext } from './browserContext'; -import type { EvaluateOptions } from './jsHandle'; import type { Clock } from './clock'; import type { APIRequestContext } from './fetch'; import type { WaitForNavigationOptions } from './frame'; @@ -118,7 +117,6 @@ export class Page extends ChannelOwner implements api.Page private _harRouters: HarRouter[] = []; private _locatorHandlers = new Map any, times: number | undefined }>(); - private _evaluateCallbacks: { name: string, disposable: DisposableObject }[] = []; static from(page: channels.PageChannel): Page { return (page as any)._object; @@ -336,9 +334,9 @@ export class Page extends ChannelOwner implements api.Page return await this._mainFrame.dispatchEvent(selector, type, eventInit, options); } - async evaluateHandle(pageFunction: structs.PageFunction, arg?: Arg, options?: EvaluateOptions): Promise> { - assertMaxArguments(arguments.length, 3); - return await this._mainFrame.evaluateHandle(pageFunction, arg, options); + async evaluateHandle(pageFunction: structs.PageFunction, arg?: Arg): Promise> { + assertMaxArguments(arguments.length, 2); + return await this._mainFrame.evaluateHandle(pageFunction, arg); } async $eval(selector: string, pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { @@ -376,20 +374,6 @@ export class Page extends ChannelOwner implements api.Page return DisposableObject.from(result.disposable); } - async _exposeEvaluateCallback(name: string, callback: Function) { - this._bindings.set(name, (source, ...args) => callback(...args)); - const result = await this._channel.exposeBinding({ name, noGlobal: true }, kNoTimeout); - this._evaluateCallbacks.push({ name, disposable: DisposableObject.from(result.disposable) }); - } - - _eraseEvaluateCallbacks() { - for (const { name, disposable } of this._evaluateCallbacks) { - this._bindings.delete(name); - disposable.dispose().catch(() => {}); - } - this._evaluateCallbacks = []; - } - async setExtraHTTPHeaders(headers: Headers) { validateHeaders(headers); await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) }, kNoTimeout); @@ -544,9 +528,9 @@ export class Page extends ChannelOwner implements api.Page return this._viewportSize || null; } - async evaluate(pageFunction: structs.PageFunction, arg?: Arg, options?: EvaluateOptions): Promise { - assertMaxArguments(arguments.length, 3); - return await this._mainFrame.evaluate(pageFunction, arg, options); + async evaluate(pageFunction: structs.PageFunction, arg?: Arg): Promise { + assertMaxArguments(arguments.length, 2); + return await this._mainFrame.evaluate(pageFunction, arg); } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { diff --git a/packages/playwright-core/src/server/channels.d.ts b/packages/playwright-core/src/server/channels.d.ts index b66eb1753d7ef..62c59f0116e2e 100644 --- a/packages/playwright-core/src/server/channels.d.ts +++ b/packages/playwright-core/src/server/channels.d.ts @@ -4049,10 +4049,9 @@ export type PageEmulateMediaOptions = { export type PageEmulateMediaResult = void; export type PageExposeBindingParams = { name: string, - noGlobal?: boolean, }; export type PageExposeBindingOptions = { - noGlobal?: boolean, + }; export type PageExposeBindingResult = { disposable: DisposableChannel, diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index ede502ef9bbc9..b9e4ba875092a 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -143,7 +143,7 @@ export class PageDispatcher extends Dispatcher { return this.frameManager.frames(); } - async exposeBinding(progress: Progress, name: string, playwrightBinding: frames.FunctionWithSource, noGlobal?: boolean): Promise { + async exposeBinding(progress: Progress, name: string, playwrightBinding: frames.FunctionWithSource): Promise { if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`); if (this.browserContext._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered in the browser context`); await progress.race(this.browserContext.exposePlaywrightBindingIfNeeded()); - const binding = new PageBinding(this, name, playwrightBinding, noGlobal); + const binding = new PageBinding(this, name, playwrightBinding); this._pageBindings.set(name, binding); try { await progress.race(this.delegate.addInitScript(binding.initScript)); @@ -1046,6 +1046,7 @@ export class Worker extends SdkObject { } export class PageBinding extends DisposableObject { + private static kController = '__playwright__binding__controller__'; static kBindingName = '__playwright__binding__'; static createInitScript(browserContext: BrowserContext): InitScript { @@ -1053,7 +1054,7 @@ export class PageBinding extends DisposableObject { (() => { const module = {}; ${rawBindingsControllerSource.source} - const property = '${kBindingsControllerProperty}'; + const property = '${PageBinding.kController}'; if (!globalThis[property]) globalThis[property] = new (module.exports.BindingsController())(globalThis, '${PageBinding.kBindingName}'); })(); @@ -1066,12 +1067,12 @@ export class PageBinding extends DisposableObject { readonly cleanupScript: string; forClient?: unknown; - constructor(parent: BrowserContext | Page, name: string, playwrightFunction: frames.FunctionWithSource, noGlobal?: boolean) { + constructor(parent: BrowserContext | Page, name: string, playwrightFunction: frames.FunctionWithSource) { super(parent); this.name = name; this.playwrightFunction = playwrightFunction; - this.initScript = new InitScript(parent, `globalThis['${kBindingsControllerProperty}'].addBinding(${JSON.stringify(name)}, ${!!noGlobal})`); - this.cleanupScript = `globalThis['${kBindingsControllerProperty}'].removeBinding(${JSON.stringify(name)})`; + this.initScript = new InitScript(parent, `globalThis['${PageBinding.kController}'].addBinding(${JSON.stringify(name)})`); + this.cleanupScript = `globalThis['${PageBinding.kController}'].removeBinding(${JSON.stringify(name)})`; } static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) { @@ -1085,9 +1086,9 @@ export class PageBinding extends DisposableObject { throw new Error(`serializedArgs is not an array. This can happen when Array.prototype.toJSON is defined incorrectly`); const args = serializedArgs.map(a => parseEvaluationResultValue(a)); const result = await binding.playwrightFunction({ frame: context.frame, page, context: page.browserContext }, ...args); - context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, result }).catch(e => debugLogger.log('error', e)); + context.evaluateExpressionHandle(`arg => globalThis['${PageBinding.kController}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, result }).catch(e => debugLogger.log('error', e)); } catch (error) { - context.evaluateExpressionHandle(`arg => globalThis['${kBindingsControllerProperty}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, error }).catch(e => debugLogger.log('error', e)); + context.evaluateExpressionHandle(`arg => globalThis['${PageBinding.kController}'].deliverBindingResult(arg)`, { isFunction: true }, { name, seq, error }).catch(e => debugLogger.log('error', e)); } } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0b3c58906b5b1..8c1ace60a3284 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -87,15 +87,14 @@ export interface Page { * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a - * [Promise], then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait - * for the promise to resolve and return its value. + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], + * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for + * the promise to resolve and return its value. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a * non-[Serializable] value, then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -119,7 +118,7 @@ export interface Page { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate): + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): * * ```js * const bodyHandle = await page.evaluateHandle('document.body'); @@ -132,23 +131,21 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; /** * Returns the value of the * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a - * [Promise], then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait - * for the promise to resolve and return its value. + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], + * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for + * the promise to resolve and return its value. * * If the function passed to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a * non-[Serializable] value, then - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -172,7 +169,7 @@ export interface Page { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate): + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): * * ```js * const bodyHandle = await page.evaluateHandle('document.body'); @@ -185,9 +182,8 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; /** * Returns the value of the @@ -195,17 +191,16 @@ export interface Page { * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * is that - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function passed to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns a [Promise], then - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * would wait for the promise to resolve and return its value. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * a [Promise], then + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would + * wait for the promise to resolve and return its value. * * **Usage** * @@ -221,7 +216,7 @@ export interface Page { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): * * ```js * const aHandle = await page.evaluateHandle(() => document.body); @@ -233,26 +228,24 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; /** * Returns the value of the * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression) invocation as a * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * is that - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function passed to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * returns a [Promise], then - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * would wait for the promise to resolve and return its value. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns + * a [Promise], then + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would + * wait for the promise to resolve and return its value. * * **Usage** * @@ -268,7 +261,7 @@ export interface Page { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): * * ```js * const aHandle = await page.evaluateHandle(() => document.body); @@ -280,9 +273,8 @@ export interface Page { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; /** * Adds a script which would be evaluated in one of the following scenarios: @@ -5794,15 +5786,14 @@ export interface Frame { * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a [Promise], then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) would - * wait for the promise to resolve and return its value. + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * [Promise], then [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) + * would wait for the promise to resolve and return its value. * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a non-[Serializable] value, then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * non-[Serializable] value, then + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -5822,7 +5813,7 @@ export interface Frame { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate): + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate): * * ```js * const bodyHandle = await frame.evaluateHandle('document.body'); @@ -5835,23 +5826,21 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a [Promise], then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) would - * wait for the promise to resolve and return its value. + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * [Promise], then [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) + * would wait for the promise to resolve and return its value. * * If the function passed to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns - * a non-[Serializable] value, then - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns a + * non-[Serializable] value, then + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) returns * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: * `-0`, `NaN`, `Infinity`, `-Infinity`. * @@ -5871,7 +5860,7 @@ export interface Frame { * ``` * * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate): + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate): * * ```js * const bodyHandle = await frame.evaluateHandle('document.body'); @@ -5884,9 +5873,8 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; /** * Returns the return value of @@ -5894,16 +5882,15 @@ export interface Frame { * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) - * is that - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) is + * that [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function, passed to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), * returns a [Promise], then - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * would wait for the promise to resolve and return its value. * * **Usage** @@ -5920,7 +5907,7 @@ export interface Frame { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): * * ```js * const aHandle = await frame.evaluateHandle(() => document.body); @@ -5934,25 +5921,23 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression) as a * [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * The only difference between - * [frame.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) - * is that - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate) and + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) is + * that [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * returns [JSHandle](https://playwright.dev/docs/api/class-jshandle). * * If the function, passed to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle), * returns a [Promise], then - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle) * would wait for the promise to resolve and return its value. * * **Usage** @@ -5969,7 +5954,7 @@ export interface Frame { * ``` * * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the - * [frame.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): + * [frame.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle): * * ```js * const aHandle = await frame.evaluateHandle(() => document.body); @@ -5983,9 +5968,8 @@ export interface Frame { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-frame#frame-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; /** * **NOTE** Use locator-based [frame.locator(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-locator) @@ -11748,8 +11732,7 @@ export interface Worker { /** * JSHandle represents an in-page JavaScript object. JSHandles can be created with the - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * method. + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) method. * * ```js * const windowHandle = await page.evaluateHandle(() => window); @@ -11762,8 +11745,8 @@ export interface Worker { * * JSHandle instances can be used as an argument in * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector), - * [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) and - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) + * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and + * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) * methods. */ export interface JSHandle { @@ -11787,9 +11770,8 @@ export interface JSHandle { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). @@ -11810,9 +11792,8 @@ export interface JSHandle { * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-option-expression). - * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; /** * Returns the return value of @@ -11828,15 +11809,13 @@ export interface JSHandle { * If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would * wait for the promise to resolve and return its value. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; /** * Returns the return value of * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression) @@ -11851,15 +11830,13 @@ export interface JSHandle { * If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would * wait for the promise to resolve and return its value. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-elementhandle#element-handle-evaluate-handle-option-expression). - * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; /** * Returns a JSON representation of the object. If the object has a `toJSON` function, it **will not be called**. @@ -11924,8 +11901,7 @@ export interface JSHandle { * * ElementHandle instances can be used as an argument in * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) - * and [page.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate) - * methods. + * and [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) methods. * * The difference between the [Locator](https://playwright.dev/docs/api/class-locator) and ElementHandle is that the * ElementHandle points to a particular element, while [Locator](https://playwright.dev/docs/api/class-locator) @@ -13867,7 +13843,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression). * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; /** * Execute JavaScript code in the page, taking the matching element as an argument, and return a * [JSHandle](https://playwright.dev/docs/api/class-jshandle) with the result. @@ -13893,15 +13869,14 @@ export interface Locator { * If [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression) throws * or rejects, this method throws. * - * See - * [page.evaluateHandle(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) - * for more details. + * See [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) for + * more details. * @param pageFunction Function to be evaluated in the page context. * @param arg Optional argument to pass to * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression). * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; /** * Execute JavaScript code in the page, taking all matching elements as an argument. * diff --git a/packages/playwright-ct-core/src/injected/serializers.ts b/packages/playwright-ct-core/src/injected/serializers.ts index 3d9fee5e7659a..be083e27db72f 100644 --- a/packages/playwright-ct-core/src/injected/serializers.ts +++ b/packages/playwright-ct-core/src/injected/serializers.ts @@ -16,15 +16,37 @@ import { isImportRef } from './importRegistry'; -// Validates that the component tree does not reference components defined in -// the test file. Functions in the tree are passed to evaluate() as arguments -// and materialize as callbacks in the page. -export function validateComponent(value: any) { - transformObject(value, () => undefined); +type FunctionRef = { + __pw_type: 'function'; + ordinal: number; +}; + +function isFunctionRef(value: any): value is FunctionRef { + return value && typeof value === 'object' && value.__pw_type === 'function'; +} + +export function wrapObject(value: any, callbacks: Function[]): any { + return transformObject(value, (v: any) => { + if (typeof v === 'function') { + const ordinal = callbacks.length; + callbacks.push(v as Function); + const result: FunctionRef = { + __pw_type: 'function', + ordinal, + }; + return { result }; + } + }); } export async function unwrapObject(value: any): Promise { return transformObjectAsync(value, async (v: any) => { + if (isFunctionRef(v)) { + const result = (...args: any[]) => { + window.__ctDispatchFunction(v.ordinal, args); + }; + return { result }; + } if (isImportRef(v)) return { result: await window.__pwRegistry.resolveImportRef(v) }; }); diff --git a/packages/playwright-ct-core/src/mount.ts b/packages/playwright-ct-core/src/mount.ts index 4486d17b79263..302439f46c390 100644 --- a/packages/playwright-ct-core/src/mount.ts +++ b/packages/playwright-ct-core/src/mount.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { validateComponent } from './injected/serializers'; +import { wrapObject } from './injected/serializers'; import { Router } from './router'; import type { config, FullConfigInternal } from 'playwright/lib/common'; @@ -24,6 +24,8 @@ import type { Component, JsxComponent, MountOptions, ObjectComponentOptions } fr import type { Fixtures, Locator, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions } from 'playwright/test'; import type { Page as PageImpl } from 'playwright-core/lib/client/page'; +let boundCallbacksForMount: Function[] = []; + interface MountResult extends Locator { unmount: () => Promise; update: (options: ObjectComponentOptions | JsxComponent) => Promise; @@ -50,6 +52,9 @@ export const fixtures: Fixtures if (!process.env.PLAYWRIGHT_TEST_BASE_URL) throw new Error('Component testing could not determine the base URL of your component under test. Ensure you have supplied a template playwright/index.html or have set the PLAYWRIGHT_TEST_BASE_URL environment variable.'); await (page as PageImpl)._wrapApiCall(async () => { + await page.exposeFunction('__ctDispatchFunction', (ordinal: number, args: any[]) => { + boundCallbacksForMount[ordinal](...args); + }); await page.goto(process.env.PLAYWRIGHT_TEST_BASE_URL!); }, { internal: true }); await use(page); @@ -75,6 +80,7 @@ export const fixtures: Fixtures } }); }); + boundCallbacksForMount = []; }, router: async ({ context, baseURL }, use) => { @@ -89,19 +95,17 @@ function isJsxComponent(component: any): component is JsxComponent { } async function innerUpdate(page: Page, componentRef: JsxComponent | ImportRef, options: ObjectComponentOptions = {}): Promise { - const component = createComponent(componentRef, options); - validateComponent(component); + const component = wrapObject(createComponent(componentRef, options), boundCallbacksForMount); await page.evaluate(async ({ component }) => { component = await window.__pwUnwrapObject(component); const rootElement = document.getElementById('root')!; return await window.playwrightUpdate(rootElement, component); - }, { component }, { exposeFunctions: true }); + }, { component }); } async function innerMount(page: Page, componentRef: JsxComponent | ImportRef, options: ObjectComponentOptions & MountOptions = {}): Promise { - const component = createComponent(componentRef, options); - validateComponent(component); + const component = wrapObject(createComponent(componentRef, options), boundCallbacksForMount); // WebKit does not wait for deferred scripts. await page.waitForFunction(() => !!window.playwrightMount); @@ -118,7 +122,7 @@ async function innerMount(page: Page, componentRef: JsxComponent | ImportRef, op await window.playwrightMount(component, rootElement, hooksConfig); return '#root >> internal:control=component'; - }, { component, hooksConfig: options.hooksConfig }, { exposeFunctions: true }); + }, { component, hooksConfig: options.hooksConfig }); return selector; } diff --git a/packages/playwright-ct-core/types/component.d.ts b/packages/playwright-ct-core/types/component.d.ts index d3dfa9f5669f3..2f488b885e6c5 100644 --- a/packages/playwright-ct-core/types/component.d.ts +++ b/packages/playwright-ct-core/types/component.d.ts @@ -47,6 +47,8 @@ declare global { __pw_hooks_after_mount?: (( params: { hooksConfig?: HooksConfig; [key: string]: any } ) => Promise)[]; + // Can't start with __pw due to core reuse bindings logic for __pw*. + __ctDispatchFunction: (ordinal: number, args: any[]) => void; __pwUnwrapObject: (value: any) => Promise; __pwTransformObject: (value: any, mapping: (v: any) => { result: any } | undefined) => any; } diff --git a/packages/protocol/spec/page.yml b/packages/protocol/spec/page.yml index 0d132d013a939..722870100e7cf 100644 --- a/packages/protocol/spec/page.yml +++ b/packages/protocol/spec/page.yml @@ -107,7 +107,6 @@ Page: group: configuration parameters: name: string - noGlobal: boolean? returns: disposable: Disposable diff --git a/packages/protocol/spec/serialized.yml b/packages/protocol/spec/serialized.yml index 5a1ddb22d0431..1457d5f8914d3 100644 --- a/packages/protocol/spec/serialized.yml +++ b/packages/protocol/spec/serialized.yml @@ -79,9 +79,6 @@ SerializedValue: v: SerializedValue # An index in the handles array from SerializedArgument. h: int? - # Name of a page-side callback that routes back to a client-side function. - # Used when passing a function as (part of) an evaluate argument. - fn: string? # Index of the object in value-type for circular reference resolution. id: int? # Ref to the object in value-type for circular reference resolution. diff --git a/packages/protocol/src/serializers.ts b/packages/protocol/src/serializers.ts index 399de41e35575..020421c646381 100644 --- a/packages/protocol/src/serializers.ts +++ b/packages/protocol/src/serializers.ts @@ -81,15 +81,10 @@ function innerParseSerializedValue(value: SerializedValue, handles: any[] | unde throw new Error('Unexpected handle'); return handles[value.h]; } - if (value.fn !== undefined) { - const dummy = () => {}; - Object.defineProperty(dummy, 'name', { value: value.fn }); - return dummy; - } throw new Error(`Attempting to deserialize unexpected value${accessChainToDisplayString(accessChain)}: ${value}`); } -export type HandleOrValue = { h: number } | { fn: string } | { fallThrough: any }; +export type HandleOrValue = { h: number } | { fallThrough: any }; type VisitorInfo = { visited: Map; lastId: number; diff --git a/packages/protocol/src/structs.d.ts b/packages/protocol/src/structs.d.ts index 30ee9c10c1149..7d96e75d8d538 100644 --- a/packages/protocol/src/structs.d.ts +++ b/packages/protocol/src/structs.d.ts @@ -284,7 +284,6 @@ export type SerializedValue = { v: SerializedValue, }[], h?: number, - fn?: string, id?: number, ref?: number, }; diff --git a/packages/protocol/src/validator.ts b/packages/protocol/src/validator.ts index 5c080081f7e09..d18c0b9138ef0 100644 --- a/packages/protocol/src/validator.ts +++ b/packages/protocol/src/validator.ts @@ -2318,7 +2318,6 @@ scheme.PageEmulateMediaParams = tObject({ scheme.PageEmulateMediaResult = tOptional(tObject({})); scheme.PageExposeBindingParams = tObject({ name: tString, - noGlobal: tOptional(tBoolean), }); scheme.PageExposeBindingResult = tObject({ disposable: tChannel(['Disposable']), @@ -2986,7 +2985,6 @@ scheme.SerializedValue = tObject({ v: tType('SerializedValue'), }))), h: tOptional(tInt), - fn: tOptional(tString), id: tOptional(tInt), ref: tOptional(tInt), }); diff --git a/tests/page/page-evaluate-callback.spec.ts b/tests/page/page-evaluate-callback.spec.ts deleted file mode 100644 index 6b58dfbc508ee..0000000000000 --- a/tests/page/page-evaluate-callback.spec.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { attachFrame } from '../config/utils'; -import { test as it, expect } from './pageTest'; - -it('should throw without the exposeFunctions option', async ({ page }) => { - await expect(page.evaluate(({ cb }) => (cb as any)(), { cb: () => {} })) - .rejects.toThrow(/Attempting to serialize unexpected value at position "cb": \(\) => {}/); -}); - -it('should call a function passed as an argument', async ({ page }) => { - const received: number[] = []; - await page.evaluate(async ({ cb }) => { - await cb(1); - await cb(2); - }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - expect(received).toEqual([1, 2]); -}); - -it('should accept a function as the whole argument', async ({ page }) => { - const received: string[] = []; - await page.evaluate(async cb => { - await cb('a'); - await cb('b'); - }, async (s: string) => { received.push(s); }, { exposeFunctions: true }); - expect(received).toEqual(['a', 'b']); -}); - -it('should pass arguments to the callback', async ({ page }) => { - const args = await new Promise(resolve => { - page.evaluate(({ cb }) => cb(1, 'two', { three: 3 }, [4]), { - cb: (...a: any[]) => resolve(a), - }, { exposeFunctions: true }).catch(() => {}); - }); - expect(args).toEqual([1, 'two', { three: 3 }, [4]]); -}); - -it('should return the callback result to the page', async ({ page }) => { - const doubled = await page.evaluate(async ({ cb }) => await cb(21), { - cb: async (n: number) => n * 2, - }, { exposeFunctions: true }); - expect(doubled).toBe(42); -}); - -it('should await an async callback result', async ({ page }) => { - const value = await page.evaluate(async ({ cb }) => await cb(20), { - cb: async (n: number) => { await new Promise(f => setTimeout(f, 10)); return n + 1; }, - }, { exposeFunctions: true }); - expect(value).toBe(21); -}); - -it('should propagate callback errors to the page', async ({ page }) => { - const message = await page.evaluate(async ({ cb }) => { - try { - await cb(); - return 'no error'; - } catch (e) { - return (e as Error).message; - } - }, { cb: async () => { throw new Error('boom'); } }, { exposeFunctions: true }); - expect(message).toContain('boom'); -}); - -it('should work with a fire-and-forget setTimeout callback', async ({ page }) => { - const value = await new Promise(resolve => { - page.evaluate(({ cb }) => { setTimeout(() => cb(5), 0); }, { - cb: (n: number) => resolve(n), - }, { exposeFunctions: true }).catch(() => {}); - }); - expect(value).toBe(5); -}); - -it('should support multiple callbacks', async ({ page }) => { - const result = await page.evaluate(async ({ add, mul }) => { - return (await add(2, 3)) + (await mul(2, 3)); - }, { - add: async (a: number, b: number) => a + b, - mul: async (a: number, b: number) => a * b, - }, { exposeFunctions: true }); - expect(result).toBe(11); -}); - -it('should work with evaluateHandle', async ({ page }) => { - const received: number[] = []; - const handle = await page.evaluateHandle(async ({ cb }) => { - await cb(7); - return { done: true }; - }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - expect(await handle.jsonValue()).toEqual({ done: true }); - expect(received).toEqual([7]); -}); - -it('should work in a child frame', async ({ page, server }) => { - await page.goto(server.EMPTY_PAGE); - const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE); - const received: number[] = []; - await frame.evaluate(async ({ cb }) => { await cb(42); }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - expect(received).toEqual([42]); -}); - -it('should work with jsHandle.evaluate', async ({ page }) => { - const handle = await page.evaluateHandle(() => window); - const received: number[] = []; - await handle.evaluate(async (win, { cb }) => { await cb(99); }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - expect(received).toEqual([99]); -}); - -it('should work with locator.evaluate', async ({ page }) => { - await page.setContent('
hello
'); - const received: string[] = []; - await page.locator('#target').evaluate(async (element, { cb }) => { await cb(element.id); }, { cb: async (s: string) => { received.push(s); } }, { exposeFunctions: true }); - expect(received).toEqual(['target']); -}); - -it('should return the callback result with locator.evaluate', async ({ page }) => { - await page.setContent('
7
'); - const result = await page.locator('#target').evaluate(async (element, { double }) => { - return await double(+element.textContent!); - }, { double: async (n: number) => n * 2 }, { exposeFunctions: true }); - expect(result).toBe(14); -}); - -it('should propagate callback errors with locator.evaluate', async ({ page }) => { - await page.setContent('
'); - const message = await page.locator('#target').evaluate(async (element, { cb }) => { - try { - await cb(); - return 'no error'; - } catch (e) { - return (e as Error).message; - } - }, { cb: async () => { throw new Error('boom'); } }, { exposeFunctions: true }); - expect(message).toContain('boom'); -}); - -it('should work with locator.evaluateHandle', async ({ page }) => { - await page.setContent('
hello
'); - const received: string[] = []; - const handle = await page.locator('#target').evaluateHandle(async (element, { cb }) => { - await cb(element.id); - return element; - }, { cb: async (s: string) => { received.push(s); } }, { exposeFunctions: true }); - expect(received).toEqual(['target']); - expect(await handle.evaluate(element => element.id)).toBe('target'); -}); - -it('should work with locator.evaluate inside an iframe', async ({ page, server }) => { - await page.goto(server.EMPTY_PAGE); - const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE); - await frame.evaluate(() => { document.body.innerHTML = '
in-frame
'; }); - const received: (string | null)[] = []; - await page.frameLocator('#frame1').locator('#target').evaluate(async (element, { cb }) => { - await cb(element.textContent); - }, { cb: async (text: string | null) => { received.push(text); } }, { exposeFunctions: true }); - expect(received).toEqual(['in-frame']); -}); - -it('should survive a navigation and keep working', async ({ page, server }) => { - const received: number[] = []; - await page.evaluate(async ({ cb }) => { await cb(1); }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(async ({ cb }) => { await cb(2); }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); - expect(received).toEqual([1, 2]); -}); - -it('should not register the callback on the global object', async ({ page }) => { - const result = await page.evaluate(async ({ cb }) => { - await cb(); - return Object.getOwnPropertyNames(globalThis).filter(name => name.startsWith('__pw_fn_')); - }, { cb: async () => {} }, { exposeFunctions: true }); - expect(result).toEqual([]); -}); - -it('should scope the page-side callback to the execution context', async ({ page, server }) => { - await page.evaluate(({ cb }) => { (window as any).__cb = cb; }, { cb: () => {} }, { exposeFunctions: true }); - expect(await page.evaluate(() => typeof (window as any).__cb)).toBe('function'); - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate(() => typeof (window as any).__cb)).toBe('undefined'); -}); diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 81ac22c943262..302fd7e31294e 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -38,11 +38,11 @@ type ZodSchema = ZodTypeAny | z3.ZodTypeAny; type InferZodSchema = T extends z3.ZodTypeAny ? z3.infer : T extends ZodTypeAny ? z.infer : never; export interface Page { - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; addInitScript(script: PageFunction | { path?: string, content?: string }, arg?: Arg): Promise; @@ -85,11 +85,11 @@ export interface Page { } export interface Frame { - evaluate(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; - evaluate(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunction, arg: Arg): Promise; + evaluate(pageFunction: PageFunction, arg?: any): Promise; - evaluateHandle(pageFunction: PageFunction, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; - evaluateHandle(pageFunction: PageFunction, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; + evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; $(selector: K, options?: { strict: boolean }): Promise | null>; $(selector: string, options?: { strict: boolean }): Promise | null>; @@ -156,11 +156,11 @@ export interface Worker { } export interface JSHandle { - evaluate(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise; - evaluate(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise; + evaluate(pageFunction: PageFunctionOn, arg: Arg): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: any): Promise; - evaluateHandle(pageFunction: PageFunctionOn, arg: Arg, options?: { exposeFunctions?: boolean }): Promise>; - evaluateHandle(pageFunction: PageFunctionOn, arg?: any, options?: { exposeFunctions?: boolean }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg: Arg): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: any): Promise>; jsonValue(): Promise; asElement(): T extends Node ? ElementHandle : null; @@ -190,8 +190,8 @@ export interface ElementHandle extends JSHandle { } export interface Locator { - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise; - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal, exposeFunctions?: boolean }): Promise>; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; evaluateAll(pageFunction: PageFunctionOn, arg?: Arg): Promise; waitForFunction(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; elementHandle(options?: { timeout?: number }): Promise>;