chore(rpc): support workers, file chooser, browser server (#2766)

This commit is contained in:
Pavel Feldman 2020-06-30 10:55:11 -07:00 committed by GitHub
parent 5bb018e0e5
commit e29f7b9f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 411 additions and 96 deletions

View File

@ -121,7 +121,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
async _initializePreview() {
const utility = await this._context.injectedScript();
this._preview = await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this);
this._setPreview(await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this));
}
asElement(): ElementHandle<T> | null {

View File

@ -82,6 +82,7 @@ export class JSHandle<T = any> {
readonly _value: any;
private _objectType: string;
protected _preview: string;
private _previewCallback: ((preview: string) => void) | undefined;
constructor(context: ExecutionContext, type: string, objectId?: ObjectId, value?: any) {
this._context = context;
@ -147,6 +148,16 @@ export class JSHandle<T = any> {
toString(): string {
return this._preview;
}
_setPreviewCallback(callback: (preview: string) => void) {
this._previewCallback = callback;
}
_setPreview(preview: string) {
this._preview = preview;
if (this._previewCallback)
this._previewCallback(preview);
}
}
export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {

View File

@ -646,12 +646,20 @@ export class Worker extends EventEmitter {
return js.evaluate(await this._executionContextPromise, true /* returnByValue */, pageFunction, arg);
}
async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, true /* returnByValue */, expression, isFunction, arg);
}
async evaluateHandle<R, Arg>(pageFunction: js.Func1<Arg, R>, arg: Arg): Promise<js.SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: js.Func1<void, R>, arg?: any): Promise<js.SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: js.Func1<Arg, R>, arg: Arg): Promise<js.SmartHandle<R>> {
assertMaxArguments(arguments.length, 2);
return js.evaluate(await this._executionContextPromise, false /* returnByValue */, pageFunction, arg);
}
async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, false /* returnByValue */, expression, isFunction, arg);
}
}
export class PageBinding {

View File

@ -25,9 +25,11 @@ export interface Channel extends EventEmitter {
_object: any;
}
export interface BrowserTypeChannel extends Channel {
connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel>;
launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel>;
launchServer(params: { options?: types.LaunchServerOptions }): Promise<BrowserServerChannel>;
launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel>;
}
export type BrowserTypeInitializer = {
@ -36,7 +38,21 @@ export type BrowserTypeInitializer = {
};
export interface BrowserServerChannel extends Channel {
on(event: 'close', callback: () => void): this;
close(): Promise<void>;
kill(): Promise<void>;
}
export type BrowserServerInitializer = {
wsEndpoint: string,
pid: number
};
export interface BrowserChannel extends Channel {
on(event: 'close', callback: () => void): this;
close(): Promise<void>;
newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>;
newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel>;
@ -46,7 +62,10 @@ export type BrowserInitializer = {};
export interface BrowserContextChannel extends Channel {
on(event: 'bindingCall', callback: (params: BindingCallChannel) => void): this;
on(event: 'close', callback: () => void): this;
on(event: 'page', callback: (params: PageChannel) => void): this;
on(event: 'route', callback: (params: { route: RouteChannel, request: RequestChannel }) => void): this;
addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>;
addInitScript(params: { source: string }): Promise<void>;
clearCookies(): Promise<void>;
@ -78,6 +97,7 @@ export interface PageChannel extends Channel {
on(event: 'dialog', callback: (params: DialogChannel) => void): this;
on(event: 'download', callback: (params: DownloadChannel) => void): this;
on(event: 'domcontentloaded', callback: () => void): this;
on(event: 'fileChooser', callback: (params: { element: ElementHandleChannel, isMultiple: boolean }) => void): this;
on(event: 'frameAttached', callback: (params: FrameChannel) => void): this;
on(event: 'frameDetached', callback: (params: FrameChannel) => void): this;
on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this;
@ -90,6 +110,7 @@ export interface PageChannel extends Channel {
on(event: 'requestFinished', callback: (params: RequestChannel) => void): this;
on(event: 'response', callback: (params: ResponseChannel) => void): this;
on(event: 'route', callback: (params: { route: RouteChannel, request: RequestChannel }) => void): this;
on(event: 'worker', callback: (params: WorkerChannel) => void): this;
setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
setDefaultTimeoutNoReply(params: { timeout: number }): Promise<void>;
@ -121,7 +142,9 @@ export interface PageChannel extends Channel {
// A11Y
accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise<types.SerializedAXNode | null>;
pdf: (params: { options?: types.PDFOptions }) => Promise<Binary>;
}
export type PageInitializer = {
mainFrame: FrameChannel,
viewportSize: types.Size | null
@ -137,7 +160,7 @@ export interface FrameChannel extends Channel {
click(params: { selector: string, options: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }, isPage?: boolean }): Promise<void>;
content(): Promise<string>;
dblclick(params: { selector: string, options: types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }, isPage?: boolean}): Promise<void>;
dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions, isPage?: boolean }): Promise<void>;
dispatchEvent(params: { selector: string, type: string, eventInit: any, options: types.TimeoutOptions, isPage?: boolean }): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<any>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<JSHandleChannel>;
fill(params: { selector: string, value: string, options: types.NavigatingActionWaitOptions, isPage?: boolean }): Promise<void>;
@ -170,7 +193,18 @@ export type FrameInitializer = {
};
export interface WorkerChannel extends Channel {
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<JSHandleChannel>;
}
export type WorkerInitializer = {
url: string,
};
export interface JSHandleChannel extends Channel {
on(event: 'previewUpdated', callback: (preview: string) => void): this;
dispose(): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel>;

View File

@ -20,6 +20,7 @@ import { BrowserContext } from './browserContext';
import { Page } from './page';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
import { Events } from '../../events';
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
readonly _contexts = new Set<BrowserContext>();
@ -36,6 +37,10 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
constructor(connection: Connection, channel: BrowserChannel, initializer: BrowserInitializer) {
super(connection, channel, initializer);
channel.on('close', () => {
this._isConnected = false;
this.emit(Events.Browser.Disconnected);
});
}
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {

View File

@ -52,7 +52,9 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
page._setBrowserContext(this);
});
this._channel.on('bindingCall', bindingCall => this._onBinding(BindingCall.from(bindingCall)));
this._channel.on('close', () => this._onClose());
this._channel.on('page', page => this._onPage(Page.from(page)));
this._channel.on('route', ({ route, request }) => this._onRoute(network.Route.from(route), network.Request.from(request)));
}
private _onPage(page: Page): void {
@ -180,8 +182,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
return result;
}
async close(): Promise<void> {
await this._channel.close();
private async _onClose() {
if (this._browser)
this._browser._contexts.delete(this);
@ -193,4 +194,8 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
this._pendingWaitForEvents.clear();
this.emit(Events.BrowserContext.Close);
}
async close(): Promise<void> {
await this._channel.close();
}
}

View File

@ -0,0 +1,50 @@
/**
* 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 { ChildProcess } from 'child_process';
import { BrowserServerChannel, BrowserServerInitializer } from '../channels';
import { Connection } from '../connection';
import { ChannelOwner } from './channelOwner';
import { Events } from '../../events';
export class BrowserServer extends ChannelOwner<BrowserServerChannel, BrowserServerInitializer> {
static from(request: BrowserServerChannel): BrowserServer {
return request._object;
}
constructor(connection: Connection, channel: BrowserServerChannel, initializer: BrowserServerInitializer) {
super(connection, channel, initializer);
channel.on('close', () => this.emit(Events.BrowserServer.Close));
}
process(): ChildProcess {
return { pid: this._initializer.pid } as any;
}
wsEndpoint(): string {
return this._initializer.wsEndpoint;
}
async kill(): Promise<void> {
await this._channel.kill();
}
async close(): Promise<void> {
await this._channel.close();
}
_checkLeaks() {}
}

View File

@ -20,6 +20,7 @@ import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
import { BrowserServer } from './browserServer';
export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeInitializer> {
constructor(connection: Connection, channel: BrowserTypeChannel, initializer: BrowserTypeInitializer) {
@ -39,6 +40,11 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
return Browser.from(await this._channel.launch({ options }));
}
async launchServer(options?: types.LaunchServerOptions): Promise<BrowserServer> {
delete (options as any).logger;
return BrowserServer.from(await this._channel.launchServer({ options }));
}
async launchPersistentContext(userDataDir: string, options: types.LaunchOptions & types.BrowserContextOptions = {}): Promise<BrowserContext> {
delete (options as any).logger;
return BrowserContext.from(await this._channel.launchPersistentContext({ userDataDir, options }));

View File

@ -0,0 +1,47 @@
/**
* 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 * as types from '../../types';
import { ElementHandle } from './elementHandle';
import { Page } from './page';
export class FileChooser {
private _page: Page;
private _elementHandle: ElementHandle<Node>;
private _isMultiple: boolean;
constructor(page: Page, elementHandle: ElementHandle, isMultiple: boolean) {
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
}
element(): ElementHandle {
return this._elementHandle;
}
isMultiple(): boolean {
return this._isMultiple;
}
page(): Page {
return this._page;
}
async setFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions) {
return this._elementHandle.setInputFiles(files, options);
}
}

View File

@ -97,8 +97,8 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
return ElementHandle.fromNullable(await this._channel.waitForSelector({ selector, options, isPage: this._page!._isPageCall })) as ElementHandle<Element> | null;
}
async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
await this._channel.dispatchEvent({ selector, type, eventInit, options, isPage: this._page!._isPageCall });
async dispatchEvent(selector: string, type: string, eventInit?: any, options: types.TimeoutOptions = {}): Promise<void> {
await this._channel.dispatchEvent({ selector, type, eventInit: serializeArgument(eventInit), options, isPage: this._page!._isPageCall });
}
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;

View File

@ -37,6 +37,8 @@ export type FuncOn<On, Arg2, R> = string | ((on: On, arg2: Unboxed<Arg2>) => R |
export type SmartHandle<T> = T extends Node ? ElementHandle<T> : JSHandle<T>;
export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleInitializer> {
private _preview: string;
static from(handle: JSHandleChannel): JSHandle {
return handle._object;
}
@ -47,6 +49,8 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
constructor(conection: Connection, channel: JSHandleChannel, initializer: JSHandleInitializer) {
super(conection, channel, initializer);
this._preview = this._initializer.preview;
channel.on('previewUpdated', preview => this._preview = preview);
}
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R>;
@ -94,7 +98,7 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
}
toString(): string {
return this._initializer.preview;
return this._preview;
}
}

View File

@ -16,35 +16,37 @@
*/
import { EventEmitter } from 'events';
import { TimeoutError } from '../../errors';
import { Events } from '../../events';
import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings';
import * as types from '../../types';
import { PageChannel, BindingCallChannel, Channel, PageInitializer, BindingCallInitializer } from '../channels';
import { BindingCallChannel, BindingCallInitializer, Channel, PageChannel, PageInitializer } from '../channels';
import { Connection } from '../connection';
import { parseError, serializeError } from '../serializers';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { ElementHandle } from './elementHandle';
import { Frame, FunctionWithSource, GotoOptions } from './frame';
import { Func1, FuncOn, SmartHandle } from './jsHandle';
import { Request, Response, RouteHandler, Route } from './network';
import { Connection } from '../connection';
import { Keyboard, Mouse } from './input';
import { Accessibility } from './accessibility';
import { ConsoleMessage } from './consoleMessage';
import { Dialog } from './dialog';
import { Download } from './download';
import { TimeoutError } from '../../errors';
import { TimeoutSettings } from '../../timeoutSettings';
import { parseError, serializeError } from '../serializers';
import { ElementHandle } from './elementHandle';
import { Worker } from './worker';
import { Frame, FunctionWithSource, GotoOptions } from './frame';
import { Keyboard, Mouse } from './input';
import { Func1, FuncOn, SmartHandle } from './jsHandle';
import { Request, Response, Route, RouteHandler } from './network';
import { FileChooser } from './fileChooser';
import { Buffer } from 'buffer';
export class Page extends ChannelOwner<PageChannel, PageInitializer> {
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
private _browserContext: BrowserContext | undefined;
_ownedContext: BrowserContext | undefined;
private _mainFrame: Frame;
private _frames = new Set<Frame>();
private _workers: Worker[] = [];
_workers = new Set<Worker>();
private _closed = false;
private _viewportSize: types.Size | null;
private _routes: { url: types.URLMatch, handler: RouteHandler }[] = [];
@ -83,6 +85,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._channel.on('dialog', dialog => this.emit(Events.Page.Dialog, Dialog.from(dialog)));
this._channel.on('domcontentloaded', () => this.emit(Events.Page.DOMContentLoaded));
this._channel.on('download', download => this.emit(Events.Page.Download, Download.from(download)));
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
this._channel.on('frameAttached', frame => this._onFrameAttached(Frame.from(frame)));
this._channel.on('frameDetached', frame => this._onFrameDetached(Frame.from(frame)));
this._channel.on('frameNavigated', ({ frame, url, name }) => this._onFrameNavigated(Frame.from(frame), url, name));
@ -94,6 +97,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._channel.on('requestFinished', request => this.emit(Events.Page.RequestFinished, Request.from(request)));
this._channel.on('response', response => this.emit(Events.Page.Response, Response.from(response)));
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
this._channel.on('worker', worker => this._onWorker(Worker.from(worker)));
}
_setBrowserContext(context: BrowserContext) {
@ -145,6 +149,12 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._browserContext!._onBinding(bindingCall);
}
_onWorker(worker: Worker): void {
this._workers.add(worker);
worker._page = this;
this.emit(Events.Page.Worker, worker);
}
private _onClose() {
this._closed = true;
this._browserContext!._pages.delete(this);
@ -221,7 +231,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
return this._attributeToPage(() => this._mainFrame.waitForSelector(selector, options));
}
async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
async dispatchEvent(selector: string, type: string, eventInit?: any, options?: types.TimeoutOptions): Promise<void> {
return this._attributeToPage(() => this._mainFrame.dispatchEvent(selector, type, eventInit, options));
}
@ -465,7 +475,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
}
workers(): Worker[] {
return this._workers;
return [...this._workers];
}
on(event: string | symbol, listener: Listener): this {
@ -483,33 +493,10 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this._channel.setFileChooserInterceptedNoReply({ intercepted: false });
return this;
}
}
export class Worker extends EventEmitter {
private _url: string;
private _channel: any;
constructor(url: string) {
super();
this._url = url;
}
url(): string {
return this._url;
}
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
return await this._channel.evaluate({ pageFunction, arg });
}
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
assertMaxArguments(arguments.length, 2);
return await this._channel.evaluateHandle({ pageFunction, arg });
async pdf(options?: types.PDFOptions): Promise<Buffer> {
const binary = await this._channel.pdf({ options });
return Buffer.from(binary, 'base64');
}
}

