dgozman comments
This commit is contained in:
parent
fc5898892b
commit
40b64a5b60
|
|
@ -194,11 +194,11 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||
await handle.evaluate(input.setFileInputFunction, files);
|
||||
}
|
||||
|
||||
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
|
||||
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||
objectId: toRemoteObject(handle).objectId,
|
||||
});
|
||||
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
|
||||
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise<dom.ElementHandle<T>>;
|
||||
}
|
||||
|
||||
async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export class Page extends EventEmitter {
|
|||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle<Element> | null> {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
|
|
@ -239,11 +239,11 @@ export class Page extends EventEmitter {
|
|||
return this.mainFrame().$$eval(selector, pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle[]> {
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle<Element>[]> {
|
||||
return this.mainFrame().$$(selector);
|
||||
}
|
||||
|
||||
async $x(expression: string): Promise<dom.ElementHandle[]> {
|
||||
async $x(expression: string): Promise<dom.ElementHandle<Element>[]> {
|
||||
return this.mainFrame().$x(expression);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class Playwright {
|
|||
this._launcher = new Launcher(projectRoot, preferredRevision);
|
||||
}
|
||||
|
||||
launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) | undefined): Promise<Browser> {
|
||||
launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) | undefined): Promise<Browser> {
|
||||
return this._launcher.launch(options);
|
||||
}
|
||||
|
||||
|
|
|
|||
53
src/dom.ts
53
src/dom.ts
|
|
@ -23,10 +23,10 @@ export interface DOMWorldDelegate {
|
|||
screenshot(handle: ElementHandle, options?: any): Promise<string | Buffer>;
|
||||
ensurePointerActionPoint(handle: ElementHandle, relativePoint?: types.Point): Promise<types.Point>;
|
||||
setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise<void>;
|
||||
adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise<ElementHandle>;
|
||||
adoptElementHandle<T extends Node>(handle: ElementHandle<T>, to: DOMWorld): Promise<ElementHandle<T>>;
|
||||
}
|
||||
|
||||
export type ScopedSelector = types.Selector & { scope?: ElementHandle };
|
||||
type ScopedSelector = types.Selector & { scope?: ElementHandle };
|
||||
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
|
||||
|
||||
export class DOMWorld {
|
||||
|
|
@ -59,7 +59,7 @@ export class DOMWorld {
|
|||
return this._injectedPromise;
|
||||
}
|
||||
|
||||
async adoptElementHandle(handle: ElementHandle): Promise<ElementHandle> {
|
||||
async adoptElementHandle<T extends Node>(handle: ElementHandle<T>): Promise<ElementHandle<T>> {
|
||||
assert(handle.executionContext() !== this.context, 'Should not adopt to the same context');
|
||||
return this.delegate.adoptElementHandle(handle, this);
|
||||
}
|
||||
|
|
@ -74,11 +74,11 @@ export class DOMWorld {
|
|||
return { scope: selector.scope, selector: normalizeSelector(selector.selector), visible: selector.visible };
|
||||
}
|
||||
|
||||
async $(selector: string | ScopedSelector): Promise<ElementHandle | null> {
|
||||
async $(selector: string | ScopedSelector): Promise<ElementHandle<Element> | null> {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const handle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
const element = injected.querySelector(selector, scope || document);
|
||||
(injected: Injected, selector: string, scope?: Node, visible?: boolean) => {
|
||||
const element = injected.querySelector(selector, scope as SelectorRoot || document);
|
||||
if (visible === undefined || !element)
|
||||
return element;
|
||||
return injected.isVisible(element) === visible ? element : undefined;
|
||||
|
|
@ -92,11 +92,11 @@ export class DOMWorld {
|
|||
return handle.asElement();
|
||||
}
|
||||
|
||||
async $$(selector: string | ScopedSelector): Promise<ElementHandle[]> {
|
||||
async $$(selector: string | ScopedSelector): Promise<ElementHandle<Element>[]> {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const arrayHandle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
const elements = injected.querySelectorAll(selector, scope || document);
|
||||
(injected: Injected, selector: string, scope?: Node, visible?: boolean) => {
|
||||
const elements = injected.querySelectorAll(selector, scope as SelectorRoot || document);
|
||||
if (visible !== undefined)
|
||||
return elements.filter(element => injected.isVisible(element) === visible);
|
||||
return elements;
|
||||
|
|
@ -130,8 +130,8 @@ export class DOMWorld {
|
|||
$$eval: types.$$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const arrayHandle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
const elements = injected.querySelectorAll(selector, scope || document);
|
||||
(injected: Injected, selector: string, scope?: Node, visible?: boolean) => {
|
||||
const elements = injected.querySelectorAll(selector, scope as SelectorRoot || document);
|
||||
if (visible !== undefined)
|
||||
return elements.filter(element => injected.isVisible(element) === visible);
|
||||
return elements;
|
||||
|
|
@ -144,7 +144,7 @@ export class DOMWorld {
|
|||
}
|
||||
}
|
||||
|
||||
export class ElementHandle extends js.JSHandle {
|
||||
export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
private readonly _world: DOMWorld;
|
||||
|
||||
constructor(context: js.ExecutionContext, remoteObject: any) {
|
||||
|
|
@ -153,7 +153,7 @@ export class ElementHandle extends js.JSHandle {
|
|||
this._world = context._domWorld;
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
asElement(): ElementHandle<T> | null {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -162,13 +162,15 @@ export class ElementHandle extends js.JSHandle {
|
|||
}
|
||||
|
||||
async _scrollIntoViewIfNeeded() {
|
||||
const error = await this.evaluate(async (element, pageJavascriptEnabled) => {
|
||||
if (!element.isConnected)
|
||||
const error = await this.evaluate(async (node: Node, pageJavascriptEnabled: boolean) => {
|
||||
if (!node.isConnected)
|
||||
return 'Node is detached from document';
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
return 'Node is not of type HTMLElement';
|
||||
const element = node as Element;
|
||||
// force-scroll if page's javascript is disabled.
|
||||
if (!pageJavascriptEnabled) {
|
||||
//@ts-ignore because only Chromium still supports 'instant'
|
||||
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -182,8 +184,10 @@ export class ElementHandle extends js.JSHandle {
|
|||
// there are rafs.
|
||||
requestAnimationFrame(() => {});
|
||||
});
|
||||
if (visibleRatio !== 1.0)
|
||||
if (visibleRatio !== 1.0) {
|
||||
//@ts-ignore because only Chromium still supports 'instant'
|
||||
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
||||
}
|
||||
return false;
|
||||
}, this._world.delegate.isJavascriptEnabled());
|
||||
if (error)
|
||||
|
|
@ -242,13 +246,13 @@ export class ElementHandle extends js.JSHandle {
|
|||
}
|
||||
|
||||
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||
const multiple = await this.evaluate((element: Node) => !!(element as HTMLInputElement).multiple);
|
||||
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
||||
await this._world.delegate.setInputFiles(this, await input.loadFiles(files));
|
||||
}
|
||||
|
||||
async focus() {
|
||||
await this.evaluate(element => element.focus());
|
||||
await this.evaluate((element: Node) => (element as HTMLElement).focus());
|
||||
}
|
||||
|
||||
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
|
|
@ -280,7 +284,7 @@ export class ElementHandle extends js.JSHandle {
|
|||
return this._world.$(this._scopedSelector(selector));
|
||||
}
|
||||
|
||||
$$(selector: string | types.Selector): Promise<ElementHandle[]> {
|
||||
$$(selector: string | types.Selector): Promise<ElementHandle<Element>[]> {
|
||||
return this._world.$$(this._scopedSelector(selector));
|
||||
}
|
||||
|
||||
|
|
@ -292,12 +296,15 @@ export class ElementHandle extends js.JSHandle {
|
|||
return this._world.$$eval(this._scopedSelector(selector), pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
$x(expression: string): Promise<ElementHandle[]> {
|
||||
$x(expression: string): Promise<ElementHandle<Element>[]> {
|
||||
return this._world.$$({ scope: this, selector: 'xpath=' + expression });
|
||||
}
|
||||
|
||||
isIntersectingViewport(): Promise<boolean> {
|
||||
return this.evaluate(async element => {
|
||||
return this.evaluate(async (node: Node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw new Error('Node is not of type HTMLElement');
|
||||
const element = node as Element;
|
||||
const visibleRatio = await new Promise(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
|
|
@ -348,7 +355,7 @@ export function waitForSelectorTask(selector: string | ScopedSelector, timeout:
|
|||
return async (domWorld: DOMWorld) => {
|
||||
// TODO: we should not be able to adopt selector scope from a different document - handle this case.
|
||||
const resolved = await domWorld.resolveSelector(selector);
|
||||
return domWorld.context.evaluateHandle((injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined, timeout: number) => {
|
||||
return domWorld.context.evaluateHandle((injected: Injected, selector: string, scope: Node | undefined, visible: boolean | undefined, timeout: number) => {
|
||||
if (visible !== undefined)
|
||||
return injected.pollRaf(predicate, timeout);
|
||||
return injected.pollMutation(predicate, timeout);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import * as types from '../types';
|
|||
import * as frames from '../frames';
|
||||
import { JugglerSession } from './Connection';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
||||
readonly keyboard: input.Keyboard;
|
||||
|
|
@ -138,13 +139,13 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||
await handle.evaluate(input.setFileInputFunction, files);
|
||||
}
|
||||
|
||||
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
|
||||
assert(false, 'Multiple isolated worlds are not implemented');
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
function toRemoteObject(handle: dom.ElementHandle): any {
|
||||
function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject {
|
||||
return handle._remoteObject;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ export class Frame {
|
|||
return context.evaluate(pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle<Element> | null> {
|
||||
const domWorld = await this._mainDOMWorld();
|
||||
return domWorld.$(types.clearSelector(selector));
|
||||
}
|
||||
|
||||
async $x(expression: string): Promise<dom.ElementHandle[]> {
|
||||
async $x(expression: string): Promise<dom.ElementHandle<Element>[]> {
|
||||
const domWorld = await this._mainDOMWorld();
|
||||
return domWorld.$$('xpath=' + expression);
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ export class Frame {
|
|||
return domWorld.$$eval(selector, pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle[]> {
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle<Element>[]> {
|
||||
const domWorld = await this._mainDOMWorld();
|
||||
return domWorld.$$(types.clearSelector(selector));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ class Injected {
|
|||
this.engines.set(engine.name, engine);
|
||||
}
|
||||
|
||||
querySelector(selector: string, root: SelectorRoot): Element | undefined {
|
||||
querySelector(selector: string, root: Node): Element | undefined {
|
||||
const parsed = this._parseSelector(selector);
|
||||
let element = root;
|
||||
if (!root["querySelector"])
|
||||
throw new Error('Node is not queryable.');
|
||||
let element = root as SelectorRoot;
|
||||
for (const { engine, selector } of parsed) {
|
||||
const next = engine.query((element as Element).shadowRoot || element, selector);
|
||||
if (!next)
|
||||
|
|
|
|||
10
src/input.ts
10
src/input.ts
|
|
@ -287,9 +287,10 @@ export class Mouse {
|
|||
}
|
||||
}
|
||||
|
||||
export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (Node | SelectOption)[]) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
export const selectFunction = (node: Node, ...optionsToSelect: (Node | SelectOption)[]) => {
|
||||
if (node.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
const element = node as HTMLSelectElement;
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
|
|
@ -315,9 +316,10 @@ export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (
|
|||
return options.filter(option => option.selected).map(option => option.value);
|
||||
};
|
||||
|
||||
export const fillFunction = (element: HTMLElement) => {
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
export const fillFunction = (node: Node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
return 'Node is not of type HTMLElement';
|
||||
const element = node as HTMLElement;
|
||||
if (element.nodeName.toLowerCase() === 'input') {
|
||||
const input = element as HTMLInputElement;
|
||||
const type = input.getAttribute('type') || '';
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export class ExecutionContext {
|
|||
}
|
||||
}
|
||||
|
||||
export class JSHandle {
|
||||
export class JSHandle<T = any> {
|
||||
readonly _context: ExecutionContext;
|
||||
readonly _remoteObject: any;
|
||||
_disposed = false;
|
||||
|
|
@ -52,11 +52,11 @@ export class JSHandle {
|
|||
return this._context;
|
||||
}
|
||||
|
||||
evaluate: types.EvaluateOn = (pageFunction, ...args) => {
|
||||
evaluate: types.EvaluateOn<T> = (pageFunction, ...args) => {
|
||||
return this._context.evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandleOn = (pageFunction, ...args) => {
|
||||
evaluateHandle: types.EvaluateHandleOn<T> = (pageFunction, ...args) => {
|
||||
return this._context.evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export class JSHandle {
|
|||
return this._context._delegate.getProperties(this);
|
||||
}
|
||||
|
||||
jsonValue(): Promise<any> {
|
||||
jsonValue(): Promise<T> {
|
||||
return this._context._delegate.handleJSONValue(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
16
src/types.ts
16
src/types.ts
|
|
@ -3,17 +3,21 @@
|
|||
|
||||
import * as js from './javascript';
|
||||
import { helper } from './helper';
|
||||
import * as dom from './dom';
|
||||
|
||||
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle };
|
||||
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle<Args[Index]> };
|
||||
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
|
||||
type PageFunctionOn<On, Args extends any[], R = any> = string | ((on: On, ...args: Args) => R | Promise<R>);
|
||||
|
||||
type Handle<T> = T extends Node ? dom.ElementHandle<T> : js.JSHandle<T>;
|
||||
type ElementForSelector<T> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : Element;
|
||||
|
||||
export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateHandle = <Args extends any[]>(pageFunction: PageFunction<Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>;
|
||||
export type $Eval<S = string | Selector> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type $$Eval<S = string | Selector> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateOn = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateHandleOn = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>;
|
||||
export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
|
||||
export type $Eval<O = string | Selector> = <Args extends any[], R, S extends O>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type $$Eval<O = string | Selector> = <Args extends any[], R, S extends O>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
|
||||
|
||||
export type Rect = { x: number, y: number, width: number, height: number };
|
||||
export type Point = { x: number, y: number };
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||
await this._client.send('DOM.setInputFiles', { objectId, files });
|
||||
}
|
||||
|
||||
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.DOMWorld): Promise<dom.ElementHandle<T>> {
|
||||
assert(false, 'Multiple isolated worlds are not implemented');
|
||||
return handle;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue