chore: expose utility script to inner evaluates (#19147)

This commit is contained in:
Pavel Feldman 2022-11-29 16:57:11 -08:00 committed by GitHub
parent 89bdaf2441
commit 5ac426b3d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 49 additions and 23 deletions

View file

@ -187,6 +187,12 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
return parseResult(result.value);
}
async _evaluateExposeUtilityScript<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', exposeUtilityScript: true, arg: serializeArgument(arg) });
return parseResult(result.value);
}
async $(selector: string, options?: { strict?: boolean }): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
const result = await this._channel.querySelector({ selector, ...options });
return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;

View file

@ -1295,6 +1295,7 @@ scheme.FrameDispatchEventResult = tOptional(tObject({}));
scheme.FrameEvaluateExpressionParams = tObject({
expression: tString,
isFunction: tOptional(tBoolean),
exposeUtilityScript: tOptional(tBoolean),
arg: tType('SerializedArgument'),
});
scheme.FrameEvaluateExpressionResult = tObject({

View file

@ -76,7 +76,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Pa
}
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
return { value: serializeResult(await this._frame.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, parseArgument(params.arg), 'main')) };
return { value: serializeResult(await this._frame.evaluateExpressionAndWaitForSignals(params.expression, { isFunction: params.isFunction, exposeUtilityScript: params.exposeUtilityScript }, parseArgument(params.arg), 'main')) };
}
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> {

View file

@ -71,19 +71,19 @@ export class FrameExecutionContext extends js.ExecutionContext {
return js.evaluate(this, false /* returnByValue */, pageFunction, arg);
}
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg?: any): Promise<any> {
return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, arg);
async evaluateExpression(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<any> {
return js.evaluateExpression(this, expression, { ...options, returnByValue: true }, arg);
}
async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg?: any): Promise<any> {
async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<any> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return this.evaluateExpression(expression, isFunction, arg);
return this.evaluateExpression(expression, options, arg);
});
}
async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
async evaluateExpressionHandleAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any): Promise<any> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return js.evaluateExpression(this, false /* returnByValue */, expression, isFunction, arg);
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
});
}

View file

@ -595,12 +595,12 @@ export class Frame extends SdkObject {
});
}
nonStallingEvaluateInExistingContext(expression: string, isFunction: boolean|undefined, world: types.World): Promise<any> {
nonStallingEvaluateInExistingContext(expression: string, isFunction: boolean | undefined, world: types.World): Promise<any> {
return this.raceAgainstEvaluationStallingEvents(() => {
const context = this._contextData.get(world)?.context;
if (!context)
throw new Error('Frame does not yet have the execution context');
return context.evaluateExpression(expression, isFunction);
return context.evaluateExpression(expression, { isFunction });
});
}
@ -763,19 +763,19 @@ export class Frame extends SdkObject {
async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const handle = await context.evaluateExpressionHandleAndWaitForSignals(expression, isFunction, arg);
const handle = await context.evaluateExpressionHandleAndWaitForSignals(expression, { isFunction }, arg);
return handle;
}
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const value = await context.evaluateExpression(expression, isFunction, arg);
const value = await context.evaluateExpression(expression, { isFunction }, arg);
return value;
}
async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const value = await context.evaluateExpressionAndWaitForSignals(expression, isFunction, arg);
const value = await context.evaluateExpressionAndWaitForSignals(expression, options, arg);
return value;
}

View file

@ -17,10 +17,16 @@
import { serializeAsCallArgument, parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
export class UtilityScript {
evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
serializeAsCallArgument = serializeAsCallArgument;
parseEvaluationResultValue = parseEvaluationResultValue;
evaluate(isFunction: boolean | undefined, returnByValue: boolean, exposeUtilityScript: boolean | undefined, expression: string, argCount: number, ...argsAndHandles: any[]) {
const args = argsAndHandles.slice(0, argCount);
const handles = argsAndHandles.slice(argCount);
const parameters = args.map(a => parseEvaluationResultValue(a, handles));
if (exposeUtilityScript)
parameters.unshift(this);
let result = globalThis.eval(expression);
if (isFunction === true) {
result = result(...parameters);

View file

@ -226,12 +226,12 @@ export class JSHandle<T = any> extends SdkObject {
}
export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
return evaluateExpression(context, returnByValue, String(pageFunction), typeof pageFunction === 'function', ...args);
return evaluateExpression(context, String(pageFunction), { returnByValue, isFunction: typeof pageFunction === 'function' }, ...args);
}
export async function evaluateExpression(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean | undefined, ...args: any[]): Promise<any> {
export async function evaluateExpression(context: ExecutionContext, expression: string, options: { returnByValue?: boolean, isFunction?: boolean, exposeUtilityScript?: boolean }, ...args: any[]): Promise<any> {
const utilityScript = await context.utilityScript();
expression = normalizeEvaluationExpression(expression, isFunction);
expression = normalizeEvaluationExpression(expression, options.isFunction);
const handles: (Promise<JSHandle>)[] = [];
const toDispose: Promise<JSHandle>[] = [];
const pushHandle = (handle: Promise<JSHandle>): number => {
@ -262,18 +262,18 @@ export async function evaluateExpression(context: ExecutionContext, returnByValu
}
// See UtilityScript for arguments.
const utilityScriptValues = [isFunction, returnByValue, expression, args.length, ...args];
const utilityScriptValues = [options.isFunction, options.returnByValue, options.exposeUtilityScript, expression, args.length, ...args];
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
try {
return await context.evaluateWithArguments(script, returnByValue, utilityScript, utilityScriptValues, utilityScriptObjectIds);
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScript, utilityScriptValues, utilityScriptObjectIds);
} finally {
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
}
}
export async function evaluateExpressionAndWaitForSignals(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction?: boolean, ...args: any[]): Promise<any> {
return await context.waitForSignalsCreatedBy(() => evaluateExpression(context, returnByValue, expression, isFunction, ...args));
export async function evaluateExpressionAndWaitForSignals(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean | undefined, ...args: any[]): Promise<any> {
return await context.waitForSignalsCreatedBy(() => evaluateExpression(context, expression, { returnByValue, isFunction }, ...args));
}
export function parseUnserializableValue(unserializableValue: string): any {

View file

@ -724,11 +724,11 @@ export class Worker extends SdkObject {
}
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, true /* returnByValue */, expression, isFunction, arg);
return js.evaluateExpression(await this._executionContextPromise, expression, { returnByValue: true, isFunction }, arg);
}
async evaluateExpressionHandle(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, false /* returnByValue */, expression, isFunction, arg);
return js.evaluateExpression(await this._executionContextPromise, expression, { returnByValue: false, isFunction }, arg);
}
}

View file

@ -2392,10 +2392,12 @@ export type FrameDispatchEventResult = void;
export type FrameEvaluateExpressionParams = {
expression: string,
isFunction?: boolean,
exposeUtilityScript?: boolean,
arg: SerializedArgument,
};
export type FrameEvaluateExpressionOptions = {
isFunction?: boolean,
exposeUtilityScript?: boolean,
};
export type FrameEvaluateExpressionResult = {
value: SerializedValue,

View file

@ -1739,6 +1739,7 @@ Frame:
parameters:
expression: string
isFunction: boolean?
exposeUtilityScript: boolean?
arg: SerializedArgument
returns:
value: SerializedValue

View file

@ -702,3 +702,13 @@ it('should work with overridden globalThis.Window/Document/Node', async ({ page,
});
}
});
it('should expose utilityScript', async ({ page }) => {
const result = await (page.mainFrame() as any)._evaluateExposeUtilityScript((utilityScript, { a }) => {
return { utils: 'parseEvaluationResultValue' in utilityScript, a };
}, { a: 42 });
expect(result).toEqual({
a: 42,
utils: true,
});
});