feat(input): dblclick/trippleclick feature parity (#60)

This commit is contained in:
Pavel Feldman 2019-11-22 14:46:34 -08:00 committed by GitHub
parent 37a9c17d3e
commit ef464e447f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 405 additions and 314 deletions

View File

@ -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();

View File

@ -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);
}

View File

@ -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> {

View File

@ -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;

View File

@ -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> {

View File

@ -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; } {

View File

@ -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);

View File

@ -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}; } = {}) {

View File

@ -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);
}

View File

@ -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 = '//';

View File

@ -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',

View File

@ -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', {

View File

@ -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) {

View File

@ -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
View 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);
};

View File

@ -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) {

View File

@ -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);

View File

@ -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> {

View File

@ -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);
}

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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'];

View File

@ -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());
})();

View File

@ -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});

View File

@ -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();

View File

@ -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));