2020-06-25 16:05:36 -07:00
|
|
|
/**
|
|
|
|
* Copyright (c) Microsoft Corporation.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
import * as channels from '../protocol/channels';
|
2020-06-25 16:05:36 -07:00
|
|
|
import { Frame } from './frame';
|
2020-12-26 17:05:57 -08:00
|
|
|
import { JSHandle, serializeArgument, parseResult } from './jsHandle';
|
2020-07-10 15:11:47 -07:00
|
|
|
import { ChannelOwner } from './channelOwner';
|
2020-07-29 17:26:59 -07:00
|
|
|
import { SelectOption, FilePayload, Rect, SelectOptionOptions } from './types';
|
2020-08-18 17:32:11 -07:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as mime from 'mime';
|
|
|
|
import * as path from 'path';
|
|
|
|
import * as util from 'util';
|
2020-08-22 15:13:51 -07:00
|
|
|
import { assert, isString, mkdirIfNeeded } from '../utils/utils';
|
2020-12-26 17:05:57 -08:00
|
|
|
import * as api from '../../types/types';
|
|
|
|
import * as structs from '../../types/structs';
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-08-18 18:46:56 -07:00
|
|
|
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements api.ElementHandle {
|
2020-08-24 17:05:16 -07:00
|
|
|
readonly _elementChannel: channels.ElementHandleChannel;
|
2020-06-25 16:05:36 -07:00
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
static from(handle: channels.ElementHandleChannel): ElementHandle {
|
2020-07-01 18:36:09 -07:00
|
|
|
return (handle as any)._object;
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
static fromNullable(handle: channels.ElementHandleChannel | undefined): ElementHandle | null {
|
2020-06-25 16:05:36 -07:00
|
|
|
return handle ? ElementHandle.from(handle) : null;
|
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.JSHandleInitializer) {
|
2020-07-10 18:00:10 -07:00
|
|
|
super(parent, type, guid, initializer);
|
2020-08-24 17:05:16 -07:00
|
|
|
this._elementChannel = this._channel as channels.ElementHandleChannel;
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
asElement(): T extends Node ? ElementHandle<T> : null {
|
|
|
|
return this as any;
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async ownerFrame(): Promise<Frame | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.ownerFrame', async () => {
|
|
|
|
return Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async contentFrame(): Promise<Frame | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.contentFrame', async () => {
|
|
|
|
return Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async getAttribute(name: string): Promise<string | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.getAttribute', async () => {
|
2020-07-20 17:38:06 -07:00
|
|
|
const value = (await this._elementChannel.getAttribute({ name })).value;
|
|
|
|
return value === undefined ? null : value;
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async textContent(): Promise<string | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.textContent', async () => {
|
2020-07-20 17:38:06 -07:00
|
|
|
const value = (await this._elementChannel.textContent()).value;
|
|
|
|
return value === undefined ? null : value;
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async innerText(): Promise<string> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.innerText', async () => {
|
|
|
|
return (await this._elementChannel.innerText()).value;
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async innerHTML(): Promise<string> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.innerHTML', async () => {
|
|
|
|
return (await this._elementChannel.innerHTML()).value;
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2021-01-08 17:36:17 -08:00
|
|
|
async isChecked(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isChecked', async () => {
|
|
|
|
return (await this._elementChannel.isChecked()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-08 12:27:54 -08:00
|
|
|
async isDisabled(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isDisabled', async () => {
|
|
|
|
return (await this._elementChannel.isDisabled()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async isEditable(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isEditable', async () => {
|
|
|
|
return (await this._elementChannel.isEditable()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async isEnabled(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isEnabled', async () => {
|
|
|
|
return (await this._elementChannel.isEnabled()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async isHidden(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isHidden', async () => {
|
|
|
|
return (await this._elementChannel.isHidden()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async isVisible(): Promise<boolean> {
|
|
|
|
return this._wrapApiCall('elementHandle.isVisible', async () => {
|
|
|
|
return (await this._elementChannel.isVisible()).value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-25 16:05:36 -07:00
|
|
|
async dispatchEvent(type: string, eventInit: Object = {}) {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.dispatchEvent', async () => {
|
2020-07-17 09:53:13 -07:00
|
|
|
await this._elementChannel.dispatchEvent({ type, eventInit: serializeArgument(eventInit) });
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.scrollIntoViewIfNeeded', async () => {
|
|
|
|
await this._elementChannel.scrollIntoViewIfNeeded(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async hover(options: channels.ElementHandleHoverOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.hover', async () => {
|
|
|
|
await this._elementChannel.hover(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async click(options: channels.ElementHandleClickOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.click', async () => {
|
|
|
|
return await this._elementChannel.click(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async dblclick(options: channels.ElementHandleDblclickOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.dblclick', async () => {
|
|
|
|
return await this._elementChannel.dblclick(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-10-19 10:07:33 -07:00
|
|
|
async tap(options: channels.ElementHandleTapOptions = {}): Promise<void> {
|
|
|
|
return this._wrapApiCall('elementHandle.tap', async () => {
|
|
|
|
return await this._elementChannel.tap(options);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.selectOption', async () => {
|
|
|
|
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
|
|
|
|
return result.values;
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async fill(value: string, options: channels.ElementHandleFillOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.fill', async () => {
|
|
|
|
return await this._elementChannel.fill({ value, ...options });
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.selectText', async () => {
|
|
|
|
await this._elementChannel.selectText(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async setInputFiles(files: string | FilePayload | string[] | FilePayload[], options: channels.ElementHandleSetInputFilesOptions = {}) {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.setInputFiles', async () => {
|
|
|
|
await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options });
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async focus(): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.focus', async () => {
|
|
|
|
await this._elementChannel.focus();
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async type(text: string, options: channels.ElementHandleTypeOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.type', async () => {
|
|
|
|
await this._elementChannel.type({ text, ...options });
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise<void> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.press', async () => {
|
|
|
|
await this._elementChannel.press({ key, ...options });
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async check(options: channels.ElementHandleCheckOptions = {}) {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.check', async () => {
|
|
|
|
return await this._elementChannel.check(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async uncheck(options: channels.ElementHandleUncheckOptions = {}) {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.uncheck', async () => {
|
|
|
|
return await this._elementChannel.uncheck(options);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-07-29 17:26:59 -07:00
|
|
|
async boundingBox(): Promise<Rect | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.boundingBox', async () => {
|
2020-07-20 17:38:06 -07:00
|
|
|
const value = (await this._elementChannel.boundingBox()).value;
|
|
|
|
return value === undefined ? null : value;
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async screenshot(options: channels.ElementHandleScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.screenshot', async () => {
|
2020-10-27 00:02:35 -07:00
|
|
|
const copy = { ...options };
|
|
|
|
if (!copy.type)
|
|
|
|
copy.type = determineScreenshotType(options);
|
|
|
|
const result = await this._elementChannel.screenshot(copy);
|
2020-08-18 18:46:56 -07:00
|
|
|
const buffer = Buffer.from(result.binary, 'base64');
|
|
|
|
if (options.path) {
|
|
|
|
await mkdirIfNeeded(options.path);
|
|
|
|
await fsWriteFileAsync(options.path, buffer);
|
|
|
|
}
|
|
|
|
return buffer;
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.$', async () => {
|
2020-12-26 17:05:57 -08:00
|
|
|
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle<SVGElement | HTMLElement> | null;
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.$$', async () => {
|
|
|
|
const result = await this._elementChannel.querySelectorAll({ selector });
|
2020-12-26 17:05:57 -08:00
|
|
|
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<SVGElement | HTMLElement>);
|
2020-07-15 14:04:39 -07:00
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.$eval', async () => {
|
|
|
|
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
|
|
|
|
return parseResult(result.value);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> {
|
2020-07-15 14:04:39 -07:00
|
|
|
return this._wrapApiCall('elementHandle.$$eval', async () => {
|
|
|
|
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
|
|
|
|
return parseResult(result.value);
|
|
|
|
});
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
2020-08-14 14:47:24 -07:00
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled', options: channels.ElementHandleWaitForElementStateOptions = {}): Promise<void> {
|
2020-08-17 16:22:34 -07:00
|
|
|
return this._wrapApiCall('elementHandle.waitForElementState', async () => {
|
|
|
|
return await this._elementChannel.waitForElementState({ state, ...options });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
|
|
|
|
waitForSelector(selector: string, options?: channels.ElementHandleWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
|
|
|
|
async waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
|
2020-08-14 14:47:24 -07:00
|
|
|
return this._wrapApiCall('elementHandle.waitForSelector', async () => {
|
|
|
|
const result = await this._elementChannel.waitForSelector({ selector, ...options });
|
2020-12-26 17:05:57 -08:00
|
|
|
return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;
|
2020-08-14 14:47:24 -07:00
|
|
|
});
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
}
|
2020-06-26 11:51:47 -07:00
|
|
|
|
2020-12-26 17:05:57 -08:00
|
|
|
export function convertSelectOptionValues(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null): { elements?: channels.ElementHandleChannel[], options?: SelectOption[] } {
|
2020-08-18 16:44:17 -07:00
|
|
|
if (values === null)
|
2020-07-10 15:39:11 -07:00
|
|
|
return {};
|
|
|
|
if (!Array.isArray(values))
|
|
|
|
values = [ values as any ];
|
|
|
|
if (!values.length)
|
|
|
|
return {};
|
2020-07-21 15:25:31 -07:00
|
|
|
for (let i = 0; i < values.length; i++)
|
|
|
|
assert(values[i] !== null, `options[${i}]: expected object, got null`);
|
2020-07-10 15:39:11 -07:00
|
|
|
if (values[0] instanceof ElementHandle)
|
|
|
|
return { elements: (values as ElementHandle[]).map((v: ElementHandle) => v._elementChannel) };
|
2020-08-22 07:07:13 -07:00
|
|
|
if (isString(values[0]))
|
2020-07-10 15:39:11 -07:00
|
|
|
return { options: (values as string[]).map(value => ({ value })) };
|
2020-07-29 17:26:59 -07:00
|
|
|
return { options: values as SelectOption[] };
|
2020-07-10 15:39:11 -07:00
|
|
|
}
|
|
|
|
|
2020-08-24 17:05:16 -07:00
|
|
|
type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files'];
|
2020-08-18 17:32:11 -07:00
|
|
|
export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[]): Promise<SetInputFilesFiles> {
|
|
|
|
const items: (string | FilePayload)[] = Array.isArray(files) ? files : [ files ];
|
|
|
|
const filePayloads: SetInputFilesFiles = await Promise.all(items.map(async item => {
|
|
|
|
if (typeof item === 'string') {
|
|
|
|
return {
|
|
|
|
name: path.basename(item),
|
|
|
|
mimeType: mime.getType(item) || 'application/octet-stream',
|
|
|
|
buffer: (await util.promisify(fs.readFile)(item)).toString('base64')
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
name: item.name,
|
|
|
|
mimeType: item.mimeType,
|
|
|
|
buffer: item.buffer.toString('base64'),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
return filePayloads;
|
2020-06-26 11:51:47 -07:00
|
|
|
}
|
2020-08-18 18:46:56 -07:00
|
|
|
|
|
|
|
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
|
|
|
if (options.path) {
|
|
|
|
const mimeType = mime.getType(options.path);
|
|
|
|
if (mimeType === 'image/png')
|
|
|
|
return 'png';
|
|
|
|
else if (mimeType === 'image/jpeg')
|
|
|
|
return 'jpeg';
|
|
|
|
throw new Error(`path: unsupported mime type "${mimeType}"`);
|
|
|
|
}
|
|
|
|
return options.type;
|
|
|
|
}
|