57
src/rpc/client/worker.ts Normal file
View File

@ -0,0 +1,57 @@
/**
* 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 { Events } from '../../events';
import { assertMaxArguments } from '../../helper';
import { WorkerChannel, WorkerInitializer } from '../channels';
import { Connection } from '../connection';
import { ChannelOwner } from './channelOwner';
import { Func1, JSHandle, parseResult, serializeArgument, SmartHandle } from './jsHandle';
import { Page } from './page';
export class Worker extends ChannelOwner<WorkerChannel, WorkerInitializer> {
_page: Page | undefined;
static from(worker: WorkerChannel): Worker {
return worker._object;
}
constructor(connection: Connection, channel: WorkerChannel, initializer: WorkerInitializer) {
super(connection, channel, initializer);
channel.on('close', () => {
this._page!._workers.delete(this);
this.emit(Events.Worker.Close, this);
});
}
url(): string {
return this._initializer.url;
}
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
return parseResult(await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }));
}
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
assertMaxArguments(arguments.length, 2);
return JSHandle.from(await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })) as SmartHandle<R>;
}
}

View File

@ -24,12 +24,14 @@ import { Frame } from './client/frame';
import { JSHandle } from './client/jsHandle';
import { Request, Response, Route } from './client/network';
import { Page, BindingCall } from './client/page';
import { Worker } from './client/worker';
import debug = require('debug');
import { Channel } from './channels';
import { ConsoleMessage } from './client/consoleMessage';
import { Dialog } from './client/dialog';
import { Download } from './client/download';
import { parseError } from './serializers';
import { BrowserServer } from './client/browserServer';
export class Connection {
private _channels = new Map<string, Channel>();
@ -52,6 +54,9 @@ export class Connection {
case 'browser':
result = new Browser(this, channel, initializer);
break;
case 'browserServer':
result = new BrowserServer(this, channel, initializer);
break;
case 'browserType':
result = new BrowserType(this, channel, initializer);
break;
@ -88,6 +93,9 @@ export class Connection {
case 'route':
result = new Route(this, channel, initializer);
break;
case 'worker':
result = new Worker(this, channel, initializer);
break;
default:
throw new Error('Missing type ' + type);
}

View File

@ -21,6 +21,7 @@ import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserChannel, BrowserContextChannel, PageChannel, BrowserInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher } from './pageDispatcher';
import { Events } from '../../events';
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
static from(scope: DispatcherScope, browser: BrowserBase): BrowserDispatcher {
@ -37,6 +38,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> i
constructor(scope: DispatcherScope, browser: BrowserBase) {
super(scope, browser, 'browser', {});
browser.on(Events.Browser.Disconnected, () => this._dispatchEvent('close'));
}
async newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel> {

View File

@ -0,0 +1,44 @@
/**
* 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 { BrowserServer } from '../../server/browserServer';
import { BrowserServerChannel, BrowserServerInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { Events } from '../../events';
export class BrowserServerDispatcher extends Dispatcher<BrowserServer, BrowserServerInitializer> implements BrowserServerChannel {
static from(scope: DispatcherScope, browserServer: BrowserServer): BrowserServerDispatcher {
if ((browserServer as any)[scope.dispatcherSymbol])
return (browserServer as any)[scope.dispatcherSymbol];
return new BrowserServerDispatcher(scope, browserServer);
}
constructor(scope: DispatcherScope, browserServer: BrowserServer) {
super(scope, browserServer, 'browserServer', {
wsEndpoint: browserServer.wsEndpoint(),
pid: browserServer.process().pid
});
browserServer.on(Events.BrowserServer.Close, () => this._dispatchEvent('close'));
}
async close(): Promise<void> {
await this._object.close();
}
async kill(): Promise<void> {
await this._object.close();
}
}

View File

@ -18,10 +18,11 @@ import { BrowserBase } from '../../browser';
import { BrowserTypeBase, BrowserType } from '../../server/browserType';
import * as types from '../../types';
import { BrowserDispatcher } from './browserDispatcher';
import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer } from '../channels';
import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer, BrowserServerChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextBase } from '../../browserContext';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserServerDispatcher } from './browserServerDispatcher';
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {
static from(scope: DispatcherScope, browserType: BrowserTypeBase): BrowserTypeDispatcher {
@ -47,6 +48,10 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
return BrowserContextDispatcher.from(this._scope, browserContext as BrowserContextBase);
}
async launchServer(params: { options?: types.LaunchServerOptions }): Promise<BrowserServerChannel> {
return BrowserServerDispatcher.from(this._scope, await this._object.launchServer(params.options));
}
async connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel> {
const browser = await this._object.connect(params.options);
return BrowserDispatcher.from(this._scope, browser as BrowserBase);

View File

@ -17,7 +17,7 @@
import { ConsoleMessage } from '../../console';
import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { fromHandle } from './elementHandlerDispatcher';
export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, ConsoleMessageInitializer> implements ConsoleMessageChannel {
static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher {
@ -30,7 +30,7 @@ export class ConsoleMessageDispatcher extends Dispatcher<ConsoleMessage, Console
super(scope, message, 'consoleMessage', {
type: message.type(),
text: message.text(),
args: message.args().map(a => ElementHandleDispatcher.from(scope, a)),
args: message.args().map(a => fromHandle(scope, a)),
location: message.location(),
});
}

View File

@ -22,21 +22,21 @@ import { DispatcherScope } from '../dispatcher';
import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher';
import { FrameDispatcher } from './frameDispatcher';
export function fromHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
if ((handle as any)[scope.dispatcherSymbol])
return (handle as any)[scope.dispatcherSymbol];
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
}
export function fromNullableHandle(scope: DispatcherScope, handle: js.JSHandle | null): JSHandleDispatcher | null {
if (!handle)
return null;
return fromHandle(scope, handle);
}
export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel {
readonly _elementHandle: ElementHandle;
static from(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
if ((handle as any)[scope.dispatcherSymbol])
return (handle as any)[scope.dispatcherSymbol];
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
}
static fromNullable(scope: DispatcherScope, handle: js.JSHandle | null): JSHandleDispatcher | null {
if (!handle)
return null;
return ElementHandleDispatcher.from(scope, handle);
}
static fromElement(scope: DispatcherScope, handle: ElementHandle): ElementHandleDispatcher {
if ((handle as any)[scope.dispatcherSymbol])
return (handle as any)[scope.dispatcherSymbol];
@ -125,7 +125,7 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
}
async press(params: { key: string, options: { delay?: number } & types.NavigatingActionWaitOptions }) {
await this._elementHandle.type(params.key, params.options);
await this._elementHandle.press(params.key, params.options);
}
async check(params: { options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions }) {

View File

@ -18,7 +18,7 @@ import { Frame } from '../../frames';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { convertSelectOptionValues, ElementHandleDispatcher } from './elementHandlerDispatcher';
import { convertSelectOptionValues, ElementHandleDispatcher, fromHandle } from './elementHandlerDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { ResponseDispatcher } from './networkDispatchers';
@ -72,7 +72,7 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<JSHandleChannel> {
const target = params.isPage ? this._frame._page : this._frame;
return ElementHandleDispatcher.fromElement(this._scope, await target._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg)));
return fromHandle(this._scope, await target._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg)));
}
async waitForSelector(params: { selector: string, options: types.WaitForElementOptions, isPage?: boolean }): Promise<ElementHandleChannel | null> {
@ -80,9 +80,9 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return ElementHandleDispatcher.fromNullableElement(this._scope, await target.waitForSelector(params.selector, params.options));
}
async dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions, isPage?: boolean }): Promise<void> {
async dispatchEvent(params: { selector: string, type: string, eventInit: any, options: types.TimeoutOptions, isPage?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame;
return target.dispatchEvent(params.selector, params.type, params.eventInit, params.options);
return target.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params.options);
}
async $eval(params: { selector: string, expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<any> {
@ -202,7 +202,7 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
async waitForFunction(params: { expression: string, isFunction: boolean, arg: any; options: types.WaitForFunctionOptions, isPage?: boolean }): Promise<JSHandleChannel> {
const target = params.isPage ? this._frame._page : this._frame;
return ElementHandleDispatcher.from(this._scope, await target._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params.options));
return fromHandle(this._scope, await target._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params.options));
}
async title(): Promise<string> {

View File

@ -17,8 +17,8 @@
import * as js from '../../javascript';
import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { parseEvaluationResultValue, serializeAsCallArgument } from '../../common/utilityScriptSerializers';
import { fromHandle } from './elementHandlerDispatcher';
export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitializer> implements JSHandleChannel {
@ -26,6 +26,7 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
super(scope, jsHandle, jsHandle.asElement() ? 'elementHandle' : 'jsHandle', {
preview: jsHandle.toString(),
});
jsHandle._setPreviewCallback(preview => this._dispatchEvent('previewUpdated', preview));
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
@ -34,7 +35,7 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
const jsHandle = await this._object._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return ElementHandleDispatcher.from(this._scope, jsHandle);
return fromHandle(this._scope, jsHandle);
}
async getPropertyList(): Promise<{ name: string, value: JSHandleChannel }[]> {

View File

@ -18,9 +18,9 @@ import { BrowserContext } from '../../browserContext';
import { Events } from '../../events';
import { Frame } from '../../frames';
import { Request } from '../../network';
import { Page } from '../../page';
import { Page, Worker } from '../../page';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel } from '../channels';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { parseError, serializeError } from '../serializers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
@ -28,6 +28,9 @@ import { DialogDispatcher } from './dialogDispatcher';
import { DownloadDispatcher } from './downloadDispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
import { serializeResult, parseArgument } from './jsHandleDispatcher';
import { fromHandle, ElementHandleDispatcher } from './elementHandlerDispatcher';
import { FileChooser } from '../../fileChooser';
export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements PageChannel {
private _page: Page;
@ -56,6 +59,10 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
page.on(Events.Page.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded'));
page.on(Events.Page.Dialog, dialog => this._dispatchEvent('dialog', DialogDispatcher.from(this._scope, dialog)));
page.on(Events.Page.Download, dialog => this._dispatchEvent('download', DownloadDispatcher.from(this._scope, dialog)));
page.on(Events.Page.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
element: ElementHandleDispatcher.fromElement(this._scope, fileChooser.element()),
isMultiple: fileChooser.isMultiple()
}));
page.on(Events.Page.FrameAttached, frame => this._onFrameAttached(frame));
page.on(Events.Page.FrameDetached, frame => this._onFrameDetached(frame));
page.on(Events.Page.FrameNavigated, frame => this._onFrameNavigated(frame));
@ -69,6 +76,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
}));
page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', RequestDispatcher.from(this._scope, request)));
page.on(Events.Page.Response, response => this._dispatchEvent('response', ResponseDispatcher.from(this._scope, response)));
page.on(Events.Page.Worker, worker => this._dispatchEvent('worker', WorkerDispatcher.from(this._scope, worker)));
}
async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) {
@ -187,6 +195,13 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
});
}
async pdf(params: { options?: types.PDFOptions }): Promise<Binary> {
if (!this._page.pdf)
throw new Error('PDF generation is only supported for Headless Chromium');
const binary = await this._page.pdf(params.options);
return binary.toString('base64');
}
_onFrameAttached(frame: Frame) {
this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame));
}
@ -201,6 +216,29 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
}
export class WorkerDispatcher extends Dispatcher<Worker, WorkerInitializer> implements WorkerChannel {
static from(scope: DispatcherScope, worker: Worker): WorkerDispatcher {
if ((worker as any)[scope.dispatcherSymbol])
return (worker as any)[scope.dispatcherSymbol];
return new WorkerDispatcher(scope, worker);
}
constructor(scope: DispatcherScope, worker: Worker) {
super(scope, worker, 'worker', {
url: worker.url()
});
worker.on(Events.Worker.Close, () => this._dispatchEvent('close'));
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<any> {
return serializeResult(await this._object._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg)));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<JSHandleChannel> {
return fromHandle(this._scope, await this._object._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg)));
}
}
export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer> implements BindingCallChannel {
private _resolve: ((arg: any) => void) | undefined;
private _reject: ((error: any) => void) | undefined;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
describe('ChromiumBrowserContext', function() {
describe.skip(CHANNEL)('ChromiumBrowserContext', function() {
it('should create a worker from a service worker', async({browser, page, server, context}) => {
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
describe('JSCoverage', function() {
describe.skip(CHANNEL)('JSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' });
@ -88,7 +88,7 @@ describe('JSCoverage', function() {
});
});
describe('CSSCoverage', function() {
describe.skip(CHANNEL)('CSSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/simple.html');

View File

@ -17,9 +17,9 @@
const path = require('path');
const utils = require('../utils');
const {makeUserDataDir, removeUserDataDir} = utils;
const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, WIN, CHANNEL} = utils.testOptions(browserType);
describe('launcher', function() {
describe.skip(CHANNEL)('launcher', function() {
it('should throw with remote-debugging-pipe argument', async({browserType, defaultBrowserOptions}) => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
@ -32,7 +32,7 @@ describe('launcher', function() {
const browser = await browserType.launchServer(options);
await browser.close();
});
it.skip(USES_HOOKS)('should open devtools when "devtools: true" option is given', async({browserType, defaultBrowserOptions}) => {
it('should open devtools when "devtools: true" option is given', async({browserType, defaultBrowserOptions}) => {
let devtoolsCallback;
const devtoolsPromise = new Promise(f => devtoolsCallback = f);
const __testHookForDevTools = devtools => devtools.__testHookOnBinding = parsed => {
@ -49,7 +49,7 @@ describe('launcher', function() {
});
});
describe('extensions', () => {
describe.skip(CHANNEL)('extensions', () => {
it('should return background pages', async({browserType, defaultBrowserOptions}) => {
const userDataDir = await makeUserDataDir();
const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension');
@ -73,7 +73,7 @@ describe('extensions', () => {
});
});
describe('BrowserContext', function() {
describe.skip(CHANNEL)('BrowserContext', function() {
it('should not create pages automatically', async ({browserType, defaultBrowserOptions}) => {
const browser = await browserType.launch(defaultBrowserOptions);
const browserSession = await browser.newBrowserCDPSession();

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
describe('OOPIF', function() {
describe.skip(CHANNEL)('OOPIF', function() {
beforeAll(async function(state) {
state.browser = await state.browserType.launch(Object.assign({}, state.defaultBrowserOptions, {
args: (state.defaultBrowserOptions.args || []).concat(['--site-per-process']),

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
describe('ChromiumBrowserContext.createSession', function() {
describe.skip(CHANNEL)('ChromiumBrowserContext.createSession', function() {
it('should work', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
@ -95,7 +95,7 @@ describe('ChromiumBrowserContext.createSession', function() {
await context.close();
});
});
describe('ChromiumBrowser.newBrowserCDPSession', function() {
describe.skip(CHANNEL)('ChromiumBrowser.newBrowserCDPSession', function() {
it('should work', async function({page, browser, server}) {
const session = await browser.newBrowserCDPSession();
const version = await session.send('Browser.getVersion');

View File

@ -16,9 +16,9 @@
const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, OUTPUT_DIR} = require('../utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, OUTPUT_DIR, CHANNEL} = require('../utils').testOptions(browserType);
describe('Chromium.startTracing', function() {
describe.skip(CHANNEL)('Chromium.startTracing', function() {
beforeEach(async function(state) {
state.outputFile = path.join(OUTPUT_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await state.browserType.launch(state.defaultBrowserOptions);

View File

@ -336,7 +336,7 @@ describe('launchPersistentContext()', function() {
expect(error.message).toContain('can not specify page');
await removeUserDataDir(userDataDir);
});
it('should have passed URL when launching with ignoreDefaultArgs: true', async ({browserType, defaultBrowserOptions, server}) => {
it.skip(USES_HOOKS)('should have passed URL when launching with ignoreDefaultArgs: true', async ({browserType, defaultBrowserOptions, server}) => {
const userDataDir = await makeUserDataDir();
const args = browserType._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank');
const options = {

View File

@ -16,9 +16,9 @@
const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('./utils').testOptions(browserType);
describe('Logger', function() {
describe.skip(CHANNEL)('Logger', function() {
it('should log', async({browserType, defaultBrowserOptions}) => {
const log = [];
const browser = await browserType.launch({...defaultBrowserOptions, logger: {

View File

@ -18,7 +18,7 @@
const utils = require('./utils');
const path = require('path');
const url = require('url');
const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, MAC, WIN, CHANNEL} = utils.testOptions(browserType);
describe('Page.goto', function() {
it('should work', async({page, server}) => {
@ -541,7 +541,7 @@ describe('Page.goto', function() {
});
});
describe('Page.waitForNavigation', function() {
describe.skip(CHANNEL)('Page.waitForNavigation', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([

View File

@ -24,6 +24,8 @@ const { Connection } = require('../lib/rpc/connection');
const { helper } = require('../lib/helper');
const { BrowserTypeDispatcher } = require('../lib/rpc/server/browserTypeDispatcher');
Error.stackTraceLimit = 15;
function getCLIArgument(argName) {
for (let i = 0; i < process.argv.length; ++i) {
// Support `./test.js --foo bar

View File

@ -200,7 +200,8 @@ const utils = module.exports = {
browserType,
GOLDEN_DIR,
OUTPUT_DIR,
USES_HOOKS: !!process.env.PWCHANNEL
USES_HOOKS: !!process.env.PWCHANNEL,
CHANNEL: !!process.env.PWCHANNEL,
};
},