mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: reduce the number of evaluate methods, improve types (#2454)
Types can now handle non-trivial tuples with handles inside.
This commit is contained in:
parent
9158ca19a0
commit
8e6375f532
@ -264,7 +264,7 @@ export class CRPage implements PageDelegate {
|
||||
}
|
||||
|
||||
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
|
||||
await handle._evaluateInUtility(({ injected, node }, files) =>
|
||||
await handle._evaluateInUtility(([injected, node, files]) =>
|
||||
injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
|
||||
}
|
||||
|
||||
|
||||
66
src/dom.ts
66
src/dom.ts
@ -60,9 +60,19 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||
return null;
|
||||
}
|
||||
|
||||
async doEvaluateInternal(returnByValue: boolean, waitForNavigations: boolean, pageFunction: string | Function, ...args: any[]): Promise<any> {
|
||||
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, !waitForNavigations, async () => {
|
||||
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
|
||||
async evaluateInternal<R>(pageFunction: types.Func0<R>): Promise<R>;
|
||||
async evaluateInternal<Arg, R>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<R>;
|
||||
async evaluateInternal(pageFunction: never, ...args: never[]): Promise<any> {
|
||||
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
|
||||
return this._delegate.evaluate(this, true /* returnByValue */, pageFunction, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
async evaluateHandleInternal<R>(pageFunction: types.Func0<R>): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandleInternal<Arg, R>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandleInternal(pageFunction: never, ...args: never[]): Promise<any> {
|
||||
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
|
||||
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,19 +114,19 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
async _evaluateInMain<R, Arg>(pageFunction: types.FuncOn<{ injected: InjectedScript, node: T }, Arg, R>, arg: Arg): Promise<R> {
|
||||
async _evaluateInMain<R, Arg>(pageFunction: types.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], R>, arg: Arg): Promise<R> {
|
||||
const main = await this._context.frame._mainContext();
|
||||
return main.doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await main.injectedScript(), node: this }, arg);
|
||||
return main.evaluateInternal(pageFunction, [await main.injectedScript(), this, arg]);
|
||||
}
|
||||
|
||||
async _evaluateInUtility<R, Arg>(pageFunction: types.FuncOn<{ injected: InjectedScript, node: T }, Arg, R>, arg: Arg): Promise<R> {
|
||||
async _evaluateInUtility<R, Arg>(pageFunction: types.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], R>, arg: Arg): Promise<R> {
|
||||
const utility = await this._context.frame._utilityContext();
|
||||
return utility.doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await utility.injectedScript(), node: this }, arg);
|
||||
return utility.evaluateInternal(pageFunction, [await utility.injectedScript(), this, arg]);
|
||||
}
|
||||
|
||||
async _evaluateHandleInUtility<R, Arg>(pageFunction: types.FuncOn<{ injected: InjectedScript, node: T }, Arg, R>, arg: Arg): Promise<js.JSHandle<R>> {
|
||||
async _evaluateHandleInUtility<R, Arg>(pageFunction: types.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], R>, arg: Arg): Promise<js.JSHandle<R>> {
|
||||
const utility = await this._context.frame._utilityContext();
|
||||
return utility.doEvaluateInternal(false /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await utility.injectedScript(), node: this }, arg);
|
||||
return utility.evaluateHandleInternal(pageFunction, [await utility.injectedScript(), this, arg]);
|
||||
}
|
||||
|
||||
async ownerFrame(): Promise<frames.Frame | null> {
|
||||
@ -135,14 +145,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
async contentFrame(): Promise<frames.Frame | null> {
|
||||
const isFrameElement = await this._evaluateInUtility(({node}) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {});
|
||||
const isFrameElement = await this._evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {});
|
||||
if (!isFrameElement)
|
||||
return null;
|
||||
return this._page._delegate.getContentFrame(this);
|
||||
}
|
||||
|
||||
async getAttribute(name: string): Promise<string | null> {
|
||||
return this._evaluateInUtility(({node}, name: string) => {
|
||||
return this._evaluateInUtility(([injeced, node, name]) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw new Error('Not an element');
|
||||
const element = node as unknown as Element;
|
||||
@ -151,11 +161,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
async textContent(): Promise<string | null> {
|
||||
return this._evaluateInUtility(({node}) => node.textContent, {});
|
||||
return this._evaluateInUtility(([injected, node]) => node.textContent, {});
|
||||
}
|
||||
|
||||
async innerText(): Promise<string> {
|
||||
return this._evaluateInUtility(({node}) => {
|
||||
return this._evaluateInUtility(([injected, node]) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw new Error('Not an element');
|
||||
if (node.namespaceURI !== 'http://www.w3.org/1999/xhtml')
|
||||
@ -166,7 +176,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
async innerHTML(): Promise<string> {
|
||||
return this._evaluateInUtility(({node}) => {
|
||||
return this._evaluateInUtility(([injected, node]) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw new Error('Not an element');
|
||||
const element = node as unknown as Element;
|
||||
@ -175,7 +185,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
async dispatchEvent(type: string, eventInit: Object = {}) {
|
||||
await this._evaluateInMain(({ injected, node }, { type, eventInit }) =>
|
||||
await this._evaluateInMain(([injected, node, { type, eventInit }]) =>
|
||||
injected.dispatchEvent(node, type, eventInit), { type, eventInit });
|
||||
}
|
||||
|
||||
@ -229,7 +239,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
private async _offsetPoint(offset: types.Point): Promise<types.Point | 'invisible'> {
|
||||
const [box, border] = await Promise.all([
|
||||
this.boundingBox(),
|
||||
this._evaluateInUtility(({ injected, node }) => injected.getElementBorderWidth(node), {}).catch(logError(this._context._logger)),
|
||||
this._evaluateInUtility(([injected, node]) => injected.getElementBorderWidth(node), {}).catch(logError(this._context._logger)),
|
||||
]);
|
||||
if (!box || !border)
|
||||
return 'invisible';
|
||||
@ -353,7 +363,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||
}
|
||||
return this._page._frameManager.waitForSignalsCreatedBy<string[]>(progress, options.noWaitAfter, async () => {
|
||||
const injectedResult = await this._evaluateInUtility(({ injected, node }, selectOptions) => injected.selectOptions(node, selectOptions), selectOptions);
|
||||
const injectedResult = await this._evaluateInUtility(([injected, node, selectOptions]) => injected.selectOptions(node, selectOptions), selectOptions);
|
||||
return handleInjectedResult(injectedResult);
|
||||
});
|
||||
}
|
||||
@ -366,9 +376,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
progress.log(inputLog, `elementHandle.fill("${value}")`);
|
||||
assert(helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
|
||||
const poll = await this._evaluateHandleInUtility(({ injected, node }, { value }) => {
|
||||
const poll = await this._evaluateHandleInUtility(([injected, node, value]) => {
|
||||
return injected.waitForEnabledAndFill(node, value);
|
||||
}, { value });
|
||||
}, value);
|
||||
new InjectedScriptPollHandler(progress, poll);
|
||||
const injectedResult = await poll.evaluate(poll => poll.result);
|
||||
const needsInput = handleInjectedResult(injectedResult);
|
||||
@ -383,7 +393,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
|
||||
async selectText(): Promise<void> {
|
||||
this._page._log(inputLog, `elementHandle.selectText()`);
|
||||
const injectedResult = await this._evaluateInUtility(({ injected, node }) => injected.selectText(node), {});
|
||||
const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.selectText(node), {});
|
||||
handleInjectedResult(injectedResult);
|
||||
}
|
||||
|
||||
@ -393,7 +403,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
|
||||
async _setInputFiles(progress: Progress, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions) {
|
||||
progress.log(inputLog, progress.apiName);
|
||||
const injectedResult = await this._evaluateInUtility(({ node }): types.InjectedScriptResult<boolean> => {
|
||||
const injectedResult = await this._evaluateInUtility(([injected, node]): types.InjectedScriptResult<boolean> => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE || (node as Node as Element).tagName !== 'INPUT')
|
||||
return { status: 'error', error: 'Node is not an HTMLInputElement' };
|
||||
if (!node.isConnected)
|
||||
@ -428,7 +438,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
|
||||
async focus() {
|
||||
this._page._log(inputLog, `elementHandle.focus()`);
|
||||
const injectedResult = await this._evaluateInUtility(({ injected, node }) => injected.focusNode(node), {});
|
||||
const injectedResult = await this._evaluateInUtility(([injected, node]) => injected.focusNode(node), {});
|
||||
handleInjectedResult(injectedResult);
|
||||
}
|
||||
|
||||
@ -471,10 +481,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
async _setChecked(progress: Progress, state: boolean, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
||||
if (await this._evaluateInUtility(({ injected, node }) => injected.isCheckboxChecked(node), {}) === state)
|
||||
if (await this._evaluateInUtility(([injected, node]) => injected.isCheckboxChecked(node), {}) === state)
|
||||
return;
|
||||
await this._click(progress, options);
|
||||
if (await this._evaluateInUtility(({ injected, node }) => injected.isCheckboxChecked(node), {}) !== state)
|
||||
if (await this._evaluateInUtility(([injected, node]) => injected.isCheckboxChecked(node), {}) !== state)
|
||||
throw new Error('Unable to click checkbox');
|
||||
}
|
||||
|
||||
@ -517,9 +527,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
async _waitForDisplayedAtStablePositionAndEnabled(progress: Progress): Promise<void> {
|
||||
progress.log(inputLog, 'waiting for element to be displayed, enabled and not moving...');
|
||||
const rafCount = this._page._delegate.rafCountForStablePosition();
|
||||
const poll = await this._evaluateHandleInUtility(({ injected, node }, { rafCount }) => {
|
||||
const poll = await this._evaluateHandleInUtility(([injected, node, rafCount]) => {
|
||||
return injected.waitForDisplayedAtStablePositionAndEnabled(node, rafCount);
|
||||
}, { rafCount });
|
||||
}, rafCount);
|
||||
new InjectedScriptPollHandler(progress, poll);
|
||||
const injectedResult = await poll.evaluate(poll => poll.result);
|
||||
handleInjectedResult(injectedResult);
|
||||
@ -536,9 +546,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
// Translate from viewport coordinates to frame coordinates.
|
||||
point = { x: point.x - box.x, y: point.y - box.y };
|
||||
}
|
||||
const injectedResult = await this._evaluateInUtility(({ injected, node }, { point }) => {
|
||||
const injectedResult = await this._evaluateInUtility(([injected, node, point]) => {
|
||||
return injected.checkHitTargetAt(node, point);
|
||||
}, { point });
|
||||
}, point);
|
||||
return handleInjectedResult(injectedResult);
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,7 +443,7 @@ export class FFPage implements PageDelegate {
|
||||
}
|
||||
|
||||
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
|
||||
await handle._evaluateInUtility(({ injected, node }, files) =>
|
||||
await handle._evaluateInUtility(([injected, node, files]) =>
|
||||
injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
|
||||
}
|
||||
|
||||
|
||||
@ -45,26 +45,10 @@ export class ExecutionContext {
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
doEvaluateInternal(returnByValue: boolean, waitForNavigations: boolean, pageFunction: string | Function, ...args: any[]): Promise<any> {
|
||||
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
|
||||
}
|
||||
|
||||
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async evaluateInternal<R>(pageFunction: types.Func0<R>): Promise<R>;
|
||||
async evaluateInternal<Arg, R>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<R>;
|
||||
async evaluateInternal(pageFunction: never, ...args: never[]): Promise<any> {
|
||||
return this.doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async evaluateHandleInternal<R>(pageFunction: types.Func0<R>): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandleInternal<Arg, R>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandleInternal(pageFunction: never, ...args: never[]): Promise<any> {
|
||||
return this.doEvaluateInternal(false /* returnByValue */, true /* waitForNavigations */, pageFunction, ...args);
|
||||
}
|
||||
|
||||
utilityScript(): Promise<JSHandle> {
|
||||
if (!this._utilityScriptPromise) {
|
||||
const source = `new (${utilityScriptSource.source})()`;
|
||||
@ -95,13 +79,13 @@ export class JSHandle<T = any> {
|
||||
async evaluate<R, Arg>(pageFunction: types.FuncOn<T, Arg, R>, arg: Arg): Promise<R>;
|
||||
async evaluate<R>(pageFunction: types.FuncOn<T, void, R>, arg?: any): Promise<R>;
|
||||
async evaluate<R, Arg>(pageFunction: types.FuncOn<T, Arg, R>, arg: Arg): Promise<R> {
|
||||
return this._context.doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, this, arg);
|
||||
return this._context._delegate.evaluate(this._context, true /* returnByValue */, pageFunction, this, arg);
|
||||
}
|
||||
|
||||
async evaluateHandle<R, Arg>(pageFunction: types.FuncOn<T, Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandle<R>(pageFunction: types.FuncOn<T, void, R>, arg?: any): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandle<R, Arg>(pageFunction: types.FuncOn<T, Arg, R>, arg: Arg): Promise<types.SmartHandle<R>> {
|
||||
return this._context.doEvaluateInternal(false /* returnByValue */, true /* waitForNavigations */, pageFunction, this, arg);
|
||||
return this._context._delegate.evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
|
||||
}
|
||||
|
||||
async getProperty(propertyName: string): Promise<JSHandle> {
|
||||
|
||||
@ -586,14 +586,16 @@ export class Worker extends EventEmitter {
|
||||
async evaluate<R>(pageFunction: types.Func1<void, R>, arg?: any): Promise<R>;
|
||||
async evaluate<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<R> {
|
||||
assertMaxArguments(arguments.length, 2);
|
||||
return (await this._executionContextPromise).evaluateInternal(pageFunction, arg);
|
||||
const context = await this._executionContextPromise;
|
||||
return context._delegate.evaluate(context, true /* returnByValue */, pageFunction, arg);
|
||||
}
|
||||
|
||||
async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandle<R>(pageFunction: types.Func1<void, R>, arg?: any): Promise<types.SmartHandle<R>>;
|
||||
async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>> {
|
||||
assertMaxArguments(arguments.length, 2);
|
||||
return (await this._executionContextPromise).evaluateHandleInternal(pageFunction, arg);
|
||||
const context = await this._executionContextPromise;
|
||||
return context._delegate.evaluate(context, false /* returnByValue */, pageFunction, arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ export class ElectronApplication extends ExtendedEventEmitter {
|
||||
this._nodeExecutionContext = new js.ExecutionContext(new CRExecutionContext(this._nodeSession, event.context), this._logger);
|
||||
});
|
||||
await this._nodeSession.send('Runtime.enable', {}).catch(e => {});
|
||||
this._nodeElectronHandle = await this._nodeExecutionContext!.evaluateHandleInternal(() => {
|
||||
this._nodeElectronHandle = await this._nodeExecutionContext!._delegate.evaluate(this._nodeExecutionContext!, false /* returnByValue */, () => {
|
||||
// Resolving the race between the debugger and the boot-time script.
|
||||
if ((global as any)._playwrightRun)
|
||||
return (global as any)._playwrightRun();
|
||||
|
||||
@ -22,6 +22,9 @@ type Unboxed<Arg> =
|
||||
Arg extends dom.ElementHandle<infer T> ? T :
|
||||
Arg extends js.JSHandle<infer T> ? T :
|
||||
Arg extends NoHandles<Arg> ? Arg :
|
||||
Arg extends [infer A0] ? [Unboxed<A0>] :
|
||||
Arg extends [infer A0, infer A1] ? [Unboxed<A0>, Unboxed<A1>] :
|
||||
Arg extends [infer A0, infer A1, infer A2] ? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>] :
|
||||
Arg extends Array<infer T> ? Array<Unboxed<T>> :
|
||||
Arg extends object ? { [Key in keyof Arg]: Unboxed<Arg[Key]> } :
|
||||
Arg;
|
||||
|
||||
4
utils/generate_types/overrides.d.ts
vendored
4
utils/generate_types/overrides.d.ts
vendored
@ -28,6 +28,10 @@ type Unboxed<Arg> =
|
||||
Arg extends ElementHandle<infer T> ? T :
|
||||
Arg extends JSHandle<infer T> ? T :
|
||||
Arg extends NoHandles<Arg> ? Arg :
|
||||
Arg extends [infer A0] ? [Unboxed<A0>] :
|
||||
Arg extends [infer A0, infer A1] ? [Unboxed<A0>, Unboxed<A1>] :
|
||||
Arg extends [infer A0, infer A1, infer A2] ? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>] :
|
||||
Arg extends [infer A0, infer A1, infer A2, infer A3] ? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>, Unboxed<A3>] :
|
||||
Arg extends Array<infer T> ? Array<Unboxed<T>> :
|
||||
Arg extends object ? { [Key in keyof Arg]: Unboxed<Arg[Key]> } :
|
||||
Arg;
|
||||
|
||||
@ -409,6 +409,11 @@ playwright.chromium.launch().then(async browser => {
|
||||
const browser = await playwright.webkit.launch();
|
||||
const page = await browser.newPage();
|
||||
const windowHandle = await page.evaluateHandle(() => window);
|
||||
|
||||
function wrap<T>(t: T): [T, string, boolean, number] {
|
||||
return [t, '1', true, 1];
|
||||
}
|
||||
|
||||
{
|
||||
const value = await page.evaluate(() => 1);
|
||||
const assertion: AssertType<number, typeof value> = true;
|
||||
@ -437,6 +442,17 @@ playwright.chromium.launch().then(async browser => {
|
||||
const value = await page.evaluate(([a, b, c]) => ({a, b, c}), [3, '123', true] as const);
|
||||
const assertion: AssertType<{a: 3, b: '123', c: true}, typeof value> = true;
|
||||
}
|
||||
{
|
||||
const handle = await page.evaluateHandle(() => 3);
|
||||
const value = await page.evaluate(([a, b, c, d]) => ({a, b, c, d}), wrap(handle));
|
||||
const assertion: AssertType<{a: number, b: string, c: boolean, d: number}, typeof value> = true;
|
||||
}
|
||||
{
|
||||
const handle = await page.evaluateHandle(() => 3);
|
||||
const h = await page.evaluateHandle(([a, b, c, d]) => ({a, b, c, d}), wrap(handle));
|
||||
const value = await h.evaluate(h => h);
|
||||
const assertion: AssertType<{a: number, b: string, c: boolean, d: number}, typeof value> = true;
|
||||
}
|
||||
|
||||
{
|
||||
const handle = await page.evaluateHandle(() => ([{a: '123'}]));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user