chore: introduce evaluateInUtility private api (#3907)

This is an experimental  client-side api. We'll experiment with it in plugins like tracing.
This commit is contained in:
Dmitry Gozman 2020-09-16 16:07:49 -07:00 committed by GitHub
parent 36f2420b0f
commit dc06f0a75c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 11 deletions

View File

@ -176,6 +176,16 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}); });
} }
async _evaluateHandleInUtility<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async _evaluateHandleInUtility<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async _evaluateHandleInUtility<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
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<R>;
});
}
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>; async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>; async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
@ -186,6 +196,16 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}); });
} }
async _evaluateInUtility<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async _evaluateInUtility<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async _evaluateInUtility<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
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<ElementHandle<Element> | null> { async $(selector: string): Promise<ElementHandle<Element> | null> {
return this._wrapApiCall(this._apiName('$'), async () => { return this._wrapApiCall(this._apiName('$'), async () => {
const result = await this._channel.querySelector({ selector }); const result = await this._channel.querySelector({ selector });

View File

@ -93,7 +93,7 @@ function waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?
} }
function waitForTimeout(timeout: number): { promise: Promise<void>, dispose: () => void } { function waitForTimeout(timeout: number): { promise: Promise<void>, dispose: () => void } {
let timeoutId: number; let timeoutId: any;
const promise = new Promise<void>(resolve => timeoutId = setTimeout(resolve, timeout)); const promise = new Promise<void>(resolve => timeoutId = setTimeout(resolve, timeout));
const dispose = () => clearTimeout(timeoutId); const dispose = () => clearTimeout(timeoutId);
return { promise, dispose }; return { promise, dispose };

View File

@ -65,11 +65,11 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
} }
async evaluateExpression(params: channels.FrameEvaluateExpressionParams): Promise<channels.FrameEvaluateExpressionResult> { async evaluateExpression(params: channels.FrameEvaluateExpressionParams): Promise<channels.FrameEvaluateExpressionResult> {
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<channels.FrameEvaluateExpressionHandleResult> { async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams): Promise<channels.FrameEvaluateExpressionHandleResult> {
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<channels.FrameWaitForSelectorResult> { async waitForSelector(params: channels.FrameWaitForSelectorParams): Promise<channels.FrameWaitForSelectorResult> {

View File

@ -1264,9 +1264,10 @@ export type FrameEvaluateExpressionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction: boolean,
arg: SerializedArgument, arg: SerializedArgument,
world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionOptions = { export type FrameEvaluateExpressionOptions = {
world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionResult = { export type FrameEvaluateExpressionResult = {
value: SerializedValue, value: SerializedValue,
@ -1275,9 +1276,10 @@ export type FrameEvaluateExpressionHandleParams = {
expression: string, expression: string,
isFunction: boolean, isFunction: boolean,
arg: SerializedArgument, arg: SerializedArgument,
world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionHandleOptions = { export type FrameEvaluateExpressionHandleOptions = {
world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionHandleResult = { export type FrameEvaluateExpressionHandleResult = {
handle: JSHandleChannel, handle: JSHandleChannel,

View File

@ -1049,6 +1049,11 @@ Frame:
expression: string expression: string
isFunction: boolean isFunction: boolean
arg: SerializedArgument arg: SerializedArgument
world:
type: enum?
literals:
- main
- utility
returns: returns:
value: SerializedValue value: SerializedValue
@ -1057,6 +1062,11 @@ Frame:
expression: string expression: string
isFunction: boolean isFunction: boolean
arg: SerializedArgument arg: SerializedArgument
world:
type: enum?
literals:
- main
- utility
returns: returns:
handle: JSHandle handle: JSHandle

View File

@ -493,11 +493,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tBoolean,
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
world: tOptional(tEnum(['main', 'utility'])),
}); });
scheme.FrameEvaluateExpressionHandleParams = tObject({ scheme.FrameEvaluateExpressionHandleParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tBoolean,
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
world: tOptional(tEnum(['main', 'utility'])),
}); });
scheme.FrameFillParams = tObject({ scheme.FrameFillParams = tObject({
selector: tString, selector: tString,

View File

@ -523,17 +523,19 @@ export class Frame extends EventEmitter {
return this._context('utility'); return this._context('utility');
} }
async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise<any> { async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._mainContext(); const context = await this._context(world);
const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg); const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg);
await this._page._doSlowMo(); if (world === 'main')
await this._page._doSlowMo();
return handle; return handle;
} }
async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise<any> { async _evaluateExpression(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._mainContext(); const context = await this._context(world);
const value = await context.evaluateExpressionInternal(expression, isFunction, arg); const value = await context.evaluateExpressionInternal(expression, isFunction, arg);
await this._page._doSlowMo(); if (world === 'main')
await this._page._doSlowMo();
return value; return value;
} }

View File

@ -18,6 +18,7 @@
import { it, expect, options } from './playwright.fixtures'; import { it, expect, options } from './playwright.fixtures';
import utils from './utils'; import utils from './utils';
import type { Frame } from '../src/client/frame';
it('should have different execution contexts', async ({ page, server }) => { it('should have different execution contexts', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -180,3 +181,23 @@ it('evaluateHandle should work', async ({page, server}) => {
const windowHandle = await mainFrame.evaluateHandle(() => window); const windowHandle = await mainFrame.evaluateHandle(() => window);
expect(windowHandle).toBeTruthy(); expect(windowHandle).toBeTruthy();
}); });
it('evaluateInUtility should work', async ({page}) => {
await page.setContent('<body>hello</body>');
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('<body>hello</body>');
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');
});