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>(pageFunction: Func1<void, R>, arg?: any): 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> {
return this._wrapApiCall(this._apiName('$'), async () => {
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 } {
let timeoutId: number;
let timeoutId: any;
const promise = new Promise<void>(resolve => timeoutId = setTimeout(resolve, timeout));
const dispose = () => clearTimeout(timeoutId);
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> {
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> {
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> {

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

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

View File

@ -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('<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');
});