mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
feat(input): dblclick/trippleclick feature parity (#60)
This commit is contained in:
parent
37a9c17d3e
commit
ef464e447f
@ -587,7 +587,7 @@ const playwright = require('playwright');
|
||||
(async () => {
|
||||
const browser = await playwright.launch();
|
||||
// Store the endpoint to be able to reconnect to Chromium
|
||||
const browserWSEndpoint = browser.wsEndpoint();
|
||||
const browserWSEndpoint = browser.chromium.wsEndpoint();
|
||||
// Disconnect playwright from Chromium
|
||||
browser.disconnect();
|
||||
|
||||
|
@ -21,9 +21,10 @@ import { ExecutionContext } from './ExecutionContext';
|
||||
import { Frame } from './Frame';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { assert, helper } from '../helper';
|
||||
import { ElementHandle, JSHandle, ClickOptions, PointerActionOptions, MultiClickOptions, SelectOption } from './JSHandle';
|
||||
import { ElementHandle, JSHandle } from './JSHandle';
|
||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
||||
const readFileAsync = helper.promisify(fs.readFile);
|
||||
|
||||
export class DOMWorld {
|
||||
@ -280,63 +281,6 @@ export class DOMWorld {
|
||||
}
|
||||
}
|
||||
|
||||
async click(selector: string, options?: ClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async fill(selector: string, value: string) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.fill(value);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string, options?: PointerActionOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.hover(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const result = await handle.select(...values);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise<ElementHandle | null> {
|
||||
return this._waitForSelectorOrXPath(selector, false, options);
|
||||
}
|
||||
|
@ -15,12 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { helper } from '../helper';
|
||||
import { helper, assert } from '../helper';
|
||||
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
||||
import { CDPSession } from './Connection';
|
||||
import { DOMWorld } from './DOMWorld';
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { ClickOptions, ElementHandle, JSHandle, MultiClickOptions, PointerActionOptions, SelectOption } from './JSHandle';
|
||||
import { ElementHandle, JSHandle } from './JSHandle';
|
||||
import { Response } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
@ -141,37 +142,62 @@ export class Frame {
|
||||
}
|
||||
|
||||
async click(selector: string, options?: ClickOptions) {
|
||||
return this._secondaryWorld.click(selector, options);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
return this._secondaryWorld.dblclick(selector, options);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
return this._secondaryWorld.tripleclick(selector, options);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async fill(selector: string, value: string) {
|
||||
return this._secondaryWorld.fill(selector, value);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.fill(value);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string) {
|
||||
return this._secondaryWorld.focus(selector);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string, options?: PointerActionOptions) {
|
||||
return this._secondaryWorld.hover(selector, options);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.hover(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]>{
|
||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const secondaryExecutionContext = await this._secondaryWorld.executionContext();
|
||||
const adoptedValues = values.map(async value => value instanceof ElementHandle ? secondaryExecutionContext._adoptElementHandle(value) : value);
|
||||
return this._secondaryWorld.select(selector, ...(await Promise.all(adoptedValues)));
|
||||
const adoptedValues = await Promise.all(values.map(async value => value instanceof ElementHandle ? secondaryExecutionContext._adoptElementHandle(value) : value));
|
||||
const result = await handle.select(...adoptedValues);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
return this._mainWorld.type(selector, text, options);
|
||||
const handle = await this._secondaryWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: any = {}, ...args: any[]): Promise<JSHandle | null> {
|
||||
|
@ -15,9 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CDPSession } from './Connection';
|
||||
import { assert } from '../helper';
|
||||
import { Modifier, Button } from '../input';
|
||||
import { keyDefinitions } from '../USKeyboardLayout';
|
||||
import { CDPSession } from './Connection';
|
||||
|
||||
type KeyDescription = {
|
||||
keyCode: number,
|
||||
@ -27,11 +28,8 @@ type KeyDescription = {
|
||||
location: number,
|
||||
};
|
||||
|
||||
export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
|
||||
const kModifiers: Modifier[] = ['Alt', 'Control', 'Meta', 'Shift'];
|
||||
|
||||
export type Button = 'left' | 'right' | 'middle';
|
||||
|
||||
export class Keyboard {
|
||||
private _client: CDPSession;
|
||||
_modifiers = 0;
|
||||
|
@ -16,43 +16,21 @@
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import { ClickOptions, Modifier, MultiClickOptions, PointerActionOptions, SelectOption, selectFunction } from '../input';
|
||||
import { CDPSession } from './Connection';
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { Frame } from './Frame';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import { valueFromRemoteObject, releaseObject } from './protocolHelper';
|
||||
import { Page } from './Page';
|
||||
import { Modifier, Button } from './Input';
|
||||
import { Protocol } from './protocol';
|
||||
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
|
||||
type Point = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type PointerActionOptions = {
|
||||
modifiers?: Modifier[];
|
||||
relativePoint?: Point;
|
||||
};
|
||||
|
||||
export type ClickOptions = PointerActionOptions & {
|
||||
delay?: number;
|
||||
button?: Button;
|
||||
clickCount?: number;
|
||||
};
|
||||
|
||||
export type MultiClickOptions = PointerActionOptions & {
|
||||
delay?: number;
|
||||
button?: Button;
|
||||
};
|
||||
|
||||
export type SelectOption = {
|
||||
value?: string;
|
||||
label?: string;
|
||||
index?: number;
|
||||
};
|
||||
|
||||
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
const frame = context.frame();
|
||||
if (remoteObject.subtype === 'node' && frame) {
|
||||
@ -335,33 +313,7 @@ export class ElementHandle extends JSHandle {
|
||||
if (option.index !== undefined)
|
||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||
}
|
||||
return this.evaluate((element: HTMLSelectElement, ...optionsToSelect: (Node | SelectOption)[]) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
const option = options[index];
|
||||
option.selected = optionsToSelect.some(optionToSelect => {
|
||||
if (optionToSelect instanceof Node)
|
||||
return option === optionToSelect;
|
||||
let matches = true;
|
||||
if (optionToSelect.value !== undefined)
|
||||
matches = matches && optionToSelect.value === option.value;
|
||||
if (optionToSelect.label !== undefined)
|
||||
matches = matches && optionToSelect.label === option.label;
|
||||
if (optionToSelect.index !== undefined)
|
||||
matches = matches && optionToSelect.index === index;
|
||||
return matches;
|
||||
});
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, ...options);
|
||||
return this.evaluate(selectFunction, ...options);
|
||||
}
|
||||
|
||||
async fill(value: string): Promise<void> {
|
||||
|
@ -261,7 +261,7 @@ export class Launcher {
|
||||
|
||||
assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
|
||||
|
||||
let connection = null;
|
||||
let connection: Connection = null;
|
||||
if (transport) {
|
||||
connection = new Connection('', transport, slowMo);
|
||||
} else if (browserWSEndpoint) {
|
||||
@ -274,7 +274,9 @@ export class Launcher {
|
||||
}
|
||||
|
||||
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
|
||||
return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError));
|
||||
return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, async () => {
|
||||
connection.rootSession.send('Browser.close').catch(debugError);
|
||||
});
|
||||
}
|
||||
|
||||
_resolveExecutablePath(): { executablePath: string; missingText: string | null; } {
|
||||
|
@ -19,29 +19,30 @@ import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import * as path from 'path';
|
||||
import { Events } from './events';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { Accessibility } from './features/accessibility';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserContext } from './BrowserContext';
|
||||
import { CDPSession, CDPSessionEvents } from './Connection';
|
||||
import { Coverage } from './features/coverage';
|
||||
import { Dialog, DialogType } from './Dialog';
|
||||
import { EmulationManager } from './EmulationManager';
|
||||
import { Events } from './events';
|
||||
import { Accessibility } from './features/accessibility';
|
||||
import { Coverage } from './features/coverage';
|
||||
import { Geolocation } from './features/geolocation';
|
||||
import { Interception } from './features/interception';
|
||||
import { PDF } from './features/pdf';
|
||||
import { Workers } from './features/workers';
|
||||
import { Frame } from './Frame';
|
||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { Keyboard, Mouse } from './Input';
|
||||
import { ClickOptions, createJSHandle, ElementHandle, JSHandle, MultiClickOptions, PointerActionOptions, SelectOption } from './JSHandle';
|
||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { NetworkManagerEvents, Response } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
import { Target } from './Target';
|
||||
import { TaskQueue } from './TaskQueue';
|
||||
import { Geolocation } from './features/geolocation';
|
||||
import { Workers } from './features/workers';
|
||||
import { Interception } from './features/interception';
|
||||
|
||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||
|
||||
|
@ -10,12 +10,12 @@ export class Interception {
|
||||
this._networkManager = networkManager;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._networkManager.setRequestInterception(true);
|
||||
async enable() {
|
||||
await this._networkManager.setRequestInterception(true);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._networkManager.setRequestInterception(false);
|
||||
async disable() {
|
||||
await this._networkManager.setRequestInterception(false);
|
||||
}
|
||||
|
||||
async continue(request: Request, overrides: { url?: string; method?: string; postData?: string; headers?: {[key: string]: string}; } = {}) {
|
||||
|
@ -218,54 +218,6 @@ export class DOMWorld {
|
||||
}
|
||||
}
|
||||
|
||||
async click(selector: string, options: { delay?: number; button?: string; clickCount?: number; } | undefined = {}) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.hover();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
select(selector: string, ...values: Array<string>): Promise<Array<string>> {
|
||||
for (const value of values)
|
||||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
return this.$eval(selector, (element : HTMLSelectElement, values : string[]) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (const option of options) {
|
||||
option.selected = values.includes(option.value);
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, values) as Promise<string[]>;
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined): Promise<ElementHandle> {
|
||||
return this._waitForSelectorOrXPath(selector, false, options);
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ import {ExecutionContext} from './ExecutionContext';
|
||||
import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog';
|
||||
import {DOMWorld} from './DOMWorld';
|
||||
import { JSHandle, ElementHandle } from './JSHandle';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { NetworkManager } from './NetworkManager';
|
||||
import { MultiClickOptions, ClickOptions, SelectOption } from '../input';
|
||||
|
||||
export const FrameManagerEvents = {
|
||||
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
||||
@ -21,7 +24,7 @@ export class FrameManager extends EventEmitter {
|
||||
_page: Page;
|
||||
_networkManager: any;
|
||||
_timeoutSettings: any;
|
||||
_mainFrame: any;
|
||||
_mainFrame: Frame;
|
||||
_frames: Map<string, Frame>;
|
||||
_contextIdToContext: Map<string, ExecutionContext>;
|
||||
_eventListeners: RegisteredListener[];
|
||||
@ -71,7 +74,7 @@ export class FrameManager extends EventEmitter {
|
||||
return this._frames.get(frameId);
|
||||
}
|
||||
|
||||
mainFrame() {
|
||||
mainFrame(): Frame {
|
||||
return this._mainFrame;
|
||||
}
|
||||
|
||||
@ -139,18 +142,19 @@ export class FrameManager extends EventEmitter {
|
||||
export class Frame {
|
||||
_parentFrame: Frame|null = null;
|
||||
private _session: JugglerSession;
|
||||
_page: any;
|
||||
_page: Page;
|
||||
_frameManager: FrameManager;
|
||||
private _networkManager: any;
|
||||
private _timeoutSettings: any;
|
||||
private _networkManager: NetworkManager;
|
||||
private _timeoutSettings: TimeoutSettings;
|
||||
_frameId: string;
|
||||
_url: string = '';
|
||||
private _name: string = '';
|
||||
_children: Set<Frame>;
|
||||
private _detached: boolean;
|
||||
_firedEvents: Set<string>;
|
||||
_mainWorld: any;
|
||||
_lastCommittedNavigationId: any;
|
||||
_mainWorld: DOMWorld;
|
||||
_lastCommittedNavigationId: string;
|
||||
|
||||
constructor(session: JugglerSession, frameManager : FrameManager, networkManager, page: Page, frameId: string, timeoutSettings) {
|
||||
this._session = session;
|
||||
this._page = page;
|
||||
@ -248,20 +252,54 @@ export class Frame {
|
||||
return watchDog.navigationResponse();
|
||||
}
|
||||
|
||||
async click(selector: string, options: { delay?: number; button?: string; clickCount?: number; } | undefined = {}) {
|
||||
return this._mainWorld.click(selector, options);
|
||||
async click(selector: string, options?: ClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const result = await handle.select(...values);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
return this._mainWorld.type(selector, text, options);
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string) {
|
||||
return this._mainWorld.focus(selector);
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string) {
|
||||
return this._mainWorld.hover(selector);
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.hover();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
_detach() {
|
||||
@ -278,10 +316,6 @@ export class Frame {
|
||||
this._firedEvents.clear();
|
||||
}
|
||||
|
||||
select(selector: string, ...values: Array<string>): Promise<Array<string>> {
|
||||
return this._mainWorld.select(selector, ...values);
|
||||
}
|
||||
|
||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: { polling?: string | number; timeout?: number; visible?: boolean; hidden?: boolean; } | undefined, ...args: Array<any>): Promise<JSHandle> {
|
||||
const xPathPattern = '//';
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import { keyDefinitions } from '../USKeyboardLayout';
|
||||
import { JugglerSession } from './Connection';
|
||||
import { Button, ClickOptions, MultiClickOptions } from '../input';
|
||||
|
||||
interface KeyDescription {
|
||||
keyCode: number;
|
||||
@ -186,7 +187,7 @@ export class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
async click(x: number, y: number, options: { delay?: number; button?: string; clickCount?: number; } | undefined = {}) {
|
||||
async click(x: number, y: number, options: ClickOptions = {}) {
|
||||
const {delay = null} = options;
|
||||
if (delay !== null) {
|
||||
await Promise.all([
|
||||
@ -204,6 +205,55 @@ export class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
async dblclick(x: number, y: number, options: MultiClickOptions = {}) {
|
||||
const { delay = null } = options;
|
||||
if (delay !== null) {
|
||||
await this.move(x, y);
|
||||
await this.down({ ...options, clickCount: 1 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.up({ ...options, clickCount: 1 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.down({ ...options, clickCount: 2 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.up({ ...options, clickCount: 2 });
|
||||
} else {
|
||||
await Promise.all([
|
||||
this.move(x, y),
|
||||
this.down({ ...options, clickCount: 1 }),
|
||||
this.up({ ...options, clickCount: 1 }),
|
||||
this.down({ ...options, clickCount: 2 }),
|
||||
this.up({ ...options, clickCount: 2 }),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
async tripleclick(x: number, y: number, options: MultiClickOptions = {}) {
|
||||
const { delay = null } = options;
|
||||
if (delay !== null) {
|
||||
await this.move(x, y);
|
||||
await this.down({ ...options, clickCount: 1 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.up({ ...options, clickCount: 1 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.down({ ...options, clickCount: 2 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.up({ ...options, clickCount: 2 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.down({ ...options, clickCount: 3 });
|
||||
await new Promise(f => setTimeout(f, delay));
|
||||
await this.up({ ...options, clickCount: 3 });
|
||||
} else {
|
||||
await Promise.all([
|
||||
this.move(x, y),
|
||||
this.down({ ...options, clickCount: 1 }),
|
||||
this.up({ ...options, clickCount: 1 }),
|
||||
this.down({ ...options, clickCount: 2 }),
|
||||
this.up({ ...options, clickCount: 2 }),
|
||||
this.down({ ...options, clickCount: 3 }),
|
||||
this.up({ ...options, clickCount: 3 }),
|
||||
]);
|
||||
}
|
||||
}
|
||||
async down(options: { button?: string; clickCount?: number; } | undefined = {}) {
|
||||
const {
|
||||
button = 'left',
|
||||
|
@ -1,17 +1,37 @@
|
||||
import {assert, debugError} from '../helper';
|
||||
/**
|
||||
* Copyright 2019 Google Inc. All rights reserved.
|
||||
* Modifications 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.
|
||||
*/
|
||||
|
||||
import {assert, debugError, helper} from '../helper';
|
||||
import * as path from 'path';
|
||||
import {ExecutionContext} from './ExecutionContext';
|
||||
import {Frame} from './FrameManager';
|
||||
import { JugglerSession } from './Connection';
|
||||
import { MultiClickOptions, ClickOptions, selectFunction, SelectOption } from '../input';
|
||||
|
||||
export class JSHandle {
|
||||
_context: ExecutionContext;
|
||||
_session: any;
|
||||
_executionContextId: any;
|
||||
_objectId: any;
|
||||
_type: any;
|
||||
_subtype: any;
|
||||
protected _session: JugglerSession;
|
||||
private _executionContextId: string;
|
||||
protected _objectId: string;
|
||||
private _type: string;
|
||||
private _subtype: string;
|
||||
_disposed: boolean;
|
||||
_protocolValue: { unserializableValue: any; value: any; objectId: any; };
|
||||
|
||||
constructor(context: ExecutionContext, payload: any) {
|
||||
this._context = context;
|
||||
this._session = this._context._session;
|
||||
@ -31,6 +51,14 @@ export class JSHandle {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
async evaluate(pageFunction: Function | string, ...args: any[]): Promise<(any)> {
|
||||
return await this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
async evaluateHandle(pageFunction: Function | string, ...args: any[]): Promise<JSHandle> {
|
||||
return await this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
if (this._objectId)
|
||||
return 'JSHandle@' + (this._subtype || this._type);
|
||||
@ -268,11 +296,23 @@ export class ElementHandle extends JSHandle {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
async click(options: { delay?: number; button?: string; clickCount?: number; } | undefined) {
|
||||
async click(options?: ClickOptions) {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this._clickablePoint();
|
||||
await this._frame._page.mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
async dblclick(options?: MultiClickOptions): Promise<void> {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this._clickablePoint();
|
||||
await this._frame._page.mouse.dblclick(x, y, options);
|
||||
}
|
||||
|
||||
async tripleclick(options?: MultiClickOptions): Promise<void> {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this._clickablePoint();
|
||||
await this._frame._page.mouse.tripleclick(x, y, options);
|
||||
}
|
||||
|
||||
async uploadFile(...filePaths: Array<string>) {
|
||||
const files = filePaths.map(filePath => path.resolve(filePath));
|
||||
@ -303,6 +343,20 @@ export class ElementHandle extends JSHandle {
|
||||
await this._frame._page.keyboard.press(key, options);
|
||||
}
|
||||
|
||||
async select(...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const options = values.map(value => typeof value === 'object' ? value : { value });
|
||||
for (const option of options) {
|
||||
if (option instanceof ElementHandle)
|
||||
continue;
|
||||
if (option.value !== undefined)
|
||||
assert(helper.isString(option.value), 'Values must be strings. Found value "' + option.value + '" of type "' + (typeof option.value) + '"');
|
||||
if (option.label !== undefined)
|
||||
assert(helper.isString(option.label), 'Labels must be strings. Found label "' + option.label + '" of type "' + (typeof option.label) + '"');
|
||||
if (option.index !== undefined)
|
||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||
}
|
||||
return this.evaluate(selectFunction, ...options);
|
||||
}
|
||||
|
||||
async _clickablePoint(): Promise<{ x: number; y: number; }> {
|
||||
const result = await this._session.send('Page.getContentQuads', {
|
||||
|
@ -10,11 +10,12 @@ import { Dialog } from './Dialog';
|
||||
import { Events } from './events';
|
||||
import { Accessibility } from './features/accessibility';
|
||||
import { Interception } from './features/interception';
|
||||
import { FrameManager, FrameManagerEvents, normalizeWaitUntil } from './FrameManager';
|
||||
import { FrameManager, FrameManagerEvents, normalizeWaitUntil, Frame } from './FrameManager';
|
||||
import { Keyboard, Mouse } from './Input';
|
||||
import { createHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { NavigationWatchdog } from './NavigationWatchdog';
|
||||
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||
import { ClickOptions, MultiClickOptions } from '../input';
|
||||
|
||||
|
||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||
@ -327,7 +328,7 @@ export class Page extends EventEmitter {
|
||||
this.emit(Events.Page.Dialog, new Dialog(this._session, params));
|
||||
}
|
||||
|
||||
mainFrame() {
|
||||
mainFrame(): Frame {
|
||||
return this._frameManager.mainFrame();
|
||||
}
|
||||
|
||||
@ -460,19 +461,27 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
async evaluate(pageFunction, ...args) {
|
||||
return await this._frameManager.mainFrame().evaluate(pageFunction, ...args);
|
||||
return await this.mainFrame().evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
async addScriptTag(options: { content?: string; path?: string; type?: string; url?: string; }): Promise<ElementHandle> {
|
||||
return await this._frameManager.mainFrame().addScriptTag(options);
|
||||
return await this.mainFrame().addScriptTag(options);
|
||||
}
|
||||
|
||||
async addStyleTag(options: { content?: string; path?: string; url?: string; }): Promise<ElementHandle> {
|
||||
return await this._frameManager.mainFrame().addStyleTag(options);
|
||||
return await this.mainFrame().addStyleTag(options);
|
||||
}
|
||||
|
||||
async click(selector: string, options: { delay?: number; button?: string; clickCount?: number; } | undefined = {}) {
|
||||
return await this._frameManager.mainFrame().click(selector, options);
|
||||
async click(selector: string, options?: ClickOptions) {
|
||||
return await this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
return this.mainFrame().dblclick(selector, options);
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
|
@ -10,12 +10,12 @@ export class Interception {
|
||||
this._networkManager = networkManager;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._networkManager.setRequestInterception(true);
|
||||
async enable() {
|
||||
await this._networkManager.setRequestInterception(true);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._networkManager.setRequestInterception(false);
|
||||
async disable() {
|
||||
await this._networkManager.setRequestInterception(false);
|
||||
}
|
||||
|
||||
async continue(request: Request, overrides: { url?: string; method?: string; postData?: string; headers?: {[key: string]: string}; } = {}) {
|
||||
|
63
src/input.ts
Normal file
63
src/input.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { assert } from "console";
|
||||
import { helper } from "./helper";
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export type Modifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
|
||||
export type Button = 'left' | 'right' | 'middle';
|
||||
|
||||
type Point = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type PointerActionOptions = {
|
||||
modifiers?: Modifier[];
|
||||
relativePoint?: Point;
|
||||
};
|
||||
|
||||
export type ClickOptions = PointerActionOptions & {
|
||||
delay?: number;
|
||||
button?: Button;
|
||||
clickCount?: number;
|
||||
};
|
||||
|
||||
export type MultiClickOptions = PointerActionOptions & {
|
||||
delay?: number;
|
||||
button?: Button;
|
||||
};
|
||||
|
||||
export type SelectOption = {
|
||||
value?: string;
|
||||
label?: string;
|
||||
index?: number;
|
||||
};
|
||||
|
||||
export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (Node | SelectOption)[]) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
const option = options[index];
|
||||
option.selected = optionsToSelect.some(optionToSelect => {
|
||||
if (optionToSelect instanceof Node)
|
||||
return option === optionToSelect;
|
||||
let matches = true;
|
||||
if (optionToSelect.value !== undefined)
|
||||
matches = matches && optionToSelect.value === option.value;
|
||||
if (optionToSelect.label !== undefined)
|
||||
matches = matches && optionToSelect.label === option.label;
|
||||
if (optionToSelect.index !== undefined)
|
||||
matches = matches && optionToSelect.index === index;
|
||||
return matches;
|
||||
});
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
};
|
@ -26,6 +26,7 @@ import { ElementHandle, JSHandle } from './JSHandle';
|
||||
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { MultiClickOptions, ClickOptions, SelectOption } from '../input';
|
||||
const readFileAsync = helper.promisify(fs.readFile);
|
||||
|
||||
export const FrameManagerEvents = {
|
||||
@ -46,6 +47,7 @@ export class FrameManager extends EventEmitter {
|
||||
_isolatedWorlds: Set<string>;
|
||||
_sessionListeners: RegisteredListener[];
|
||||
_mainFrame: Frame;
|
||||
|
||||
constructor(session: TargetSession, page: Page, timeoutSettings: TimeoutSettings) {
|
||||
super();
|
||||
this._session = session;
|
||||
@ -519,13 +521,27 @@ export class Frame {
|
||||
return this._detached;
|
||||
}
|
||||
|
||||
async click(selector: string, options: { delay?: number; button?: 'left' | 'right' | 'middle'; clickCount?: number; } | undefined) {
|
||||
async click(selector: string, options?: ClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async fill(selector: string, value: string) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
@ -547,26 +563,12 @@ export class Frame {
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
select(selector: string, ...values: Array<string>): Promise<Array<string>>{
|
||||
for (const value of values)
|
||||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
return this.$eval(selector, (element : HTMLSelectElement, values) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (const option of options) {
|
||||
option.selected = values.includes(option.value);
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, values);
|
||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const result = await handle.select(...values);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
import { TargetSession } from './Connection';
|
||||
import { assert } from '../helper';
|
||||
import { keyDefinitions } from '../USKeyboardLayout';
|
||||
import { MultiClickOptions, ClickOptions } from '../input';
|
||||
|
||||
type KeyDescription = {
|
||||
keyCode: number,
|
||||
@ -215,7 +216,7 @@ export class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
async click(x: number, y: number, options: { delay?: number; button?: Button; clickCount?: number; } = {}) {
|
||||
async click(x: number, y: number, options: ClickOptions = {}) {
|
||||
const {delay = null} = options;
|
||||
if (delay !== null) {
|
||||
await Promise.all([
|
||||
@ -233,7 +234,7 @@ export class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
async dblclick(x: number, y: number, options: { delay?: number; button?: Button; } = {}) {
|
||||
async dblclick(x: number, y: number, options: MultiClickOptions = {}) {
|
||||
const { delay = null } = options;
|
||||
if (delay !== null) {
|
||||
await this.move(x, y);
|
||||
@ -255,7 +256,7 @@ export class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
async tripleclick(x: number, y: number, options: { delay?: number; button?: Button; } = {}) {
|
||||
async tripleclick(x: number, y: number, options: MultiClickOptions = {}) {
|
||||
const { delay = null } = options;
|
||||
if (delay !== null) {
|
||||
await this.move(x, y);
|
||||
|
@ -16,21 +16,15 @@
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import { ClickOptions, MultiClickOptions, selectFunction, SelectOption } from '../input';
|
||||
import { TargetSession } from './Connection';
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { Button } from './Input';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||
|
||||
export type ClickOptions = {
|
||||
delay?: number;
|
||||
button?: Button;
|
||||
clickCount?: number;
|
||||
};
|
||||
|
||||
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
const frame = context.frame();
|
||||
if (remoteObject.subtype === 'node' && frame) {
|
||||
@ -223,24 +217,31 @@ export class ElementHandle extends JSHandle {
|
||||
await this._page.mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
async select(...values: string[]): Promise<string[]> {
|
||||
for (const value of values)
|
||||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
return this.evaluate((element: HTMLSelectElement, values: string[]) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
async dblclick(options?: MultiClickOptions): Promise<void> {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this._clickablePoint();
|
||||
await this._page.mouse.dblclick(x, y, options);
|
||||
}
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (const option of options) {
|
||||
option.selected = values.includes(option.value);
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, values);
|
||||
async tripleclick(options?: MultiClickOptions): Promise<void> {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this._clickablePoint();
|
||||
await this._page.mouse.tripleclick(x, y, options);
|
||||
}
|
||||
|
||||
async select(...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||
const options = values.map(value => typeof value === 'object' ? value : { value });
|
||||
for (const option of options) {
|
||||
if (option instanceof ElementHandle)
|
||||
continue;
|
||||
if (option.value !== undefined)
|
||||
assert(helper.isString(option.value), 'Values must be strings. Found value "' + option.value + '" of type "' + (typeof option.value) + '"');
|
||||
if (option.label !== undefined)
|
||||
assert(helper.isString(option.label), 'Labels must be strings. Found label "' + option.label + '" of type "' + (typeof option.label) + '"');
|
||||
if (option.index !== undefined)
|
||||
assert(helper.isNumber(option.index), 'Indices must be numbers. Found index "' + option.index + '" of type "' + (typeof option.index) + '"');
|
||||
}
|
||||
return this.evaluate(selectFunction, ...options);
|
||||
}
|
||||
|
||||
async fill(value: string): Promise<void> {
|
||||
|
@ -18,19 +18,20 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||
import { ClickOptions, MultiClickOptions } from '../input';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { Browser, BrowserContext } from './Browser';
|
||||
import { TargetSession, TargetSessionEvents } from './Connection';
|
||||
import { Events } from './events';
|
||||
import { Frame, FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||
import { valueFromRemoteObject } from './protocolHelper';
|
||||
import { Keyboard, Mouse } from './Input';
|
||||
import { createJSHandle, ElementHandle, JSHandle, ClickOptions } from './JSHandle';
|
||||
import { Response, NetworkManagerEvents } from './NetworkManager';
|
||||
import { TaskQueue } from './TaskQueue';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { Target } from './Target';
|
||||
import { Browser, BrowserContext } from './Browser';
|
||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { NetworkManagerEvents, Response } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { valueFromRemoteObject } from './protocolHelper';
|
||||
import { Target } from './Target';
|
||||
import { TaskQueue } from './TaskQueue';
|
||||
|
||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||
|
||||
@ -437,6 +438,14 @@ export class Page extends EventEmitter {
|
||||
return this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
||||
dblclick(selector: string, options?: MultiClickOptions) {
|
||||
return this.mainFrame().dblclick(selector, options);
|
||||
}
|
||||
|
||||
tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
hover(selector: string) {
|
||||
return this.mainFrame().hover(selector);
|
||||
}
|
||||
|
@ -43,29 +43,22 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip(WEBKIT)('Browser.target', function() {
|
||||
it('should return browser target', async({browser}) => {
|
||||
const target = browser.target();
|
||||
expect(target.type()).toBe('browser');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Browser.process', function() {
|
||||
it('should return child_process instance', async function({browser}) {
|
||||
const process = await browser.process();
|
||||
expect(process.pid).toBeGreaterThan(0);
|
||||
});
|
||||
it.skip(WEBKIT)('should not return child_process for remote browser', async function({browser}) {
|
||||
const browserWSEndpoint = browser.wsEndpoint();
|
||||
it.skip(WEBKIT || FFOX)('should not return child_process for remote browser', async function({browser}) {
|
||||
const browserWSEndpoint = browser.chromium.wsEndpoint();
|
||||
const remoteBrowser = await playwright.connect({browserWSEndpoint});
|
||||
expect(remoteBrowser.process()).toBe(null);
|
||||
remoteBrowser.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip(WEBKIT)('Browser.isConnected', () => {
|
||||
describe.skip(WEBKIT || FFOX)('Browser.isConnected', () => {
|
||||
it('should set the browser connected state', async({browser}) => {
|
||||
const browserWSEndpoint = browser.wsEndpoint();
|
||||
const browserWSEndpoint = browser.chromium.wsEndpoint();
|
||||
const newBrowser = await playwright.connect({browserWSEndpoint});
|
||||
expect(newBrowser.isConnected()).toBe(true);
|
||||
newBrowser.disconnect();
|
||||
|
@ -145,7 +145,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
||||
const context = await browser.createIncognitoBrowserContext();
|
||||
expect(browser.browserContexts().length).toBe(2);
|
||||
const remoteBrowser = await playwright.connect({
|
||||
browserWSEndpoint: browser.wsEndpoint()
|
||||
browserWSEndpoint: browser.chromium.wsEndpoint()
|
||||
});
|
||||
const contexts = remoteBrowser.browserContexts();
|
||||
expect(contexts.length).toBe(2);
|
||||
|
@ -45,7 +45,7 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
|
||||
const browserURL = 'http://127.0.0.1:21222';
|
||||
|
||||
let error = null;
|
||||
await playwright.connect({browserURL, browserWSEndpoint: originalBrowser.wsEndpoint()}).catch(e => error = e);
|
||||
await playwright.connect({browserURL, browserWSEndpoint: originalBrowser.chromium.wsEndpoint()}).catch(e => error = e);
|
||||
expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport');
|
||||
|
||||
originalBrowser.close();
|
||||
@ -68,7 +68,7 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
|
||||
const options = Object.assign({pipe: true}, defaultBrowserOptions);
|
||||
const browser = await playwright.launch(options);
|
||||
expect((await browser.pages()).length).toBe(1);
|
||||
expect(browser.wsEndpoint()).toBe('');
|
||||
expect(browser.chromium.wsEndpoint()).toBe('');
|
||||
const page = await browser.newPage();
|
||||
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||
await page.close();
|
||||
@ -78,7 +78,7 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
|
||||
const options = Object.assign({}, defaultBrowserOptions);
|
||||
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
|
||||
const browser = await playwright.launch(options);
|
||||
expect(browser.wsEndpoint()).toBe('');
|
||||
expect(browser.chromium.wsEndpoint()).toBe('');
|
||||
const page = await browser.newPage();
|
||||
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||
await page.close();
|
||||
@ -100,7 +100,7 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
await originalBrowser.pages();
|
||||
// 2. Connect a remote browser and connect to first page.
|
||||
const remoteBrowser = await playwright.connect({browserWSEndpoint: originalBrowser.wsEndpoint()});
|
||||
const remoteBrowser = await playwright.connect({browserWSEndpoint: originalBrowser.chromium.wsEndpoint()});
|
||||
const [page] = await remoteBrowser.pages();
|
||||
// 3. Make sure |page.waitForFileChooser()| does not work with multiclient.
|
||||
let error = null;
|
||||
|
@ -91,7 +91,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
||||
await page.click('span');
|
||||
expect(await page.evaluate(() => window.CLICKED)).toBe(42);
|
||||
});
|
||||
it.skip(FFOX || WEBKIT)('should select the text by triple clicking', async({page, server}) => {
|
||||
it('should select the text by triple clicking', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.focus('textarea');
|
||||
const text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
|
||||
@ -185,7 +185,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
||||
await page.click('#button-80');
|
||||
expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked');
|
||||
});
|
||||
it.skip(FFOX || WEBKIT)('should double click the button', async({page, server}) => {
|
||||
it('should double click the button', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.evaluate(() => {
|
||||
window.double = false;
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME, WEBKIT}) {
|
||||
let {describe, xdescribe, fdescribe} = testRunner;
|
||||
const {describe, xdescribe, fdescribe} = testRunner;
|
||||
const {it, fit, xit} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
const iPhone = playwright.devices['iPhone 6'];
|
||||
|
2
test/fixtures/closeme.js
vendored
2
test/fixtures/closeme.js
vendored
@ -1,5 +1,5 @@
|
||||
(async() => {
|
||||
const [, , playwrightRoot, options] = process.argv;
|
||||
const browser = await require(playwrightRoot).launch(JSON.parse(options));
|
||||
console.log(browser.wsEndpoint());
|
||||
console.log(browser.chromium.wsEndpoint());
|
||||
})();
|
||||
|
@ -59,11 +59,11 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
await rmAsync(downloadsFolder);
|
||||
});
|
||||
});
|
||||
describe.skip(WEBKIT)('Browser.disconnect', function() {
|
||||
describe.skip(WEBKIT || FFOX)('Browser.disconnect', function() {
|
||||
it('should reject navigation when browser closes', async({server}) => {
|
||||
server.setRoute('/one-style.css', () => {});
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.wsEndpoint()});
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const page = await remote.newPage();
|
||||
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
||||
await server.waitForRequest('/one-style.css');
|
||||
@ -75,7 +75,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
it('should reject waitForSelector when browser closes', async({server}) => {
|
||||
server.setRoute('/empty.html', () => {});
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.wsEndpoint()});
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const page = await remote.newPage();
|
||||
const watchdog = page.waitForSelector('div', {timeout: 60000}).catch(e => e);
|
||||
remote.disconnect();
|
||||
@ -85,9 +85,9 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
});
|
||||
});
|
||||
describe('Browser.close', function() {
|
||||
it.skip(WEBKIT)('should terminate network waiters', async({context, server}) => {
|
||||
it.skip(WEBKIT || FFOX)('should terminate network waiters', async({context, server}) => {
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.wsEndpoint()});
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const newPage = await remote.newPage();
|
||||
const results = await Promise.all([
|
||||
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
|
||||
@ -275,12 +275,12 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
await browser.close();
|
||||
});
|
||||
});
|
||||
describe.skip(WEBKIT)('Playwright.connect', function() {
|
||||
describe.skip(WEBKIT || FFOX)('Playwright.connect', function() {
|
||||
it('should be able to connect multiple times to the same browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browser = await playwright.connect({
|
||||
...defaultBrowserOptions,
|
||||
browserWSEndpoint: originalBrowser.wsEndpoint()
|
||||
browserWSEndpoint: originalBrowser.chromium.wsEndpoint()
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
expect(await page.evaluate(() => 7 * 8)).toBe(56);
|
||||
@ -294,7 +294,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const remoteBrowser = await playwright.connect({
|
||||
...defaultBrowserOptions,
|
||||
browserWSEndpoint: originalBrowser.wsEndpoint()
|
||||
browserWSEndpoint: originalBrowser.chromium.wsEndpoint()
|
||||
});
|
||||
await Promise.all([
|
||||
utils.waitEvent(originalBrowser, 'disconnected'),
|
||||
@ -303,7 +303,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
});
|
||||
it('should support ignoreHTTPSErrors option', async({httpsServer}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = originalBrowser.wsEndpoint();
|
||||
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
|
||||
|
||||
const browser = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint, ignoreHTTPSErrors: true});
|
||||
const page = await browser.newPage();
|
||||
@ -319,7 +319,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
});
|
||||
it('should be able to reconnect to a disconnected browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = originalBrowser.wsEndpoint();
|
||||
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
|
||||
const page = await originalBrowser.newPage();
|
||||
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
||||
originalBrowser.disconnect();
|
||||
@ -340,7 +340,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
|
||||
it('should be able to connect to the same page simultaneously', async({server}) => {
|
||||
const browserOne = await playwright.launch(defaultBrowserOptions);
|
||||
const browserTwo = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserOne.wsEndpoint() });
|
||||
const browserTwo = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserOne.chromium.wsEndpoint() });
|
||||
const [page1, page2] = await Promise.all([
|
||||
new Promise(x => browserOne.once('targetcreated', target => x(target.page()))),
|
||||
browserTwo.newPage(),
|
||||
@ -386,10 +386,10 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip(WEBKIT)('Browser.Events.disconnected', function() {
|
||||
describe.skip(WEBKIT || FFOX)('Browser.Events.disconnected', function() {
|
||||
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = originalBrowser.wsEndpoint();
|
||||
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
|
||||
const remoteBrowser1 = await playwright.connect({browserWSEndpoint});
|
||||
const remoteBrowser2 = await playwright.connect({browserWSEndpoint});
|
||||
|
||||
|
@ -67,7 +67,7 @@ describe('Playwright-Web', () => {
|
||||
});
|
||||
it('should work over exposed DevTools protocol', async({browser, page, serverConfig}) => {
|
||||
// Expose devtools protocol binding into page.
|
||||
const session = await browser.target().createCDPSession();
|
||||
const session = await browser.chromium.createBrowserCDPSession();
|
||||
const pageInfo = (await session.send('Target.getTargets')).targetInfos.find(info => info.attached);
|
||||
await session.send('Target.exposeDevToolsProtocol', {targetId: pageInfo.targetId});
|
||||
await session.detach();
|
||||
|
@ -8,7 +8,7 @@ async function generateChromeProtocol(revision) {
|
||||
return;
|
||||
const playwright = await require('../../chromium');
|
||||
const browser = await playwright.launch({executablePath: revision.executablePath});
|
||||
const origin = browser.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
|
||||
const origin = browser.chromium.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
|
||||
const page = await browser.newPage();
|
||||
await page.goto(`http://${origin}/json/protocol`);
|
||||
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
|
||||
|
Loading…
Reference in New Issue
Block a user