Skip to content

Commit

Permalink
chore: introduce evaluateInUtility private api (#3907)
Browse files Browse the repository at this point in the history
This is an experimental  client-side api. We'll experiment with it in plugins like tracing.
  • Loading branch information
dgozman authored Sep 16, 2020
1 parent 36f2420 commit dc06f0a
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 11 deletions.
20 changes: 20 additions & 0 deletions src/client/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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 });
Expand Down
2 changes: 1 addition & 1 deletion src/client/waiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
4 changes: 2 additions & 2 deletions src/dispatchers/frameDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
6 changes: 4 additions & 2 deletions src/protocol/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/protocol/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,11 @@ Frame:
expression: string
isFunction: boolean
arg: SerializedArgument
world:
type: enum?
literals:
- main
- utility
returns:
value: SerializedValue

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

Expand Down
2 changes: 2 additions & 0 deletions src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 8 additions & 6 deletions src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,17 +523,19 @@ 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);
await this._page._doSlowMo();
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);
await this._page._doSlowMo();
if (world === 'main')
await this._page._doSlowMo();
return value;
}

Expand Down
21 changes: 21 additions & 0 deletions test/frame-evaluate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
});

0 comments on commit dc06f0a

Please sign in to comment.