diff --git a/src/client/frame.ts b/src/client/frame.ts index 24b68c88a3..8b1f7403b2 100644 --- a/src/client/frame.ts +++ b/src/client/frame.ts @@ -176,6 +176,16 @@ export class Frame extends ChannelOwner(pageFunction: Func1, arg: Arg): Promise>; + async _evaluateHandleInUtility(pageFunction: Func1, arg?: any): Promise>; + async _evaluateHandleInUtility(pageFunction: Func1, arg: Arg): Promise> { + assertMaxArguments(arguments.length, 2); + return this._wrapApiCall(this._apiName('_evaluateHandleInUtility'), async () => { + const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), world: 'utility' }); + return JSHandle.from(result.handle) as SmartHandle; + }); + } + async evaluate(pageFunction: Func1, arg: Arg): Promise; async evaluate(pageFunction: Func1, arg?: any): Promise; async evaluate(pageFunction: Func1, arg: Arg): Promise { @@ -186,6 +196,16 @@ export class Frame extends ChannelOwner(pageFunction: Func1, arg: Arg): Promise; + async _evaluateInUtility(pageFunction: Func1, arg?: any): Promise; + async _evaluateInUtility(pageFunction: Func1, arg: Arg): Promise { + assertMaxArguments(arguments.length, 2); + return this._wrapApiCall(this._apiName('evaluate'), async () => { + const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), world: 'utility' }); + return parseResult(result.value); + }); + } + async $(selector: string): Promise | null> { return this._wrapApiCall(this._apiName('$'), async () => { const result = await this._channel.querySelector({ selector }); diff --git a/src/client/waiter.ts b/src/client/waiter.ts index a56ef0ec39..aec5d0cc5d 100644 --- a/src/client/waiter.ts +++ b/src/client/waiter.ts @@ -93,7 +93,7 @@ function waitForEvent(emitter: EventEmitter, event: string, predicate? } function waitForTimeout(timeout: number): { promise: Promise, dispose: () => void } { - let timeoutId: number; + let timeoutId: any; const promise = new Promise(resolve => timeoutId = setTimeout(resolve, timeout)); const dispose = () => clearTimeout(timeoutId); return { promise, dispose }; diff --git a/src/dispatchers/frameDispatcher.ts b/src/dispatchers/frameDispatcher.ts index 9d40310e4f..301f6495d4 100644 --- a/src/dispatchers/frameDispatcher.ts +++ b/src/dispatchers/frameDispatcher.ts @@ -65,11 +65,11 @@ export class FrameDispatcher extends Dispatcher { - return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) }; + return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg), params.world)) }; } async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams): Promise { - return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) }; + return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg), params.world)) }; } async waitForSelector(params: channels.FrameWaitForSelectorParams): Promise { diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index ae68b808f1..3b772ecda9 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -1264,9 +1264,10 @@ export type FrameEvaluateExpressionParams = { expression: string, isFunction: boolean, arg: SerializedArgument, + world?: 'main' | 'utility', }; export type FrameEvaluateExpressionOptions = { - + world?: 'main' | 'utility', }; export type FrameEvaluateExpressionResult = { value: SerializedValue, @@ -1275,9 +1276,10 @@ export type FrameEvaluateExpressionHandleParams = { expression: string, isFunction: boolean, arg: SerializedArgument, + world?: 'main' | 'utility', }; export type FrameEvaluateExpressionHandleOptions = { - + world?: 'main' | 'utility', }; export type FrameEvaluateExpressionHandleResult = { handle: JSHandleChannel, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index ebaaed67c8..6f272514ae 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -1049,6 +1049,11 @@ Frame: expression: string isFunction: boolean arg: SerializedArgument + world: + type: enum? + literals: + - main + - utility returns: value: SerializedValue @@ -1057,6 +1062,11 @@ Frame: expression: string isFunction: boolean arg: SerializedArgument + world: + type: enum? + literals: + - main + - utility returns: handle: JSHandle diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index bb9d7e5b50..87c63f8a53 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -493,11 +493,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { expression: tString, isFunction: tBoolean, arg: tType('SerializedArgument'), + world: tOptional(tEnum(['main', 'utility'])), }); scheme.FrameEvaluateExpressionHandleParams = tObject({ expression: tString, isFunction: tBoolean, arg: tType('SerializedArgument'), + world: tOptional(tEnum(['main', 'utility'])), }); scheme.FrameFillParams = tObject({ selector: tString, diff --git a/src/server/frames.ts b/src/server/frames.ts index bcf59d76a6..89fa4493f9 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -523,17 +523,19 @@ export class Frame extends EventEmitter { return this._context('utility'); } - async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise { - const context = await this._mainContext(); + async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise { + const context = await this._context(world); const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg); - await this._page._doSlowMo(); + if (world === 'main') + await this._page._doSlowMo(); return handle; } - async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise { - const context = await this._mainContext(); + async _evaluateExpression(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise { + const context = await this._context(world); const value = await context.evaluateExpressionInternal(expression, isFunction, arg); - await this._page._doSlowMo(); + if (world === 'main') + await this._page._doSlowMo(); return value; } diff --git a/test/frame-evaluate.spec.ts b/test/frame-evaluate.spec.ts index 27a64fdf33..37f2ab22e4 100644 --- a/test/frame-evaluate.spec.ts +++ b/test/frame-evaluate.spec.ts @@ -18,6 +18,7 @@ import { it, expect, options } from './playwright.fixtures'; import utils from './utils'; +import type { Frame } from '../src/client/frame'; it('should have different execution contexts', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); @@ -180,3 +181,23 @@ it('evaluateHandle should work', async ({page, server}) => { const windowHandle = await mainFrame.evaluateHandle(() => window); expect(windowHandle).toBeTruthy(); }); + +it('evaluateInUtility should work', async ({page}) => { + await page.setContent('hello'); + const mainFrame = page.mainFrame() as any as Frame; + await mainFrame.evaluate(() => window['foo'] = 42); + expect(await mainFrame.evaluate(() => window['foo'])).toBe(42); + expect(await mainFrame._evaluateInUtility(() => window['foo'])).toBe(undefined); + expect(await mainFrame._evaluateInUtility(() => document.body.textContent)).toBe('hello'); +}); + +it('evaluateHandleInUtility should work', async ({page}) => { + await page.setContent('hello'); + const mainFrame = page.mainFrame() as any as Frame; + await mainFrame.evaluate(() => window['foo'] = 42); + expect(await mainFrame.evaluate(() => window['foo'])).toBe(42); + const handle1 = await mainFrame._evaluateHandleInUtility(() => window['foo']); + expect(await handle1.jsonValue()).toBe(undefined); + const handle2 = await mainFrame._evaluateHandleInUtility(() => document.body); + expect(await handle2.evaluate(body => body.textContent)).toBe('hello'); +});