feat(webkit): covert pipe to websocket when asked (#570)
This commit is contained in:
parent
bb4ae87c61
commit
3269358ac8
19
docs/api.md
19
docs/api.md
|
|
@ -173,6 +173,7 @@ Closes browser and all of its pages (if any were opened). The [Browser] object i
|
||||||
Returns the default browser context. The default browser context can not be closed.
|
Returns the default browser context. The default browser context can not be closed.
|
||||||
|
|
||||||
#### browser.disconnect()
|
#### browser.disconnect()
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
Disconnects Playwright from the browser, but leaves the browser process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
|
Disconnects Playwright from the browser, but leaves the browser process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
|
||||||
|
|
||||||
|
|
@ -3603,11 +3604,21 @@ Browser websocket endpoint which can be used as an argument to [firefoxPlaywrigh
|
||||||
* extends: [Playwright]
|
* extends: [Playwright]
|
||||||
|
|
||||||
<!-- GEN:toc -->
|
<!-- GEN:toc -->
|
||||||
|
- [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions)
|
||||||
- [webkitPlaywright.defaultArgs([options])](#webkitplaywrightdefaultargsoptions)
|
- [webkitPlaywright.defaultArgs([options])](#webkitplaywrightdefaultargsoptions)
|
||||||
- [webkitPlaywright.launch([options])](#webkitplaywrightlaunchoptions)
|
- [webkitPlaywright.launch([options])](#webkitplaywrightlaunchoptions)
|
||||||
- [webkitPlaywright.launchServer([options])](#webkitplaywrightlaunchserveroptions)
|
- [webkitPlaywright.launchServer([options])](#webkitplaywrightlaunchserveroptions)
|
||||||
<!-- GEN:stop -->
|
<!-- GEN:stop -->
|
||||||
|
|
||||||
|
#### webkitPlaywright.connect(options)
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
|
||||||
|
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||||
|
- `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use.
|
||||||
|
- returns: <[Promise]<[WebKitBrowser]>>
|
||||||
|
|
||||||
|
This methods attaches Playwright to an existing WebKit instance.
|
||||||
|
|
||||||
#### webkitPlaywright.defaultArgs([options])
|
#### webkitPlaywright.defaultArgs([options])
|
||||||
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
|
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
|
||||||
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
|
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
|
||||||
|
|
@ -3631,6 +3642,7 @@ The default flags that WebKit will be launched with.
|
||||||
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||||
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
|
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
|
||||||
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
|
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
|
||||||
|
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
|
||||||
- returns: <[Promise]<[WebKitBrowser]>> Promise which resolves to browser instance.
|
- returns: <[Promise]<[WebKitBrowser]>> Promise which resolves to browser instance.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3655,6 +3667,7 @@ const browser = await playwright.launch({
|
||||||
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
|
||||||
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
|
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
|
||||||
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
|
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
|
||||||
|
- `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`.
|
||||||
- returns: <[Promise]<[WebKitBrowserServer]>> Promise which resolves to browser server instance.
|
- returns: <[Promise]<[WebKitBrowserServer]>> Promise which resolves to browser server instance.
|
||||||
|
|
||||||
### class: WebKitBrowser
|
### class: WebKitBrowser
|
||||||
|
|
@ -3670,6 +3683,7 @@ WebKit browser instance does not expose WebKit-specific features.
|
||||||
- [webKitBrowserServer.connect()](#webkitbrowserserverconnect)
|
- [webKitBrowserServer.connect()](#webkitbrowserserverconnect)
|
||||||
- [webKitBrowserServer.connectOptions()](#webkitbrowserserverconnectoptions)
|
- [webKitBrowserServer.connectOptions()](#webkitbrowserserverconnectoptions)
|
||||||
- [webKitBrowserServer.process()](#webkitbrowserserverprocess)
|
- [webKitBrowserServer.process()](#webkitbrowserserverprocess)
|
||||||
|
- [webKitBrowserServer.wsEndpoint()](#webkitbrowserserverwsendpoint)
|
||||||
<!-- GEN:stop -->
|
<!-- GEN:stop -->
|
||||||
|
|
||||||
#### webKitBrowserServer.close()
|
#### webKitBrowserServer.close()
|
||||||
|
|
@ -3692,6 +3706,11 @@ This options object can be passed to [webKitPlaywright.connect(options)](#webkit
|
||||||
#### webKitBrowserServer.process()
|
#### webKitBrowserServer.process()
|
||||||
- returns: <?[ChildProcess]> Spawned browser server process.
|
- returns: <?[ChildProcess]> Spawned browser server process.
|
||||||
|
|
||||||
|
#### webKitBrowserServer.wsEndpoint()
|
||||||
|
- returns: <?[string]> Browser websocket url.
|
||||||
|
|
||||||
|
Browser websocket endpoint which can be used as an argument to [webkitPlaywright.connect(options)](#webkitplaywrightconnectoptions).
|
||||||
|
|
||||||
### Working with selectors
|
### Working with selectors
|
||||||
|
|
||||||
Selector describes an element in the page. It can be used to obtain `ElementHandle` (see [page.$()](#pageselector) for example) or shortcut element operations to avoid intermediate handle (see [page.click()](#pageclickselector-options) for example).
|
Selector describes an element in the page. It can be used to obtain `ElementHandle` (see [page.$()](#pageselector) for example) or shortcut element operations to avoid intermediate handle (see [page.click()](#pageclickselector-options) for example).
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"proxy-from-env": "^1.0.0",
|
"proxy-from-env": "^1.0.0",
|
||||||
"rimraf": "^2.6.1",
|
"rimraf": "^2.6.1",
|
||||||
|
"uuid": "^3.4.0",
|
||||||
"ws": "^6.1.0"
|
"ws": "^6.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -56,6 +57,7 @@
|
||||||
"@types/pngjs": "^3.4.0",
|
"@types/pngjs": "^3.4.0",
|
||||||
"@types/proxy-from-env": "^1.0.0",
|
"@types/proxy-from-env": "^1.0.0",
|
||||||
"@types/rimraf": "^2.0.2",
|
"@types/rimraf": "^2.0.2",
|
||||||
|
"@types/uuid": "^3.4.6",
|
||||||
"@types/ws": "^6.0.1",
|
"@types/ws": "^6.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.6.1",
|
"@typescript-eslint/eslint-plugin": "^2.6.1",
|
||||||
"@typescript-eslint/parser": "^2.6.1",
|
"@typescript-eslint/parser": "^2.6.1",
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export interface Browser extends platform.EventEmitterType {
|
||||||
browserContexts(): BrowserContext[];
|
browserContexts(): BrowserContext[];
|
||||||
defaultContext(): BrowserContext;
|
defaultContext(): BrowserContext;
|
||||||
|
|
||||||
disconnect(): void;
|
disconnect(): Promise<void>;
|
||||||
isConnected(): boolean;
|
isConnected(): boolean;
|
||||||
close(): Promise<void>;
|
close(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,8 +299,10 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
||||||
return CRTarget.fromPage(page);
|
return CRTarget.fromPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
async disconnect() {
|
||||||
this._connection.dispose();
|
const disconnected = new Promise(f => this.once(CommonEvents.Browser.Disconnected, f));
|
||||||
|
this._connection.close();
|
||||||
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,6 @@ export class CRConnection extends platform.EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClose() {
|
_onClose() {
|
||||||
if (this._closed)
|
|
||||||
return;
|
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
|
|
@ -92,9 +90,9 @@ export class CRConnection extends platform.EventEmitter {
|
||||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
close() {
|
||||||
this._onClose();
|
if (!this._closed)
|
||||||
this._transport.close();
|
this._transport.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(targetInfo: Protocol.Target.TargetInfo): Promise<CRSession> {
|
async createSession(targetInfo: Protocol.Target.TargetInfo): Promise<CRSession> {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
async disconnect() {
|
||||||
this._connection.dispose();
|
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
||||||
|
this._connection.close();
|
||||||
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,6 @@ export class FFConnection extends platform.EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClose() {
|
_onClose() {
|
||||||
if (this._closed)
|
|
||||||
return;
|
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
|
|
@ -134,9 +132,9 @@ export class FFConnection extends platform.EventEmitter {
|
||||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
close() {
|
||||||
this._onClose();
|
if (!this._closed)
|
||||||
this._transport.close();
|
this._transport.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(targetId: string): Promise<FFSession> {
|
async createSession(targetId: string): Promise<FFSession> {
|
||||||
|
|
|
||||||
|
|
@ -69,5 +69,7 @@ export class PipeTransport implements ConnectionTransport {
|
||||||
close() {
|
close() {
|
||||||
this._pipeWrite = null;
|
this._pipeWrite = null;
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
|
if (this.onclose)
|
||||||
|
this.onclose.call(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
|
||||||
debugLauncher(`[${id}] <kill>`);
|
debugLauncher(`[${id}] <kill>`);
|
||||||
helper.removeEventListeners(listeners);
|
helper.removeEventListeners(listeners);
|
||||||
if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {
|
if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {
|
||||||
// Force kill chrome.
|
// Force kill the browser.
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'win32')
|
if (process.platform === 'win32')
|
||||||
childProcess.execSync(`taskkill /pid ${spawnedProcess.pid} /T /F`);
|
childProcess.execSync(`taskkill /pid ${spawnedProcess.pid} /T /F`);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { BrowserFetcher, BrowserFetcherOptions } from './browserFetcher';
|
||||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||||
import { TimeoutError } from '../errors';
|
import { TimeoutError } from '../errors';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { WKBrowser, createTransport } from '../webkit/wkBrowser';
|
import { WKBrowser } from '../webkit/wkBrowser';
|
||||||
import { WKConnectOptions } from '../webkit/wkBrowser';
|
import { WKConnectOptions } from '../webkit/wkBrowser';
|
||||||
import { execSync, ChildProcess } from 'child_process';
|
import { execSync, ChildProcess } from 'child_process';
|
||||||
import { PipeTransport } from './pipeTransport';
|
import { PipeTransport } from './pipeTransport';
|
||||||
|
|
@ -32,6 +32,9 @@ import * as os from 'os';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
|
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
|
||||||
import { Playwright } from './playwright';
|
import { Playwright } from './playwright';
|
||||||
|
import { ConnectionTransport } from '../transport';
|
||||||
|
import * as ws from 'ws';
|
||||||
|
import * as uuidv4 from 'uuid/v4';
|
||||||
|
|
||||||
export type SlowMoOptions = {
|
export type SlowMoOptions = {
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
|
|
@ -52,6 +55,7 @@ export type LaunchOptions = WebKitArgOptions & SlowMoOptions & {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
dumpio?: boolean,
|
dumpio?: boolean,
|
||||||
env?: {[key: string]: string} | undefined,
|
env?: {[key: string]: string} | undefined,
|
||||||
|
pipe?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WKBrowserServer {
|
export class WKBrowserServer {
|
||||||
|
|
@ -76,6 +80,10 @@ export class WKBrowserServer {
|
||||||
return this._process;
|
return this._process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsEndpoint(): string | null {
|
||||||
|
return this._connectOptions.browserWSEndpoint || null;
|
||||||
|
}
|
||||||
|
|
||||||
connectOptions(): WKConnectOptions {
|
connectOptions(): WKConnectOptions {
|
||||||
return this._connectOptions;
|
return this._connectOptions;
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +118,7 @@ export class WKPlaywright implements Playwright {
|
||||||
handleSIGTERM = true,
|
handleSIGTERM = true,
|
||||||
handleSIGHUP = true,
|
handleSIGHUP = true,
|
||||||
slowMo = 0,
|
slowMo = 0,
|
||||||
|
pipe = false,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const webkitArguments = [];
|
const webkitArguments = [];
|
||||||
|
|
@ -142,7 +151,7 @@ export class WKPlaywright implements Playwright {
|
||||||
if (options.headless !== false)
|
if (options.headless !== false)
|
||||||
webkitArguments.push('--headless');
|
webkitArguments.push('--headless');
|
||||||
|
|
||||||
let connectOptions: WKConnectOptions | undefined = undefined;
|
let transport: PipeTransport | undefined = undefined;
|
||||||
|
|
||||||
const { launchedProcess, gracefullyClose } = await launchProcess({
|
const { launchedProcess, gracefullyClose } = await launchProcess({
|
||||||
executablePath: webkitExecutable!,
|
executablePath: webkitExecutable!,
|
||||||
|
|
@ -155,20 +164,30 @@ export class WKPlaywright implements Playwright {
|
||||||
pipe: true,
|
pipe: true,
|
||||||
tempDir: temporaryUserDataDir || undefined,
|
tempDir: temporaryUserDataDir || undefined,
|
||||||
attemptToGracefullyClose: async () => {
|
attemptToGracefullyClose: async () => {
|
||||||
if (!connectOptions)
|
if (!transport)
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
// We try to gracefully close to prevent crash reporting and core dumps.
|
// We try to gracefully close to prevent crash reporting and core dumps.
|
||||||
const transport = await createTransport(connectOptions);
|
|
||||||
const message = JSON.stringify({method: 'Browser.close', params: {}, id: kBrowserCloseMessageId});
|
const message = JSON.stringify({method: 'Browser.close', params: {}, id: kBrowserCloseMessageId});
|
||||||
transport.send(message);
|
transport.send(message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
|
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
|
||||||
connectOptions = { transport, slowMo };
|
|
||||||
|
let connectOptions: WKConnectOptions;
|
||||||
|
if (!pipe) {
|
||||||
|
const browserWSEndpoint = wrapTransportWithWebSocket(transport);
|
||||||
|
connectOptions = { browserWSEndpoint, slowMo };
|
||||||
|
} else {
|
||||||
|
connectOptions = { transport, slowMo };
|
||||||
|
}
|
||||||
return new WKBrowserServer(launchedProcess, gracefullyClose, connectOptions);
|
return new WKBrowserServer(launchedProcess, gracefullyClose, connectOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async connect(options: WKConnectOptions): Promise<WKBrowser> {
|
||||||
|
return WKBrowser.connect(options);
|
||||||
|
}
|
||||||
|
|
||||||
executablePath(): string {
|
executablePath(): string {
|
||||||
return this._resolveExecutablePath().executablePath;
|
return this._resolveExecutablePath().executablePath;
|
||||||
}
|
}
|
||||||
|
|
@ -252,3 +271,39 @@ function getMacVersion() {
|
||||||
return cachedMacVersion;
|
return cachedMacVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wrapTransportWithWebSocket(transport: ConnectionTransport) {
|
||||||
|
const server = new ws.Server({ port: 0 });
|
||||||
|
let socket: ws | undefined;
|
||||||
|
const guid = uuidv4();
|
||||||
|
|
||||||
|
server.on('connection', (s, req) => {
|
||||||
|
if (req.url !== '/' + guid) {
|
||||||
|
s.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (socket) {
|
||||||
|
s.close(undefined, 'Multiple connections are not supported');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket = s;
|
||||||
|
s.on('message', message => transport.send(Buffer.from(message).toString()));
|
||||||
|
transport.onmessage = message => s.send(message);
|
||||||
|
s.on('close', () => {
|
||||||
|
socket = undefined;
|
||||||
|
transport.onmessage = undefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.onclose = () => {
|
||||||
|
if (socket)
|
||||||
|
socket.close(undefined, 'Browser disconnected');
|
||||||
|
server.close();
|
||||||
|
transport.onmessage = undefined;
|
||||||
|
transport.onclose = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const address = server.address();
|
||||||
|
if (typeof address === 'string')
|
||||||
|
return address + '/' + guid;
|
||||||
|
return 'ws://127.0.0.1:' + address.port + '/' + guid;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
export interface ConnectionTransport {
|
export interface ConnectionTransport {
|
||||||
send(s: string): void;
|
send(s: string): void;
|
||||||
close(): void;
|
close(): void; // Note: calling close is expected to issue onclose at some point.
|
||||||
onmessage?: (message: string) => void,
|
onmessage?: (message: string) => void,
|
||||||
onclose?: () => void,
|
onclose?: () => void,
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +27,6 @@ export class SlowMoTransport {
|
||||||
private readonly _delegate: ConnectionTransport;
|
private readonly _delegate: ConnectionTransport;
|
||||||
private _incomingMessageQueue: string[] = [];
|
private _incomingMessageQueue: string[] = [];
|
||||||
private _dispatchTimerId?: NodeJS.Timer;
|
private _dispatchTimerId?: NodeJS.Timer;
|
||||||
private _closed = false;
|
|
||||||
|
|
||||||
onmessage?: (message: string) => void;
|
onmessage?: (message: string) => void;
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
|
|
@ -60,8 +59,6 @@ export class SlowMoTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dispatchOneMessageFromQueue() {
|
private _dispatchOneMessageFromQueue() {
|
||||||
if (this._closed)
|
|
||||||
return;
|
|
||||||
const message = this._incomingMessageQueue.shift();
|
const message = this._incomingMessageQueue.shift();
|
||||||
try {
|
try {
|
||||||
if (this.onmessage)
|
if (this.onmessage)
|
||||||
|
|
@ -72,11 +69,8 @@ export class SlowMoTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClose() {
|
private _onClose() {
|
||||||
if (this._closed)
|
|
||||||
return;
|
|
||||||
if (this.onclose)
|
if (this.onclose)
|
||||||
this.onclose();
|
this.onclose();
|
||||||
this._closed = true;
|
|
||||||
this._delegate.onmessage = undefined;
|
this._delegate.onmessage = undefined;
|
||||||
this._delegate.onclose = undefined;
|
this._delegate.onclose = undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +80,6 @@ export class SlowMoTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this._closed = true;
|
|
||||||
this._delegate.close();
|
this._delegate.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ import * as platform from '../platform';
|
||||||
|
|
||||||
export type WKConnectOptions = {
|
export type WKConnectOptions = {
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
transport: ConnectionTransport;
|
browserWSEndpoint?: string,
|
||||||
|
transport?: ConnectionTransport,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WKBrowser extends platform.EventEmitter implements Browser {
|
export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||||
|
|
@ -148,12 +149,14 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||||
pageProxy.handleProvisionalLoadFailed(event);
|
pageProxy.handleProvisionalLoadFailed(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
async disconnect() {
|
||||||
throw new Error('Unsupported operation');
|
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
||||||
|
this._connection.close();
|
||||||
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return true;
|
return !this._connection.isClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
|
@ -227,6 +230,11 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTransport(options: WKConnectOptions): Promise<ConnectionTransport> {
|
export async function createTransport(options: WKConnectOptions): Promise<ConnectionTransport> {
|
||||||
assert(!!options.transport, 'Transport must be passed to connect');
|
assert(Number(!!options.browserWSEndpoint) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint or transport must be passed to connect');
|
||||||
return SlowMoTransport.wrap(options.transport, options.slowMo);
|
let transport: ConnectionTransport | undefined;
|
||||||
|
if (options.transport)
|
||||||
|
transport = options.transport;
|
||||||
|
else if (options.browserWSEndpoint)
|
||||||
|
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
|
||||||
|
return SlowMoTransport.wrap(transport!, options.slowMo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,6 @@ export class WKConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClose() {
|
_onClose() {
|
||||||
if (this._closed)
|
|
||||||
return;
|
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
|
|
@ -82,9 +80,13 @@ export class WKConnection {
|
||||||
this._onDisconnect();
|
this._onDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
isClosed() {
|
||||||
this._onClose();
|
return this._closed;
|
||||||
this._transport.close();
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this._closed)
|
||||||
|
this._transport.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,26 +50,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
||||||
remote.close(),
|
remote.close(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('should be able to reconnect to a browser', async({server}) => {
|
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
|
||||||
const browser = await browserServer.connect();
|
|
||||||
const browserWSEndpoint = browserServer.wsEndpoint();
|
|
||||||
const page = await browser.defaultContext().newPage();
|
|
||||||
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
|
||||||
|
|
||||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint});
|
|
||||||
const pages = await remote.defaultContext().pages();
|
|
||||||
const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html');
|
|
||||||
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
|
|
||||||
'http://localhost:<PORT>/frames/nested-frames.html',
|
|
||||||
' http://localhost:<PORT>/frames/frame.html (aframe)',
|
|
||||||
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
|
|
||||||
' http://localhost:<PORT>/frames/frame.html (dos)',
|
|
||||||
' http://localhost:<PORT>/frames/frame.html (uno)',
|
|
||||||
]);
|
|
||||||
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
|
|
||||||
await remote.close();
|
|
||||||
});
|
|
||||||
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
|
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
|
||||||
it('should be able to connect to the same page simultaneously', async({server}) => {
|
it('should be able to connect to the same page simultaneously', async({server}) => {
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
|
@ -84,65 +64,4 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
||||||
await local.close();
|
await local.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Browser.disconnect', function() {
|
|
||||||
it('should reject navigation when browser closes', async({server}) => {
|
|
||||||
server.setRoute('/one-style.css', () => {});
|
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
|
||||||
const local = await browserServer.connect();
|
|
||||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
|
||||||
const page = await remote.defaultContext().newPage();
|
|
||||||
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
|
||||||
await server.waitForRequest('/one-style.css');
|
|
||||||
remote.disconnect();
|
|
||||||
const error = await navigationPromise;
|
|
||||||
expect(error.message).toBe('Navigation failed because browser has disconnected!');
|
|
||||||
await local.close();
|
|
||||||
});
|
|
||||||
it('should reject waitForSelector when browser closes', async({server}) => {
|
|
||||||
server.setRoute('/empty.html', () => {});
|
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
|
||||||
const local = await browserServer.connect();
|
|
||||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
|
||||||
const page = await remote.defaultContext().newPage();
|
|
||||||
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
|
|
||||||
remote.disconnect();
|
|
||||||
const error = await watchdog;
|
|
||||||
expect(error.message).toContain('Protocol error');
|
|
||||||
await local.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Browser.close', function() {
|
|
||||||
it('should terminate network waiters', async({context, server}) => {
|
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
|
||||||
const local = await browserServer.connect();
|
|
||||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
|
||||||
const newPage = await remote.defaultContext().newPage();
|
|
||||||
const results = await Promise.all([
|
|
||||||
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
|
|
||||||
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
|
|
||||||
local.close()
|
|
||||||
]);
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
const message = results[i].message;
|
|
||||||
expect(message).toContain('Target closed');
|
|
||||||
expect(message).not.toContain('Timeout');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Browser.isConnected', () => {
|
|
||||||
it('should set the browser connected state', async () => {
|
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
|
||||||
const local = await browserServer.connect();
|
|
||||||
const browserWSEndpoint = browserServer.wsEndpoint();
|
|
||||||
const newBrowser = await playwright.connect({browserWSEndpoint});
|
|
||||||
expect(newBrowser.isConnected()).toBe(true);
|
|
||||||
newBrowser.disconnect();
|
|
||||||
expect(newBrowser.isConnected()).toBe(false);
|
|
||||||
await browserServer.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -80,4 +80,84 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
||||||
expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']);
|
expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Browser.isConnected', () => {
|
||||||
|
it('should set the browser connected state', async () => {
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
const browserWSEndpoint = browserServer.wsEndpoint();
|
||||||
|
const remote = await playwright.connect({browserWSEndpoint});
|
||||||
|
expect(remote.isConnected()).toBe(true);
|
||||||
|
await remote.disconnect();
|
||||||
|
expect(remote.isConnected()).toBe(false);
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Browser.disconnect', function() {
|
||||||
|
it('should reject navigation when browser closes', async({server}) => {
|
||||||
|
server.setRoute('/one-style.css', () => {});
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||||
|
const page = await remote.defaultContext().newPage();
|
||||||
|
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
||||||
|
await server.waitForRequest('/one-style.css');
|
||||||
|
await remote.disconnect();
|
||||||
|
const error = await navigationPromise;
|
||||||
|
expect(error.message).toBe('Navigation failed because browser has disconnected!');
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
it('should reject waitForSelector when browser closes', async({server}) => {
|
||||||
|
server.setRoute('/empty.html', () => {});
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||||
|
const page = await remote.defaultContext().newPage();
|
||||||
|
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
|
||||||
|
await remote.disconnect();
|
||||||
|
const error = await watchdog;
|
||||||
|
expect(error.message).toContain('Protocol error');
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Browser.close', function() {
|
||||||
|
it('should terminate network waiters', async({context, server}) => {
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||||
|
const newPage = await remote.defaultContext().newPage();
|
||||||
|
const results = await Promise.all([
|
||||||
|
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
|
||||||
|
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
|
||||||
|
browserServer.close()
|
||||||
|
]);
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
const message = results[i].message;
|
||||||
|
expect(message).toContain('Target closed');
|
||||||
|
expect(message).not.toContain('Timeout');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Playwright.connect', function() {
|
||||||
|
it.skip(WEBKIT)('should be able to reconnect to a browser', async({server}) => {
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
const browser = await browserServer.connect();
|
||||||
|
const browserWSEndpoint = browserServer.wsEndpoint();
|
||||||
|
const page = await browser.defaultContext().newPage();
|
||||||
|
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
||||||
|
await browser.disconnect();
|
||||||
|
|
||||||
|
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint});
|
||||||
|
const pages = await remote.defaultContext().pages();
|
||||||
|
const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html');
|
||||||
|
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
|
||||||
|
'http://localhost:<PORT>/frames/nested-frames.html',
|
||||||
|
' http://localhost:<PORT>/frames/frame.html (aframe)',
|
||||||
|
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
|
||||||
|
' http://localhost:<PORT>/frames/frame.html (dos)',
|
||||||
|
' http://localhost:<PORT>/frames/frame.html (uno)',
|
||||||
|
]);
|
||||||
|
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports.describe = function ({ testRunner, expect, playwright }) {
|
module.exports.describe = function ({ testRunner, expect, playwright, defaultBrowserOptions }) {
|
||||||
const {describe, xdescribe, fdescribe} = testRunner;
|
const {describe, xdescribe, fdescribe} = testRunner;
|
||||||
const {it, fit, xit, dit} = testRunner;
|
const {it, fit, xit, dit} = testRunner;
|
||||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||||
|
|
@ -26,5 +26,48 @@ module.exports.describe = function ({ testRunner, expect, playwright }) {
|
||||||
expect(playwright.defaultArgs().length).toBe(0);
|
expect(playwright.defaultArgs().length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Playwright.launch |pipe| option', function() {
|
||||||
|
it('should have websocket by default', async() => {
|
||||||
|
const options = Object.assign({pipe: false}, defaultBrowserOptions);
|
||||||
|
const browserServer = await playwright.launchServer(options);
|
||||||
|
const browser = await browserServer.connect();
|
||||||
|
expect((await browser.defaultContext().pages()).length).toBe(1);
|
||||||
|
expect(browserServer.wsEndpoint()).not.toBe(null);
|
||||||
|
const page = await browser.defaultContext().newPage();
|
||||||
|
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||||
|
await page.close();
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
it('should support the pipe option', async() => {
|
||||||
|
const options = Object.assign({pipe: true}, defaultBrowserOptions);
|
||||||
|
const browserServer = await playwright.launchServer(options);
|
||||||
|
const browser = await browserServer.connect();
|
||||||
|
expect((await browser.defaultContext().pages()).length).toBe(1);
|
||||||
|
expect(browserServer.wsEndpoint()).toBe(null);
|
||||||
|
const page = await browser.defaultContext().newPage();
|
||||||
|
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||||
|
await page.close();
|
||||||
|
await browserServer.close();
|
||||||
|
});
|
||||||
|
it('should fire "disconnected" when closing with pipe', async() => {
|
||||||
|
const options = Object.assign({pipe: true}, defaultBrowserOptions);
|
||||||
|
const browserServer = await playwright.launchServer(options);
|
||||||
|
const browser = await browserServer.connect();
|
||||||
|
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
|
||||||
|
// Emulate user exiting browser.
|
||||||
|
process.kill(-browserServer.process().pid, 'SIGKILL');
|
||||||
|
await disconnectedEventPromise;
|
||||||
|
});
|
||||||
|
it('should fire "disconnected" when closing with websocket', async() => {
|
||||||
|
const options = Object.assign({pipe: false}, defaultBrowserOptions);
|
||||||
|
const browserServer = await playwright.launchServer(options);
|
||||||
|
const browser = await browserServer.connect();
|
||||||
|
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
|
||||||
|
// Emulate user exiting browser.
|
||||||
|
process.kill(-browserServer.process().pid, 'SIGKILL');
|
||||||
|
await disconnectedEventPromise;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue