chore: move server-side code to src/server (#411)
This commit is contained in:
parent
e0693654b7
commit
6318b1de75
1
index.d.ts
vendored
1
index.d.ts
vendored
|
|
@ -18,3 +18,4 @@ export * from './lib/api';
|
||||||
export function playwright(browser: 'chromium'): import('./lib/api').ChromiumPlaywright;
|
export function playwright(browser: 'chromium'): import('./lib/api').ChromiumPlaywright;
|
||||||
export function playwright(browser: 'firefox'): import('./lib/api').FirefoxPlaywright;
|
export function playwright(browser: 'firefox'): import('./lib/api').FirefoxPlaywright;
|
||||||
export function playwright(browser: 'webkit'): import('./lib/api').WebKitPlaywright;
|
export function playwright(browser: 'webkit'): import('./lib/api').WebKitPlaywright;
|
||||||
|
export function connect(browser: 'chromium'): import('./lib/api').ChromiumBrowser.connect;
|
||||||
|
|
|
||||||
6
index.js
6
index.js
|
|
@ -33,3 +33,9 @@ module.exports.playwright = browser => {
|
||||||
return new api.WebKitPlaywright(__dirname, packageJson.playwright.webkit_revision);
|
return new api.WebKitPlaywright(__dirname, packageJson.playwright.webkit_revision);
|
||||||
throw new Error(`Unsupported browser "${browser}"`);
|
throw new Error(`Unsupported browser "${browser}"`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.connect = browser => {
|
||||||
|
if (browser === 'chromium')
|
||||||
|
return api.ChromiumBrowser.connect;
|
||||||
|
throw new Error(`Unsupported browser "${browser}"`);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
export { Accessibility } from './accessibility';
|
export { Accessibility } from './accessibility';
|
||||||
export { Browser, BrowserServer } from './browser';
|
export { Browser, BrowserServer } from './browser';
|
||||||
export { BrowserContext } from './browserContext';
|
export { BrowserContext } from './browserContext';
|
||||||
export { BrowserFetcher } from './browserFetcher';
|
|
||||||
export { ConsoleMessage } from './console';
|
export { ConsoleMessage } from './console';
|
||||||
export { Dialog } from './dialog';
|
export { Dialog } from './dialog';
|
||||||
export { ElementHandle } from './dom';
|
export { ElementHandle } from './dom';
|
||||||
|
|
@ -28,6 +27,9 @@ export { JSHandle } from './javascript';
|
||||||
export { Request, Response } from './network';
|
export { Request, Response } from './network';
|
||||||
export { Coverage, FileChooser, Page, Worker } from './page';
|
export { Coverage, FileChooser, Page, Worker } from './page';
|
||||||
|
|
||||||
|
export { BrowserFetcher } from './server/browserFetcher';
|
||||||
|
export { CRPlaywright as ChromiumPlaywright, CRBrowserServer as ChromiumBrowserServer } from './server/crPlaywright';
|
||||||
|
|
||||||
export * from './chromium/crApi';
|
export * from './chromium/crApi';
|
||||||
export * from './firefox/ffApi';
|
export * from './firefox/ffApi';
|
||||||
export * from './webkit/wkApi';
|
export * from './webkit/wkApi';
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,4 @@
|
||||||
|
|
||||||
export { CRBrowser as ChromiumBrowser } from './crBrowser';
|
export { CRBrowser as ChromiumBrowser } from './crBrowser';
|
||||||
export { CRSession as ChromiumSession } from './crConnection';
|
export { CRSession as ChromiumSession } from './crConnection';
|
||||||
export { CRPlaywright as ChromiumPlaywright } from './crPlaywright';
|
|
||||||
export { CRTarget as ChromiumTarget } from './crTarget';
|
export { CRTarget as ChromiumTarget } from './crTarget';
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,16 @@ import * as browser from '../browser';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import * as platform from '../platform';
|
import * as platform from '../platform';
|
||||||
import { ConnectionTransport } from '../transport';
|
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||||
import { readProtocolStream } from './crProtocolHelper';
|
import { readProtocolStream } from './crProtocolHelper';
|
||||||
|
|
||||||
|
export type CRConnectOptions = {
|
||||||
|
slowMo?: number,
|
||||||
|
browserWSEndpoint?: string;
|
||||||
|
browserURL?: string;
|
||||||
|
transport?: ConnectionTransport;
|
||||||
|
};
|
||||||
|
|
||||||
export class CRBrowser extends browser.Browser {
|
export class CRBrowser extends browser.Browser {
|
||||||
_connection: CRConnection;
|
_connection: CRConnection;
|
||||||
_client: CRSession;
|
_client: CRSession;
|
||||||
|
|
@ -42,10 +49,9 @@ export class CRBrowser extends browser.Browser {
|
||||||
private _tracingPath = '';
|
private _tracingPath = '';
|
||||||
private _tracingClient: CRSession | undefined;
|
private _tracingClient: CRSession | undefined;
|
||||||
|
|
||||||
static async create(
|
static async connect(options: CRConnectOptions): Promise<CRBrowser> {
|
||||||
transport: ConnectionTransport) {
|
const transport = await createTransport(options);
|
||||||
const connection = new CRConnection(transport);
|
const connection = new CRConnection(transport);
|
||||||
|
|
||||||
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
|
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
|
||||||
const browser = new CRBrowser(connection, browserContextIds);
|
const browser = new CRBrowser(connection, browserContextIds);
|
||||||
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
|
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
|
||||||
|
|
@ -297,3 +303,25 @@ export class CRBrowser extends browser.Browser {
|
||||||
return !this._connection._closed;
|
return !this._connection._closed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createTransport(options: CRConnectOptions): Promise<ConnectionTransport> {
|
||||||
|
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
|
||||||
|
let transport: ConnectionTransport | undefined;
|
||||||
|
let connectionURL: string = '';
|
||||||
|
if (options.transport) {
|
||||||
|
transport = options.transport;
|
||||||
|
} else if (options.browserWSEndpoint) {
|
||||||
|
connectionURL = options.browserWSEndpoint;
|
||||||
|
transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
|
||||||
|
} else if (options.browserURL) {
|
||||||
|
try {
|
||||||
|
const data = await platform.fetchUrl(new URL('/json/version', options.browserURL).href);
|
||||||
|
connectionURL = JSON.parse(data).webSocketDebuggerUrl;
|
||||||
|
} catch (e) {
|
||||||
|
e.message = `Failed to fetch browser webSocket url from ${options.browserURL}: ` + e.message;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
transport = await platform.createWebSocketTransport(connectionURL);
|
||||||
|
}
|
||||||
|
return SlowMoTransport.wrap(transport, options.slowMo);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,15 @@
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { FFBrowser } from './ffBrowser';
|
import { FFBrowser } from './ffBrowser';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { TimeoutError } from '../errors';
|
import { TimeoutError } from '../errors';
|
||||||
import { WebSocketTransport, SlowMoTransport } from '../transport';
|
import { SlowMoTransport } from '../transport';
|
||||||
import { launchProcess, waitForLine } from '../processLauncher';
|
import { launchProcess, waitForLine } from '../server/processLauncher';
|
||||||
import { BrowserServer } from '../browser';
|
import { BrowserServer } from '../browser';
|
||||||
|
import * as platform from '../platform';
|
||||||
|
|
||||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||||
const writeFileAsync = util.promisify(fs.writeFile);
|
const writeFileAsync = util.promisify(fs.writeFile);
|
||||||
|
|
@ -122,7 +123,7 @@ export class FFLauncher {
|
||||||
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
|
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
|
||||||
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
|
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
|
||||||
const url = match[1];
|
const url = match[1];
|
||||||
const transport = await WebSocketTransport.create(url);
|
const transport = await platform.createWebSocketTransport(url);
|
||||||
browser = await FFBrowser.create(SlowMoTransport.wrap(transport, slowMo));
|
browser = await FFBrowser.create(SlowMoTransport.wrap(transport, slowMo));
|
||||||
await browser._waitForTarget(t => t.type() === 'page');
|
await browser._waitForTarget(t => t.type() === 'page');
|
||||||
return new BrowserServer(browser, launchedProcess, url);
|
return new BrowserServer(browser, launchedProcess, url);
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,13 @@
|
||||||
|
|
||||||
import * as browsers from '../browser';
|
import * as browsers from '../browser';
|
||||||
import { FFBrowser } from './ffBrowser';
|
import { FFBrowser } from './ffBrowser';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../server/browserFetcher';
|
||||||
import { WebSocketTransport, SlowMoTransport } from '../transport';
|
import { SlowMoTransport } from '../transport';
|
||||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||||
import * as Errors from '../errors';
|
import * as Errors from '../errors';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { FFLauncher, createBrowserFetcher } from './ffLauncher';
|
import { FFLauncher, createBrowserFetcher } from './ffLauncher';
|
||||||
|
import * as platform from '../platform';
|
||||||
|
|
||||||
export class FFPlaywright {
|
export class FFPlaywright {
|
||||||
private _projectRoot: string;
|
private _projectRoot: string;
|
||||||
|
|
@ -52,7 +53,7 @@ export class FFPlaywright {
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: { slowMo?: number, browserWSEndpoint: string }): Promise<FFBrowser> {
|
async connect(options: { slowMo?: number, browserWSEndpoint: string }): Promise<FFBrowser> {
|
||||||
const transport = await WebSocketTransport.create(options.browserWSEndpoint);
|
const transport = await platform.createWebSocketTransport(options.browserWSEndpoint);
|
||||||
return FFBrowser.create(SlowMoTransport.wrap(transport, options.slowMo || 0));
|
return FFBrowser.create(SlowMoTransport.wrap(transport, options.slowMo || 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,13 @@ import * as nodeBuffer from 'buffer';
|
||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import * as jpeg from 'jpeg-js';
|
import * as jpeg from 'jpeg-js';
|
||||||
import * as png from 'pngjs';
|
import * as png from 'pngjs';
|
||||||
|
import * as http from 'http';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as NodeWebSocket from 'ws';
|
||||||
|
|
||||||
import { assert, helper } from './helper';
|
import { assert, helper } from './helper';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
import { ConnectionTransport } from './transport';
|
||||||
|
|
||||||
export const isNode = typeof process === 'object' && !!process && typeof process.versions === 'object' && !!process.versions && !!process.versions.node;
|
export const isNode = typeof process === 'object' && !!process && typeof process.versions === 'object' && !!process.versions && !!process.versions.node;
|
||||||
|
|
||||||
|
|
@ -219,3 +223,79 @@ export function pngToJpeg(buffer: Buffer): Buffer {
|
||||||
assert(isNode, 'Converting from png to jpeg is only supported in Node.js');
|
assert(isNode, 'Converting from png to jpeg is only supported in Node.js');
|
||||||
return jpeg.encode(png.PNG.sync.read(buffer)).data;
|
return jpeg.encode(png.PNG.sync.read(buffer)).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nodeFetch(url: string): Promise<string> {
|
||||||
|
let resolve: (url: string) => void;
|
||||||
|
let reject: (e: Error) => void;
|
||||||
|
const promise = new Promise<string>((res, rej) => { resolve = res; reject = rej; });
|
||||||
|
|
||||||
|
const endpointURL = new URL(url);
|
||||||
|
const protocol = endpointURL.protocol === 'https:' ? https : http;
|
||||||
|
const request = protocol.request(endpointURL, res => {
|
||||||
|
let data = '';
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
// Consume response data to free up memory.
|
||||||
|
res.resume();
|
||||||
|
reject(new Error('HTTP ' + res.statusCode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', chunk => data += chunk);
|
||||||
|
res.on('end', () => resolve(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', reject);
|
||||||
|
request.end();
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUrl(url: string): Promise<string> {
|
||||||
|
if (isNode)
|
||||||
|
return nodeFetch(url);
|
||||||
|
return fetch(url).then(response => {
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error('HTTP ' + response.status + ' ' + response.statusText);
|
||||||
|
return response.text();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketTransport implements ConnectionTransport {
|
||||||
|
private _ws: WebSocket;
|
||||||
|
|
||||||
|
onmessage?: (message: string) => void;
|
||||||
|
onclose?: () => void;
|
||||||
|
|
||||||
|
constructor(ws: WebSocket) {
|
||||||
|
this._ws = ws;
|
||||||
|
this._ws.addEventListener('message', event => {
|
||||||
|
if (this.onmessage)
|
||||||
|
this.onmessage.call(null, event.data);
|
||||||
|
});
|
||||||
|
this._ws.addEventListener('close', event => {
|
||||||
|
if (this.onclose)
|
||||||
|
this.onclose.call(null);
|
||||||
|
});
|
||||||
|
// Silently ignore all errors - we don't know what to do with them.
|
||||||
|
this._ws.addEventListener('error', () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(message: string) {
|
||||||
|
this._ws.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._ws.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWebSocketTransport(url: string): Promise<ConnectionTransport> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ws = (isNode ? new NodeWebSocket(url, [], {
|
||||||
|
perMessageDeflate: false,
|
||||||
|
maxPayload: 256 * 1024 * 1024, // 256Mb
|
||||||
|
}) : new WebSocket(url)) as WebSocket;
|
||||||
|
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
|
||||||
|
ws.addEventListener('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,20 @@ import * as extract from 'extract-zip';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as ProxyAgent from 'https-proxy-agent';
|
import * as ProxyAgent from 'https-proxy-agent';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as platform from './platform';
|
import * as platform from '../platform';
|
||||||
// @ts-ignore
|
|
||||||
import { getProxyForUrl } from 'proxy-from-env';
|
import { getProxyForUrl } from 'proxy-from-env';
|
||||||
import * as removeRecursive from 'rimraf';
|
import * as removeRecursive from 'rimraf';
|
||||||
import * as URL from 'url';
|
import * as URL from 'url';
|
||||||
import { assert } from './helper';
|
import { assert } from '../helper';
|
||||||
|
|
||||||
const readdirAsync = platform.promisify(fs.readdir.bind(fs));
|
const readdirAsync = platform.promisify(fs.readdir.bind(fs));
|
||||||
const mkdirAsync = platform.promisify(fs.mkdir.bind(fs));
|
const mkdirAsync = platform.promisify(fs.mkdir.bind(fs));
|
||||||
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
|
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
|
||||||
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
|
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
|
||||||
|
|
||||||
function existsAsync(filePath) {
|
function existsAsync(filePath: string): Promise<boolean> {
|
||||||
let fulfill = null;
|
let fulfill: (exists: boolean) => void;
|
||||||
const promise = new Promise(x => fulfill = x);
|
const promise = new Promise<boolean>(x => fulfill = x);
|
||||||
fs.access(filePath, err => fulfill(!err));
|
fs.access(filePath, err => fulfill(!err));
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
@ -15,25 +15,22 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as http from 'http';
|
|
||||||
import * as https from 'https';
|
|
||||||
import * as URL from 'url';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnProgressCallback } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnProgressCallback } from '../server/browserFetcher';
|
||||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||||
import * as Errors from '../errors';
|
import * as Errors from '../errors';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { ConnectionTransport, WebSocketTransport, SlowMoTransport, PipeTransport } from '../transport';
|
import { CRBrowser, CRConnectOptions, createTransport } from '../chromium/crBrowser';
|
||||||
import { CRBrowser } from './crBrowser';
|
|
||||||
import * as platform from '../platform';
|
import * as platform from '../platform';
|
||||||
import { TimeoutError } from '../errors';
|
import { TimeoutError } from '../errors';
|
||||||
import { launchProcess, waitForLine } from '../processLauncher';
|
import { launchProcess, waitForLine } from '../server/processLauncher';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { CRConnection } from './crConnection';
|
import { CRConnection } from '../chromium/crConnection';
|
||||||
|
import { PipeTransport } from './pipeTransport';
|
||||||
|
|
||||||
export type SlowMoOptions = {
|
export type SlowMoOptions = {
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
|
|
@ -58,24 +55,17 @@ export type LaunchOptions = ChromeArgOptions & SlowMoOptions & {
|
||||||
pipe?: boolean,
|
pipe?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConnectOptions = SlowMoOptions & {
|
|
||||||
browserWSEndpoint?: string;
|
|
||||||
browserURL?: string;
|
|
||||||
transport?: ConnectionTransport;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CRBrowserServer {
|
export class CRBrowserServer {
|
||||||
private _process: ChildProcess;
|
private _process: ChildProcess;
|
||||||
private _connectOptions: ConnectOptions;
|
private _connectOptions: CRConnectOptions;
|
||||||
|
|
||||||
constructor(process: ChildProcess, connectOptions: ConnectOptions) {
|
constructor(process: ChildProcess, connectOptions: CRConnectOptions) {
|
||||||
this._process = process;
|
this._process = process;
|
||||||
this._connectOptions = connectOptions;
|
this._connectOptions = connectOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(): Promise<CRBrowser> {
|
async connect(): Promise<CRBrowser> {
|
||||||
const transport = await createTransport(this._connectOptions);
|
return CRBrowser.connect(this._connectOptions);
|
||||||
return CRBrowser.create(transport);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process(): ChildProcess {
|
process(): ChildProcess {
|
||||||
|
|
@ -86,7 +76,7 @@ export class CRBrowserServer {
|
||||||
return this._connectOptions.browserWSEndpoint || null;
|
return this._connectOptions.browserWSEndpoint || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectOptions(): ConnectOptions {
|
connectOptions(): CRConnectOptions {
|
||||||
return this._connectOptions;
|
return this._connectOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +169,7 @@ export class CRPlaywright {
|
||||||
|
|
||||||
let server: CRBrowserServer | undefined;
|
let server: CRBrowserServer | undefined;
|
||||||
try {
|
try {
|
||||||
let connectOptions: ConnectOptions | undefined;
|
let connectOptions: CRConnectOptions | undefined;
|
||||||
let browserWSEndpoint: string = '';
|
let browserWSEndpoint: string = '';
|
||||||
if (!usePipe) {
|
if (!usePipe) {
|
||||||
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r${this._revision}`);
|
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r${this._revision}`);
|
||||||
|
|
@ -199,9 +189,8 @@ export class CRPlaywright {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<CRBrowser> {
|
async connect(options: CRConnectOptions): Promise<CRBrowser> {
|
||||||
const transport = await createTransport(options);
|
return CRBrowser.connect(options);
|
||||||
return CRBrowser.create(transport);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
executablePath(): string {
|
executablePath(): string {
|
||||||
|
|
@ -328,49 +317,3 @@ const DEFAULT_ARGS = [
|
||||||
'--password-store=basic',
|
'--password-store=basic',
|
||||||
'--use-mock-keychain',
|
'--use-mock-keychain',
|
||||||
];
|
];
|
||||||
|
|
||||||
function getWSEndpoint(browserURL: string): Promise<string> {
|
|
||||||
let resolve: (url: string) => void;
|
|
||||||
let reject: (e: Error) => void;
|
|
||||||
const promise = new Promise<string>((res, rej) => { resolve = res; reject = rej; });
|
|
||||||
|
|
||||||
const endpointURL = URL.resolve(browserURL, '/json/version');
|
|
||||||
const protocol = endpointURL.startsWith('https') ? https : http;
|
|
||||||
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' });
|
|
||||||
const request = protocol.request(requestOptions, res => {
|
|
||||||
let data = '';
|
|
||||||
if (res.statusCode !== 200) {
|
|
||||||
// Consume response data to free up memory.
|
|
||||||
res.resume();
|
|
||||||
reject(new Error('HTTP ' + res.statusCode));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.setEncoding('utf8');
|
|
||||||
res.on('data', chunk => data += chunk);
|
|
||||||
res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl));
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', reject);
|
|
||||||
request.end();
|
|
||||||
|
|
||||||
return promise.catch(e => {
|
|
||||||
e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message;
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createTransport(options: ConnectOptions): Promise<ConnectionTransport> {
|
|
||||||
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
|
|
||||||
let transport: ConnectionTransport | undefined;
|
|
||||||
let connectionURL: string = '';
|
|
||||||
if (options.transport) {
|
|
||||||
transport = options.transport;
|
|
||||||
} else if (options.browserWSEndpoint) {
|
|
||||||
connectionURL = options.browserWSEndpoint;
|
|
||||||
transport = await WebSocketTransport.create(options.browserWSEndpoint);
|
|
||||||
} else if (options.browserURL) {
|
|
||||||
connectionURL = await getWSEndpoint(options.browserURL);
|
|
||||||
transport = await WebSocketTransport.create(connectionURL);
|
|
||||||
}
|
|
||||||
return SlowMoTransport.wrap(transport, options.slowMo);
|
|
||||||
}
|
|
||||||
73
src/server/pipeTransport.ts
Normal file
73
src/server/pipeTransport.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 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 { debugError, helper, RegisteredListener } from '../helper';
|
||||||
|
import { ConnectionTransport } from '../transport';
|
||||||
|
|
||||||
|
export class PipeTransport implements ConnectionTransport {
|
||||||
|
private _pipeWrite: NodeJS.WritableStream;
|
||||||
|
private _pendingMessage = '';
|
||||||
|
private _eventListeners: RegisteredListener[];
|
||||||
|
onmessage?: (message: string) => void;
|
||||||
|
onclose?: () => void;
|
||||||
|
|
||||||
|
constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) {
|
||||||
|
this._pipeWrite = pipeWrite;
|
||||||
|
this._eventListeners = [
|
||||||
|
helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)),
|
||||||
|
helper.addEventListener(pipeRead, 'close', () => {
|
||||||
|
if (this.onclose)
|
||||||
|
this.onclose.call(null);
|
||||||
|
}),
|
||||||
|
helper.addEventListener(pipeRead, 'error', debugError),
|
||||||
|
helper.addEventListener(pipeWrite, 'error', debugError),
|
||||||
|
];
|
||||||
|
this.onmessage = null;
|
||||||
|
this.onclose = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(message: string) {
|
||||||
|
this._pipeWrite.write(message);
|
||||||
|
this._pipeWrite.write('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
_dispatch(buffer: Buffer) {
|
||||||
|
let end = buffer.indexOf('\0');
|
||||||
|
if (end === -1) {
|
||||||
|
this._pendingMessage += buffer.toString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = this._pendingMessage + buffer.toString(undefined, 0, end);
|
||||||
|
if (this.onmessage)
|
||||||
|
this.onmessage.call(null, message);
|
||||||
|
|
||||||
|
let start = end + 1;
|
||||||
|
end = buffer.indexOf('\0', start);
|
||||||
|
while (end !== -1) {
|
||||||
|
if (this.onmessage)
|
||||||
|
this.onmessage.call(null, buffer.toString(undefined, start, end));
|
||||||
|
start = end + 1;
|
||||||
|
end = buffer.indexOf('\0', start);
|
||||||
|
}
|
||||||
|
this._pendingMessage = buffer.toString(undefined, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._pipeWrite = null;
|
||||||
|
helper.removeEventListeners(this._eventListeners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,10 @@
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as stream from 'stream';
|
import * as stream from 'stream';
|
||||||
import * as removeFolder from 'rimraf';
|
import * as removeFolder from 'rimraf';
|
||||||
import { helper } from './helper';
|
import { helper } from '../helper';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from '../errors';
|
||||||
import * as platform from './platform';
|
import * as platform from '../platform';
|
||||||
|
|
||||||
const removeFolderAsync = platform.promisify(removeFolder);
|
const removeFolderAsync = platform.promisify(removeFolder);
|
||||||
|
|
||||||
|
|
@ -15,9 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as WebSocket from 'ws';
|
|
||||||
import { debugError, helper, RegisteredListener } from './helper';
|
|
||||||
|
|
||||||
export interface ConnectionTransport {
|
export interface ConnectionTransport {
|
||||||
send(s: string): void;
|
send(s: string): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
|
|
@ -25,100 +22,6 @@ export interface ConnectionTransport {
|
||||||
onclose?: () => void,
|
onclose?: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebSocketTransport implements ConnectionTransport {
|
|
||||||
private _ws: WebSocket;
|
|
||||||
|
|
||||||
onmessage?: (message: string) => void;
|
|
||||||
onclose?: () => void;
|
|
||||||
|
|
||||||
static create(url: string): Promise<WebSocketTransport> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const ws = new WebSocket(url, [], {
|
|
||||||
perMessageDeflate: false,
|
|
||||||
maxPayload: 256 * 1024 * 1024, // 256Mb
|
|
||||||
});
|
|
||||||
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws, url)));
|
|
||||||
ws.addEventListener('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(ws: WebSocket, url: string) {
|
|
||||||
this._ws = ws;
|
|
||||||
this._ws.addEventListener('message', event => {
|
|
||||||
if (this.onmessage)
|
|
||||||
this.onmessage.call(null, event.data);
|
|
||||||
});
|
|
||||||
this._ws.addEventListener('close', event => {
|
|
||||||
if (this.onclose)
|
|
||||||
this.onclose.call(null);
|
|
||||||
});
|
|
||||||
// Silently ignore all errors - we don't know what to do with them.
|
|
||||||
this._ws.addEventListener('error', () => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
send(message: string) {
|
|
||||||
this._ws.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this._ws.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PipeTransport implements ConnectionTransport {
|
|
||||||
private _pipeWrite: NodeJS.WritableStream;
|
|
||||||
private _pendingMessage = '';
|
|
||||||
private _eventListeners: RegisteredListener[];
|
|
||||||
onmessage?: (message: string) => void;
|
|
||||||
onclose?: () => void;
|
|
||||||
|
|
||||||
constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) {
|
|
||||||
this._pipeWrite = pipeWrite;
|
|
||||||
this._eventListeners = [
|
|
||||||
helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)),
|
|
||||||
helper.addEventListener(pipeRead, 'close', () => {
|
|
||||||
if (this.onclose)
|
|
||||||
this.onclose.call(null);
|
|
||||||
}),
|
|
||||||
helper.addEventListener(pipeRead, 'error', debugError),
|
|
||||||
helper.addEventListener(pipeWrite, 'error', debugError),
|
|
||||||
];
|
|
||||||
this.onmessage = null;
|
|
||||||
this.onclose = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
send(message: string) {
|
|
||||||
this._pipeWrite.write(message);
|
|
||||||
this._pipeWrite.write('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
_dispatch(buffer: Buffer) {
|
|
||||||
let end = buffer.indexOf('\0');
|
|
||||||
if (end === -1) {
|
|
||||||
this._pendingMessage += buffer.toString();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = this._pendingMessage + buffer.toString(undefined, 0, end);
|
|
||||||
if (this.onmessage)
|
|
||||||
this.onmessage.call(null, message);
|
|
||||||
|
|
||||||
let start = end + 1;
|
|
||||||
end = buffer.indexOf('\0', start);
|
|
||||||
while (end !== -1) {
|
|
||||||
if (this.onmessage)
|
|
||||||
this.onmessage.call(null, buffer.toString(undefined, start, end));
|
|
||||||
start = end + 1;
|
|
||||||
end = buffer.indexOf('\0', start);
|
|
||||||
}
|
|
||||||
this._pendingMessage = buffer.toString(undefined, start);
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this._pipeWrite = null;
|
|
||||||
helper.removeEventListeners(this._eventListeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SlowMoTransport {
|
export class SlowMoTransport {
|
||||||
private readonly _delay: number;
|
private readonly _delay: number;
|
||||||
private readonly _delegate: ConnectionTransport;
|
private readonly _delegate: ConnectionTransport;
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,15 @@
|
||||||
|
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { WKBrowser } from './wkBrowser';
|
import { WKBrowser } from './wkBrowser';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions } from '../server/browserFetcher';
|
||||||
import { PipeTransport, SlowMoTransport } from '../transport';
|
import { SlowMoTransport } from '../transport';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { launchProcess } from '../processLauncher';
|
import { launchProcess } from '../server/processLauncher';
|
||||||
import { BrowserServer } from '../browser';
|
import { BrowserServer } from '../browser';
|
||||||
|
import { PipeTransport } from '../server/pipeTransport';
|
||||||
|
|
||||||
const DEFAULT_ARGS = [
|
const DEFAULT_ARGS = [
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import * as browsers from '../browser';
|
import * as browsers from '../browser';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../server/browserFetcher';
|
||||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||||
import * as Errors from '../errors';
|
import * as Errors from '../errors';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,12 @@ function checkSources(sources) {
|
||||||
excludeClasses.add(className);
|
excludeClasses.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!node.getSourceFile().fileName.endsWith('platform.ts')) {
|
const fileName = node.getSourceFile().fileName;
|
||||||
|
if (!fileName.endsWith('platform.ts') && !fileName.includes('src/server/')) {
|
||||||
// Only relative imports.
|
// Only relative imports.
|
||||||
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
||||||
const module = node.moduleSpecifier.text;
|
const module = node.moduleSpecifier.text;
|
||||||
if (!module.startsWith('.')) {
|
if (!module.startsWith('.') || path.resolve(path.dirname(fileName), module).includes('src/server')) {
|
||||||
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.moduleSpecifier.pos);
|
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.moduleSpecifier.pos);
|
||||||
errors.push(`Disallowed import "${module}" at ${node.getSourceFile().fileName}:${lac.line + 1}`);
|
errors.push(`Disallowed import "${module}" at ${node.getSourceFile().fileName}:${lac.line + 1}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue