chore: introduce the experimental rpc implementation (#2720)

This commit is contained in:
Pavel Feldman 2020-06-25 16:05:36 -07:00 committed by GitHub
parent 5d5cf26a0e
commit bab6833232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2779 additions and 3 deletions

View File

@ -451,7 +451,7 @@ export class Frame {
async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise<any> {
const context = await this._mainContext();
return context.evaluateExpressionHandleInternal(expression, isFunction, arg);
return context.evaluateExpressionInternal(expression, isFunction, arg);
}
async $(selector: string): Promise<dom.ElementHandle<Element> | null> {

View File

@ -213,7 +213,7 @@ export async function evaluateExpression(context: ExecutionContext, returnByValu
const script = `(utilityScript, ...args) => utilityScript.callFunction(...args)` + sourceMap.generateSourceUrl();
try {
return context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, utilityScriptValues, utilityScriptObjectIds);
return await context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, utilityScriptValues, utilityScriptObjectIds);
} finally {
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
}

175
src/rpc/channels.ts Normal file
View File

@ -0,0 +1,175 @@
/**
* 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 { EventEmitter } from 'events';
import * as types from '../types';
export interface Channel extends EventEmitter {
_type: string;
_guid: string;
_object: any;
}
export interface BrowserTypeChannel extends Channel {
launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel>;
launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel>;
connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel>;
}
export interface BrowserChannel extends Channel {
newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>;
newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel>;
close(): Promise<void>;
}
export interface BrowserContextChannel extends Channel {
setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
setDefaultTimeoutNoReply(params: { timeout: number }): void;
exposeBinding(params: { name: string }): Promise<void>;
newPage(): Promise<PageChannel>;
cookies(params: { urls: string[] }): Promise<types.NetworkCookie[]>;
addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>;
clearCookies(): Promise<void>;
grantPermissions(params: { permissions: string[]; options?: { origin?: string } }): Promise<void>;
clearPermissions(): Promise<void>;
setGeolocation(params: { geolocation: types.Geolocation | null }): Promise<void>;
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
setOffline(params: { offline: boolean }): Promise<void>;
setHTTPCredentials(params: { httpCredentials: types.Credentials | null }): Promise<void>;
addInitScript(params: { source: string }): Promise<void>;
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
waitForEvent(params: { event: string }): Promise<any>;
close(): Promise<void>;
}
export interface PageChannel extends Channel {
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 }) => void): this;
on(event: 'request', callback: (params: RequestChannel) => void): this;
on(event: 'response', callback: (params: ResponseChannel) => void): this;
on(event: 'requestFinished', callback: (params: RequestChannel) => void): this;
on(event: 'requestFailed', callback: (params: RequestChannel) => void): this;
on(event: 'close', callback: () => void): this;
setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
setDefaultTimeoutNoReply(params: { timeout: number }): Promise<void>;
setFileChooserInterceptedNoReply(params: { intercepted: boolean }): Promise<void>;
opener(): Promise<PageChannel | null>;
exposeBinding(params: { name: string }): Promise<void>;
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
reload(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
waitForEvent(params: { event: string }): Promise<any>;
goBack(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
goForward(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
emulateMedia(params: { options: { media?: 'screen' | 'print', colorScheme?: 'dark' | 'light' | 'no-preference' } }): Promise<void>;
setViewportSize(params: { viewportSize: types.Size }): Promise<void>;
addInitScript(params: { source: string }): Promise<void>;
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer>;
close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void>;
}
export interface FrameChannel extends Channel {
goto(params: { url: string, options: types.GotoOptions }): Promise<ResponseChannel | null>;
waitForNavigation(params: { options: types.WaitForNavigationOptions }): Promise<ResponseChannel | null>;
waitForLoadState(params: { state: types.LifecycleEvent, options: types.TimeoutOptions }): Promise<void>;
frameElement(): Promise<ElementHandleChannel>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel>;
querySelector(params: { selector: string }): Promise<ElementHandleChannel | null>;
waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null>;
dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions }): Promise<void>;
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]>;
content(): Promise<string>;
setContent(params: { html: string, options: types.NavigateOptions }): Promise<void>;
addScriptTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined, type?: string | undefined } }): Promise<ElementHandleChannel>;
addStyleTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined } }): Promise<ElementHandleChannel>;
click(params: { selector: string, options: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
dblclick(params: { selector: string, options: types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }}): Promise<void>;
fill(params: { selector: string, value: string, options: types.NavigatingActionWaitOptions }): Promise<void>;
focus(params: { selector: string, options: types.TimeoutOptions }): Promise<void>;
textContent(params: { selector: string, options: types.TimeoutOptions }): Promise<string | null>;
innerText(params: { selector: string, options: types.TimeoutOptions }): Promise<string>;
innerHTML(params: { selector: string, options: types.TimeoutOptions }): Promise<string>;
getAttribute(params: { selector: string, name: string, options: types.TimeoutOptions }): Promise<string | null>;
hover(params: { selector: string, options: types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } }): Promise<void>;
selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions }): Promise<string[]>;
setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void>;
type(params: { selector: string, text: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
press(params: { selector: string, key: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
check(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
uncheck(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
waitForFunction(params: { expression: string, isFunction: boolean, arg: any; options: types.WaitForFunctionOptions }): Promise<JSHandleChannel>;
title(): Promise<string>;
}
export interface JSHandleChannel extends Channel {
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel>;
getPropertyList(): Promise<{ name: string, value: JSHandleChannel}[]>;
jsonValue(): Promise<any>;
dispose(): Promise<void>;
}
export interface ElementHandleChannel extends JSHandleChannel {
ownerFrame(): Promise<FrameChannel | null>;
contentFrame(): Promise<FrameChannel | null>;
getAttribute(params: { name: string }): Promise<string | null>;
textContent(): Promise<string | null>;
innerText(): Promise<string>;
innerHTML(): Promise<string>;
boundingBox(): Promise<types.Rect | null>;
hover(params: { options?: types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } }): Promise<void>;
click(params: { options?: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
dblclick(params: { options?: types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null; options?: types.NavigatingActionWaitOptions }): string[] | Promise<string[]>;
fill(params: { value: string; options?: types.NavigatingActionWaitOptions }): Promise<void>;
selectText(params: { options?: types.TimeoutOptions }): Promise<void>;
setInputFiles(params: { files: string | string[] | types.FilePayload | types.FilePayload[], options?: types.NavigatingActionWaitOptions }): Promise<void>;
focus(): Promise<void>;
type(params: { text: string; options?: { delay?: number } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
press(params: { key: string; options?: { delay?: number } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
check(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
uncheck(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
dispatchEvent(params: { type: string, eventInit: any }): Promise<void>;
scrollIntoViewIfNeeded(params: { options?: types.TimeoutOptions }): Promise<void>;
screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<Buffer>;
querySelector(params: { selector: string }): Promise<ElementHandleChannel | null>;
querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]>;
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
}
export interface RequestChannel extends Channel {
continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void>;
fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void>;
abort(params: { errorCode: string }): Promise<void>;
response(): Promise<ResponseChannel | null>;
}
export interface ResponseChannel extends Channel {
body(): Promise<Buffer>;
finished(): Promise<Error | null>;
}

65
src/rpc/client/browser.ts Normal file
View File

@ -0,0 +1,65 @@
/**
* 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 { BrowserChannel } from '../channels';
import { BrowserContext } from './browserContext';
import { Page } from './page';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
export class Browser extends ChannelOwner<BrowserChannel> {
readonly _contexts = new Set<BrowserContext>();
private _isConnected = true;
static from(browser: BrowserChannel): Browser {
return browser._object;
}
static fromNullable(browser: BrowserChannel | null): Browser | null {
return browser ? Browser.from(browser) : null;
}
constructor(connection: Connection, channel: BrowserChannel) {
super(connection, channel);
}
_initialize() {}
async newContext(options?: types.BrowserContextOptions): Promise<BrowserContext> {
const context = BrowserContext.from(await this._channel.newContext({ options }));
this._contexts.add(context);
context._browser = this;
return context;
}
contexts(): BrowserContext[] {
return [...this._contexts];
}
async newPage(options?: types.BrowserContextOptions): Promise<Page> {
return Page.from(await this._channel.newPage({ options }));
}
isConnected(): boolean {
return this._isConnected;
}
async close(): Promise<void> {
await this._channel.close();
}
}

View File

@ -0,0 +1,136 @@
/**
* Copyright 2017 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 * as frames from './frame';
import { Page } from './page';
import * as types from '../../types';
import * as network from './network';
import { BrowserContextChannel } from '../channels';
import { ChannelOwner } from './channelOwner';
import { helper } from '../../helper';
import { Browser } from './browser';
import { Connection } from '../connection';
export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
_pages = new Set<Page>();
private _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
_browser: Browser | undefined;
static from(context: BrowserContextChannel): BrowserContext {
return context._object;
}
static fromNullable(context: BrowserContextChannel | null): BrowserContext | null {
return context ? BrowserContext.from(context) : null;
}
constructor(connection: Connection, channel: BrowserContextChannel) {
super(connection, channel);
}
_initialize() {
}
setDefaultNavigationTimeout(timeout: number) {
this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}
setDefaultTimeout(timeout: number) {
this._channel.setDefaultTimeoutNoReply({ timeout });
}
pages(): Page[] {
return [...this._pages];
}
async newPage(): Promise<Page> {
return Page.from(await this._channel.newPage());
}
async cookies(urls?: string | string[]): Promise<network.NetworkCookie[]> {
if (!urls)
urls = [];
if (urls && typeof urls === 'string')
urls = [ urls ];
return this._channel.cookies({ urls: urls as string[] });
}
async addCookies(cookies: network.SetNetworkCookieParam[]): Promise<void> {
await this._channel.addCookies({ cookies });
}
async clearCookies(): Promise<void> {
await this._channel.clearCookies();
}
async grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void> {
await this._channel.grantPermissions({ permissions, options });
}
async clearPermissions(): Promise<void> {
await this._channel.clearPermissions();
}
async setGeolocation(geolocation: types.Geolocation | null): Promise<void> {
await this._channel.setGeolocation({ geolocation });
}
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
await this._channel.setExtraHTTPHeaders({ headers });
}
async setOffline(offline: boolean): Promise<void> {
await this._channel.setOffline({ offline });
}
async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise<void> {
await this._channel.setHTTPCredentials({ httpCredentials });
}
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
const source = await helper.evaluationScript(script, arg);
await this._channel.addInitScript({ source });
}
async exposeBinding(name: string, playwrightBinding: frames.FunctionWithSource): Promise<void> {
}
async exposeFunction(name: string, playwrightFunction: Function): Promise<void> {
await this.exposeBinding(name, (source, ...args) => playwrightFunction(...args));
}
async route(url: types.URLMatch, handler: network.RouteHandler): Promise<void> {
this._routes.push({ url, handler });
if (this._routes.length === 1)
await this._channel.setNetworkInterceptionEnabled({ enabled: true });
}
async unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise<void> {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0)
await this._channel.setNetworkInterceptionEnabled({ enabled: false });
}
async waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise<any> {
return await this._channel.waitForEvent({ event });
}
async close(): Promise<void> {
await this._channel.close();
this._browser!._contexts.delete(this);
}
}

View File

@ -0,0 +1,56 @@
/**
* 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 { BrowserTypeChannel } from '../channels';
import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
export class BrowserType extends ChannelOwner<BrowserTypeChannel> {
private _executablePath: string = '';
private _name: string = '';
constructor(connection: Connection, channel: BrowserTypeChannel) {
super(connection, channel);
}
_initialize(payload: { executablePath: string, name: string }) {
this._executablePath = payload.executablePath;
this._name = payload.name;
}
executablePath(): string {
return this._executablePath;
}
name(): string {
return this._name;
}
async launch(options?: types.LaunchOptions): Promise<Browser> {
return Browser.from(await this._channel.launch({ options }));
}
async launchPersistentContext(userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions): Promise<BrowserContext> {
return BrowserContext.from(await this._channel.launchPersistentContext({ userDataDir, options }));
}
async connect(options: types.ConnectOptions): Promise<Browser> {
return Browser.from(await this._channel.connect({ options }));
}
}

View File

@ -0,0 +1,34 @@
/**
* 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 { EventEmitter } from 'events';
import { Channel } from '../channels';
import { Connection } from '../connection';
export abstract class ChannelOwner<T extends Channel> extends EventEmitter {
readonly _channel: T;
readonly _connection: Connection;
static clientSymbol = Symbol('client');
constructor(connection: Connection, channel: T) {
super();
this._connection = connection;
this._channel = channel;
(channel as any)[ChannelOwner.clientSymbol] = this;
}
abstract _initialize(payload: any): void;
}

View File

@ -0,0 +1,150 @@
/**
* 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 { ElementHandleChannel } from '../channels';
import { Frame } from './frame';
import { FuncOn, JSHandle, convertArg } from './jsHandle';
import { Connection } from '../connection';
export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
private _elementChannel: ElementHandleChannel;
static from(handle: ElementHandleChannel): ElementHandle {
return handle._object;
}
static fromNullable(handle: ElementHandleChannel | null): ElementHandle | null {
return handle ? ElementHandle.from(handle) : null;
}
constructor(connection: Connection, channel: ElementHandleChannel) {
super(connection, channel);
this._elementChannel = channel;
}
asElement(): ElementHandle<T> | null {
return this;
}
async ownerFrame(): Promise<Frame | null> {
return Frame.fromNullable(await this._elementChannel.ownerFrame());
}
async contentFrame(): Promise<Frame | null> {
return Frame.fromNullable(await this._elementChannel.contentFrame());
}
async getAttribute(name: string): Promise<string | null> {
return await this._elementChannel.getAttribute({ name });
}
async textContent(): Promise<string | null> {
return await this._elementChannel.textContent();
}
async innerText(): Promise<string> {
return await this._elementChannel.innerText();
}
async innerHTML(): Promise<string> {
return await this._elementChannel.innerHTML();
}
async dispatchEvent(type: string, eventInit: Object = {}) {
await this._elementChannel.dispatchEvent({ type, eventInit });
}
async scrollIntoViewIfNeeded(options?: types.TimeoutOptions) {
await this._elementChannel.scrollIntoViewIfNeeded({ options });
}
async hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> {
await this._elementChannel.hover({ options });
}
async click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
return await this._elementChannel.click({ options });
}
async dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
return await this._elementChannel.dblclick({ options });
}
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
return await this._elementChannel.selectOption({ values: values as any, options });
}
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
return await this._elementChannel.fill({ value, options });
}
async selectText(options: types.TimeoutOptions): Promise<void> {
await this._elementChannel.selectText({ options });
}
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) {
await this._elementChannel.setInputFiles({ files, options });
}
async focus(): Promise<void> {
await this._elementChannel.focus();
}
async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
await this._elementChannel.type({ text, options });
}
async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
await this._elementChannel.press({ key, options });
}
async check(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._elementChannel.check({ options });
}
async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._elementChannel.uncheck({ options });
}
async boundingBox(): Promise<types.Rect | null> {
return await this._elementChannel.boundingBox();
}
async screenshot(options?: types.ElementScreenshotOptions): Promise<Buffer> {
return await this._elementChannel.screenshot({ options });
}
async $(selector: string): Promise<ElementHandle<Element> | null> {
return ElementHandle.fromNullable(await this._elementChannel.querySelector({ selector })) as ElementHandle<Element> | null;
}
async $$(selector: string): Promise<ElementHandle<Element>[]> {
return (await this._elementChannel.querySelectorAll({ selector })).map(h => ElementHandle.from(h) as ElementHandle<Element>);
}
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
return await this._elementChannel.$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
return await this._elementChannel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
}

231
src/rpc/client/frame.ts Normal file
View File

@ -0,0 +1,231 @@
/**
* Copyright 2017 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 { assertMaxArguments } from '../../helper';
import * as types from '../../types';
import { FrameChannel } from '../channels';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { ElementHandle } from './elementHandle';
import { JSHandle, Func1, FuncOn, SmartHandle, convertArg } from './jsHandle';
import * as network from './network';
import { Response } from './network';
import { Page } from './page';
import { Connection } from '../connection';
export type GotoOptions = types.NavigateOptions & {
referer?: string,
};
export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame }, ...args: any) => any;
export class Frame extends ChannelOwner<FrameChannel> {
_parentFrame: Frame | null = null;
_url = '';
private _detached = false;
_childFrames = new Set<Frame>();
private _name = '';
static from(frame: FrameChannel): Frame {
return frame._object;
}
static fromNullable(frame: FrameChannel | null): Frame | null {
return frame ? Frame.from(frame) : null;
}
constructor(connection: Connection, channel: FrameChannel) {
super(connection, channel);
}
_initialize(payload: { parentFrame: FrameChannel | null }) {
this._parentFrame = payload.parentFrame ? payload.parentFrame._object : null;
if (this._parentFrame)
this._parentFrame._childFrames.add(this);
}
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {
return Response.fromNullable(await this._channel.goto({ url, options }));
}
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
return Response.fromNullable(await this._channel.waitForNavigation({ options }));
}
async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
await this._channel.waitForLoadState({ state, options });
}
async frameElement(): Promise<ElementHandle> {
return ElementHandle.from(await this._channel.frameElement());
}
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: convertArg(arg) })) as SmartHandle<R>;
}
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.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
async $(selector: string): Promise<ElementHandle<Element> | null> {
return ElementHandle.fromNullable(await this._channel.querySelector({ selector })) as ElementHandle<Element> | null;
}
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
return ElementHandle.fromNullable(await this._channel.waitForSelector({ selector, options })) 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 });
}
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._channel.$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._channel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
async $$(selector: string): Promise<ElementHandle<Element>[]> {
const result = await this._channel.querySelectorAll({ selector });
return result.map(c => ElementHandle.from(c) as ElementHandle<Element>);
}
async content(): Promise<string> {
return await this._channel.content();
}
async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
await this._channel.setContent({ html, options });
}
name(): string {
return this._name || '';
}
url(): string {
return this._url;
}
parentFrame(): Frame | null {
return this._parentFrame;
}
childFrames(): Frame[] {
return Array.from(this._childFrames);
}
isDetached(): boolean {
return this._detached;
}
async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string }): Promise<ElementHandle> {
return ElementHandle.from(await this._channel.addScriptTag({ options }));
}
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
return ElementHandle.from(await this._channel.addStyleTag({ options }));
}
async click(selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._channel.click({ selector, options });
}
async dblclick(selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._channel.dblclick({ selector, options });
}
async fill(selector: string, value: string, options: types.NavigatingActionWaitOptions = {}) {
return await this._channel.fill({ selector, value, options });
}
async focus(selector: string, options: types.TimeoutOptions = {}) {
await this._channel.focus({ selector, options });
}
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
return await this._channel.textContent({ selector, options });
}
async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._channel.innerText({ selector, options });
}
async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._channel.innerHTML({ selector, options });
}
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
return await this._channel.getAttribute({ selector, name, options });
}
async hover(selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) {
await this._channel.hover({ selector, options });
}
async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
return await this._channel.selectOption({ selector, values: values as any, options });
}
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
await this._channel.setInputFiles({ selector, files, options });
}
async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
await this._channel.type({ selector, text, options });
}
async press(selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
await this._channel.press({ selector, key, options });
}
async check(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._channel.check({ selector, options });
}
async uncheck(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
await this._channel.uncheck({ selector, options });
}
async waitForTimeout(timeout: number) {
await new Promise(fulfill => setTimeout(fulfill, timeout));
}
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R>(pageFunction: Func1<void, R>, arg?: any, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise<SmartHandle<R>> {
return JSHandle.from(await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg, options })) as SmartHandle<R>;
}
async title(): Promise<string> {
return await this._channel.title();
}
}

122
src/rpc/client/jsHandle.ts Normal file
View File

@ -0,0 +1,122 @@
/**
* 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 { JSHandleChannel } from '../channels';
import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg);
type Unboxed<Arg> =
Arg extends ElementHandle<infer T> ? T :
Arg extends JSHandle<infer T> ? T :
Arg extends NoHandles<Arg> ? Arg :
Arg extends [infer A0] ? [Unboxed<A0>] :
Arg extends [infer A0, infer A1] ? [Unboxed<A0>, Unboxed<A1>] :
Arg extends [infer A0, infer A1, infer A2] ? [Unboxed<A0>, Unboxed<A1>, Unboxed<A2>] :
Arg extends Array<infer T> ? Array<Unboxed<T>> :
Arg extends object ? { [Key in keyof Arg]: Unboxed<Arg[Key]> } :
Arg;
export type Func0<R> = string | (() => R | Promise<R>);
export type Func1<Arg, R> = string | ((arg: Unboxed<Arg>) => R | Promise<R>);
export type FuncOn<On, Arg2, R> = string | ((on: On, arg2: Unboxed<Arg2>) => R | Promise<R>);
export type SmartHandle<T> = T extends Node ? ElementHandle<T> : JSHandle<T>;
export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel> {
protected _jsChannel: JSHandleChannel;
private _preview: string | undefined;
static from(handle: JSHandleChannel): JSHandle {
return handle._object;
}
static fromNullable(handle: JSHandleChannel | null): JSHandle | null {
return handle ? JSHandle.from(handle) : null;
}
constructor(conection: Connection, channel: JSHandleChannel) {
super(conection, channel);
this._jsChannel = channel;
}
_initialize(params: { preview: string }) {
this._preview = params.preview;
}
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R> {
return await this._jsChannel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
}
async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
const handleChannel = await this._jsChannel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return JSHandle.from(handleChannel) as SmartHandle<R>;
}
async getProperty(propertyName: string): Promise<JSHandle> {
const objectHandle = await this.evaluateHandle((object: any, propertyName: string) => {
const result: any = {__proto__: null};
result[propertyName] = object[propertyName];
return result;
}, propertyName);
const properties = await objectHandle.getProperties();
const result = properties.get(propertyName)!;
objectHandle.dispose();
return result;
}
async getProperties(): Promise<Map<string, JSHandle>> {
const map = new Map<string, JSHandle>();
for (const { name, value } of await this._jsChannel.getPropertyList())
map.set(name, JSHandle.from(value));
return map;
}
async jsonValue(): Promise<T> {
return await this._jsChannel.jsonValue();
}
asElement(): ElementHandle | null {
return null;
}
async dispose() {
return await this._jsChannel.dispose();
}
toString(): string {
return this._preview!;
}
}
export function convertArg(arg: any): any {
if (arg === null)
return null;
if (Array.isArray(arg))
return arg.map(item => convertArg(item));
if (arg instanceof ChannelOwner)
return arg._channel;
if (typeof arg === 'object') {
const result: any = {};
for (const key of Object.keys(arg))
result[key] = convertArg(arg[key]);
return result;
}
return arg;
}

251
src/rpc/client/network.ts Normal file
View File

@ -0,0 +1,251 @@
/**
* 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 { URLSearchParams } from 'url';
import * as types from '../../types';
import { RequestChannel, ResponseChannel, FrameChannel } from '../channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { Connection } from '../connection';
export type NetworkCookie = {
name: string,
value: string,
domain: string,
path: string,
expires: number,
httpOnly: boolean,
secure: boolean,
sameSite: 'Strict' | 'Lax' | 'None'
};
export type SetNetworkCookieParam = {
name: string,
value: string,
url?: string,
domain?: string,
path?: string,
expires?: number,
httpOnly?: boolean,
secure?: boolean,
sameSite?: 'Strict' | 'Lax' | 'None'
};
export class Request extends ChannelOwner<RequestChannel> {
private _redirectedFrom: Request | null = null;
private _redirectedTo: Request | null = null;
private _isNavigationRequest = false;
private _failureText: string | null = null;
private _url: string = '';
private _resourceType = '';
private _method = '';
private _postData: string | null = null;
private _headers: types.Headers = {};
private _frame: Frame | undefined;
static from(request: RequestChannel): Request {
return request._object;
}
static fromNullable(request: RequestChannel | null): Request | null {
return request ? Request.from(request) : null;
}
constructor(connection: Connection, channel: RequestChannel) {
super(connection, channel);
}
_initialize(payload: { frame: FrameChannel, redirectedFrom: RequestChannel | null, isNavigationRequest: boolean,
url: string, resourceType: string, method: string, postData: string | null, headers: types.Headers }) {
this._frame = payload.frame._object as Frame;
this._isNavigationRequest = payload.isNavigationRequest;
this._redirectedFrom = Request.fromNullable(payload.redirectedFrom);
if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this;
this._url = payload.url;
this._resourceType = payload.resourceType;
this._method = payload.method;
this._postData = payload.postData;
this._headers = payload.headers;
}
url(): string {
return this._url;
}
resourceType(): string {
return this._resourceType;
}
method(): string {
return this._method;
}
postData(): string | null {
return this._postData;
}
postDataJSON(): Object | null {
if (!this._postData)
return null;
const contentType = this.headers()['content-type'];
if (!contentType)
return null;
if (contentType === 'application/x-www-form-urlencoded') {
const entries: Record<string, string> = {};
const parsed = new URLSearchParams(this._postData);
for (const [k, v] of parsed.entries())
entries[k] = v;
return entries;
}
return JSON.parse(this._postData);
}
headers(): {[key: string]: string} {
return { ...this._headers };
}
async response(): Promise<Response | null> {
return Response.fromNullable(await this._channel.response());
}
frame(): Frame {
return this._frame!;
}
isNavigationRequest(): boolean {
return this._isNavigationRequest;
}
redirectedFrom(): Request | null {
return this._redirectedFrom;
}
redirectedTo(): Request | null {
return this._redirectedTo;
}
failure(): { errorText: string; } | null {
if (this._failureText === null)
return null;
return {
errorText: this._failureText
};
}
}
export class Route {
private _request: Request;
constructor(request: Request) {
this._request = request;
}
request(): Request {
return this._request;
}
async abort(errorCode: string = 'failed') {
await this._request._channel.abort({ errorCode });
}
async fulfill(response: types.FulfillResponse & { path?: string }) {
await this._request._channel.fulfill({ response });
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) {
await this._request._channel.continue({ overrides });
}
}
export type RouteHandler = (route: Route, request: Request) => void;
export class Response extends ChannelOwner<ResponseChannel> {
private _request: Request | undefined;
private _status: number = 0;
private _statusText: string = '';
private _url: string = '';
private _headers: types.Headers = {};
static from(response: ResponseChannel): Response {
return response._object;
}
static fromNullable(response: ResponseChannel | null): Response | null {
return response ? Response.from(response) : null;
}
constructor(connection: Connection, channel: ResponseChannel) {
super(connection, channel);
}
_initialize(payload: { request: RequestChannel, url: string, status: number, statusText: string, headers: types.Headers }) {
this._request = Request.from(payload.request);
this._status = payload.status;
this._statusText = payload.statusText;
this._url = payload.url;
this._headers = payload.headers;
}
url(): string {
return this._url;
}
ok(): boolean {
return this._status === 0 || (this._status >= 200 && this._status <= 299);
}
status(): number {
return this._status;
}
statusText(): string {
return this._statusText;
}
headers(): object {
return { ...this._headers };
}
async finished(): Promise<Error | null> {
return await this._channel.finished();
}
async body(): Promise<Buffer> {
return await this._channel.body();
}
async text(): Promise<string> {
const content = await this.body();
return content.toString('utf8');
}
async json(): Promise<object> {
const content = await this.text();
return JSON.parse(content);
}
request(): Request {
return this._request!;
}
frame(): Frame {
return this._request!.frame();
}
}

413
src/rpc/client/page.ts Normal file
View File

@ -0,0 +1,413 @@
/**
* Copyright 2017 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 { EventEmitter } from 'events';
import { Events } from '../../events';
import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import * as types from '../../types';
import { BrowserContextChannel, FrameChannel, PageChannel } from '../channels';
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 } from './network';
import { Connection } from '../connection';
export class Page extends ChannelOwner<PageChannel> {
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
private _browserContext: BrowserContext | undefined;
private _mainFrame: Frame | undefined;
private _frames = new Set<Frame>();
private _workers: Worker[] = [];
private _closed = false;
private _viewportSize: types.Size | null = null;
private _routes: { url: types.URLMatch, handler: RouteHandler }[] = [];
static from(page: PageChannel): Page {
return page._object;
}
static fromNullable(page: PageChannel | null): Page | null {
return page ? Page.from(page) : null;
}
constructor(connection: Connection, channel: PageChannel) {
super(connection, channel);
}
_initialize(payload: { browserContext: BrowserContextChannel, mainFrame: FrameChannel, viewportSize: types.Size }) {
this._browserContext = BrowserContext.from(payload.browserContext);
this._mainFrame = Frame.from(payload.mainFrame);
this._frames.add(this._mainFrame);
this._viewportSize = payload.viewportSize;
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 }) => this._onFrameNavigated(Frame.from(frame), url));
this._channel.on('request', request => this.emit(Events.Page.Request, Request.from(request)));
this._channel.on('response', response => this.emit(Events.Page.Response, Response.from(response)));
this._channel.on('requestFinished', request => this.emit(Events.Page.Request, Request.from(request)));
this._channel.on('requestFailed', request => this.emit(Events.Page.Request, Request.from(request)));
this._channel.on('close', () => this._onClose());
}
private _onFrameAttached(frame: Frame) {
this._frames.add(frame);
if (frame._parentFrame)
frame._parentFrame._childFrames.add(frame);
this.emit(Events.Page.FrameAttached, frame);
}
private _onFrameDetached(frame: Frame) {
this._frames.delete(frame);
if (frame._parentFrame)
frame._parentFrame._childFrames.delete(frame);
this.emit(Events.Page.FrameDetached, frame);
}
private _onFrameNavigated(frame: Frame, url: string) {
frame._url = url;
this.emit(Events.Page.FrameNavigated, frame);
}
private _onClose() {
this._browserContext!._pages.delete(this);
this.emit(Events.Page.Close);
}
context(): BrowserContext {
return this._browserContext!;
}
async opener(): Promise<Page | null> {
return Page.fromNullable(await this._channel.opener());
}
mainFrame(): Frame {
return this._mainFrame!!;
}
frame(options: string | { name?: string, url?: types.URLMatch }): Frame | null {
const name = helper.isString(options) ? options : options.name;
const url = helper.isObject(options) ? options.url : undefined;
assert(name || url, 'Either name or url matcher should be specified');
return this.frames().find(f => {
if (name)
return f.name() === name;
return helper.urlMatches(f.url(), url);
}) || null;
}
frames(): Frame[] {
return [...this._frames];
}
setDefaultNavigationTimeout(timeout: number) {
this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}
setDefaultTimeout(timeout: number) {
this._channel.setDefaultTimeoutNoReply({ timeout });
}
async $(selector: string): Promise<ElementHandle<Element> | null> {
return await this._mainFrame!.$(selector);
}
async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<ElementHandle<Element> | null> {
return await this._mainFrame!.waitForSelector(selector, options);
}
async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
return await this._mainFrame!.dispatchEvent(selector, type, eventInit, options);
}
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._mainFrame!.evaluateHandle(pageFunction, arg);
}
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._mainFrame!.$eval(selector, pageFunction, arg);
}
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._mainFrame!.$$eval(selector, pageFunction, arg);
}
async $$(selector: string): Promise<ElementHandle<Element>[]> {
return await this._mainFrame!.$$(selector);
}
async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise<ElementHandle> {
return await this._mainFrame!.addScriptTag(options);
}
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
return await this._mainFrame!.addStyleTag(options);
}
async exposeFunction(name: string, playwrightFunction: Function) {
await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args));
}
async exposeBinding(name: string, playwrightBinding: FunctionWithSource) {
await this._channel.exposeBinding({ name });
}
async setExtraHTTPHeaders(headers: types.Headers) {
await this._channel.setExtraHTTPHeaders({ headers });
}
url(): string {
return this.mainFrame().url();
}
async content(): Promise<string> {
return this.mainFrame().content();
}
async setContent(html: string, options?: types.NavigateOptions): Promise<void> {
return this.mainFrame().setContent(html, options);
}
async goto(url: string, options?: GotoOptions): Promise<Response | null> {
return this.mainFrame().goto(url, options);
}
async reload(options?: types.NavigateOptions): Promise<Response | null> {
return Response.fromNullable(await this._channel.reload({ options }));
}
async waitForLoadState(state?: types.LifecycleEvent, options?: types.TimeoutOptions): Promise<void> {
return this._mainFrame!.waitForLoadState(state, options);
}
async waitForNavigation(options?: types.WaitForNavigationOptions): Promise<Response | null> {
return this._mainFrame!.waitForNavigation(options);
}
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean), options: types.TimeoutOptions = {}): Promise<Request> {
const predicate = (request: Request) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
return helper.urlMatches(request.url(), urlOrPredicate);
return urlOrPredicate(request);
};
return this.waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout });
}
async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean), options: types.TimeoutOptions = {}): Promise<Response> {
const predicate = (response: Response) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
return helper.urlMatches(response.url(), urlOrPredicate);
return urlOrPredicate(response);
};
return this.waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout });
}
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
return await this._channel.waitForEvent({ event });
}
async goBack(options?: types.NavigateOptions): Promise<Response | null> {
return Response.fromNullable(await this._channel.goBack({ options }));
}
async goForward(options?: types.NavigateOptions): Promise<Response | null> {
return Response.fromNullable(await this._channel.goForward({ options }));
}
async emulateMedia(options: { media?: types.MediaType, colorScheme?: types.ColorScheme }) {
await this._channel.emulateMedia({ options });
}
async setViewportSize(viewportSize: types.Size) {
await this._channel.setViewportSize({ viewportSize });
}
viewportSize(): types.Size | null {
return this._viewportSize;
}
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 this.mainFrame().evaluate(pageFunction, arg);
}
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
const source = await helper.evaluationScript(script, arg);
await this._channel.addInitScript({ source });
}
async route(url: types.URLMatch, handler: RouteHandler): Promise<void> {
this._routes.push({ url, handler });
if (this._routes.length === 1)
await this._channel.setNetworkInterceptionEnabled({ enabled: true });
}
async unroute(url: types.URLMatch, handler?: RouteHandler): Promise<void> {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0)
await this._channel.setNetworkInterceptionEnabled({ enabled: false });
}
async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
return await this._channel.screenshot({ options });
}
async title(): Promise<string> {
return this.mainFrame().title();
}
async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) {
await this._channel.close({ options });
}
isClosed(): boolean {
return this._closed;
}
async click(selector: string, options?: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
return this.mainFrame().click(selector, options);
}
async dblclick(selector: string, options?: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
return this.mainFrame().dblclick(selector, options);
}
async fill(selector: string, value: string, options?: types.NavigatingActionWaitOptions) {
return this.mainFrame().fill(selector, value, options);
}
async focus(selector: string, options?: types.TimeoutOptions) {
return this.mainFrame().focus(selector, options);
}
async textContent(selector: string, options?: types.TimeoutOptions): Promise<null|string> {
return this.mainFrame().textContent(selector, options);
}
async innerText(selector: string, options?: types.TimeoutOptions): Promise<string> {
return this.mainFrame().innerText(selector, options);
}
async innerHTML(selector: string, options?: types.TimeoutOptions): Promise<string> {
return this.mainFrame().innerHTML(selector, options);
}
async getAttribute(selector: string, name: string, options?: types.TimeoutOptions): Promise<string | null> {
return this.mainFrame().getAttribute(selector, name, options);
}
async hover(selector: string, options?: types.PointerActionOptions & types.PointerActionWaitOptions) {
return this.mainFrame().hover(selector, options);
}
async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options?: types.NavigatingActionWaitOptions): Promise<string[]> {
return this.mainFrame().selectOption(selector, values, options);
}
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions): Promise<void> {
return this.mainFrame().setInputFiles(selector, files, options);
}
async type(selector: string, text: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
return this.mainFrame().type(selector, text, options);
}
async press(selector: string, key: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
return this.mainFrame().press(selector, key, options);
}
async check(selector: string, options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
return this.mainFrame().check(selector, options);
}
async uncheck(selector: string, options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
return this.mainFrame().uncheck(selector, options);
}
async waitForTimeout(timeout: number) {
await this.mainFrame().waitForTimeout(timeout);
}
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R>(pageFunction: Func1<void, R>, arg?: any, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>> {
return this.mainFrame().waitForFunction(pageFunction, arg, options);
}
workers(): Worker[] {
return this._workers;
}
on(event: string | symbol, listener: Listener): this {
if (event === Events.Page.FileChooser) {
if (!this.listenerCount(event))
this._channel.setFileChooserInterceptedNoReply({ intercepted: true });
}
super.on(event, listener);
return this;
}
removeListener(event: string | symbol, listener: Listener): this {
super.removeListener(event, listener);
if (event === Events.Page.FileChooser && !this.listenerCount(event))
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 });
}
}

145
src/rpc/connection.ts Normal file
View File

@ -0,0 +1,145 @@
/**
* 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 { EventEmitter } from 'ws';
import { Browser } from './client/browser';
import { BrowserContext } from './client/browserContext';
import { BrowserType } from './client/browserType';
import { ChannelOwner } from './client/channelOwner';
import { ElementHandle } from './client/elementHandle';
import { Frame } from './client/frame';
import { JSHandle } from './client/jsHandle';
import { Request, Response } from './client/network';
import { Page } from './client/page';
import debug = require('debug');
import { Channel } from './channels';
export class Connection {
private _channels = new Map<string, Channel>();
sendMessageToServerTransport = (message: any): Promise<any> => Promise.resolve();
constructor() {}
createRemoteObject(type: string, guid: string): any {
const channel = this._createChannel(guid) as any;
this._channels.set(guid, channel);
let result: ChannelOwner<any>;
switch (type) {
case 'browserType':
result = new BrowserType(this, channel);
break;
case 'browser':
result = new Browser(this, channel);
break;
case 'context':
result = new BrowserContext(this, channel);
break;
case 'page':
result = new Page(this, channel);
break;
case 'frame':
result = new Frame(this, channel);
break;
case 'request':
result = new Request(this, channel);
break;
case 'response':
result = new Response(this, channel);
break;
case 'jsHandle':
result = new JSHandle(this, channel);
break;
case 'elementHandle':
result = new ElementHandle(this, channel);
break;
default:
throw new Error('Missing type ' + type);
}
channel._object = result;
return result;
}
async sendMessageToServer(message: { guid: string, method: string, params: any }) {
const converted = {...message, params: this._replaceChannelsWithGuids(message.params)};
debug('pw:channel:command')(converted);
const response = await this.sendMessageToServerTransport(converted);
debug('pw:channel:response')(response);
return this._replaceGuidsWithChannels(response);
}
dispatchMessageFromServer(message: { guid: string, method: string, params: any }) {
debug('pw:channel:event')(message);
const { guid, method, params } = message;
if (method === '__create__') {
this.createRemoteObject(params.type, guid);
return;
}
const channel = this._channels.get(guid)!;
if (message.method === '__init__') {
channel._object._initialize(this._replaceGuidsWithChannels(params));
return;
}
channel.emit(method, this._replaceGuidsWithChannels(params));
}
private _createChannel(guid: string): Channel {
const base = new EventEmitter();
(base as any)._guid = guid;
return new Proxy(base, {
get: (obj: any, prop) => {
if (String(prop).startsWith('_'))
return obj[prop];
if (prop === 'then')
return obj.then;
if (prop === 'emit')
return obj.emit;
if (prop === 'on')
return obj.on;
if (prop === 'addEventListener')
return obj.addListener;
if (prop === 'removeEventListener')
return obj.removeListener;
return (params: any) => this.sendMessageToServer({ guid, method: String(prop), params });
},
});
}
private _replaceChannelsWithGuids(payload: any): any {
if (!payload)
return payload;
if (Array.isArray(payload))
return payload.map(p => this._replaceChannelsWithGuids(p));
if (payload._guid)
return { guid: payload._guid };
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceChannelsWithGuids(v)]));
return payload;
}
private _replaceGuidsWithChannels(payload: any): any {
if (!payload)
return payload;
if (Array.isArray(payload))
return payload.map(p => this._replaceGuidsWithChannels(p));
if (payload.guid && this._channels.has(payload.guid))
return this._channels.get(payload.guid);
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithChannels(v)]));
return payload;
}
}

86
src/rpc/dispatcher.ts Normal file
View File

@ -0,0 +1,86 @@
/**
* 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 { EventEmitter } from 'events';
import { helper } from '../helper';
import { Channel } from './channels';
export class Dispatcher extends EventEmitter implements Channel {
readonly _guid: string;
readonly _type: string;
protected _scope: DispatcherScope;
_object: any;
constructor(scope: DispatcherScope, object: any, type: string, guid = type + '@' + helper.guid()) {
super();
this._type = type;
this._guid = guid;
this._object = object;
this._scope = scope;
scope.dispatchers.set(this._guid, this);
object[scope.dispatcherSymbol] = this;
this._scope.sendMessageToClient(this._guid, '__create__', { type });
}
_initialize(payload: any) {
this._scope.sendMessageToClient(this._guid, '__init__', payload);
}
_dispatchEvent(method: string, params: Dispatcher | any = {}) {
this._scope.sendMessageToClient(this._guid, method, params);
}
}
export class DispatcherScope {
readonly dispatchers = new Map<string, Dispatcher>();
readonly dispatcherSymbol = Symbol('dispatcher');
sendMessageToClientTransport = (message: any) => {};
async sendMessageToClient(guid: string, method: string, params: any): Promise<any> {
this.sendMessageToClientTransport({ guid, method, params: this._replaceDispatchersWithGuids(params) });
}
async dispatchMessageFromClient(message: any): Promise<any> {
const dispatcher = this.dispatchers.get(message.guid)!;
const value = await (dispatcher as any)[message.method](this._replaceGuidsWithDispatchers(message.params));
const result = this._replaceDispatchersWithGuids(value);
return result;
}
private _replaceDispatchersWithGuids(payload: any): any {
if (!payload)
return payload;
if (payload instanceof Dispatcher)
return { guid: payload._guid };
if (Array.isArray(payload))
return payload.map(p => this._replaceDispatchersWithGuids(p));
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceDispatchersWithGuids(v)]));
return payload;
}
private _replaceGuidsWithDispatchers(payload: any): any {
if (!payload)
return payload;
if (Array.isArray(payload))
return payload.map(p => this._replaceGuidsWithDispatchers(p));
if (payload.guid && this.dispatchers.has(payload.guid))
return this.dispatchers.get(payload.guid);
if (typeof payload === 'object')
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithDispatchers(v)]));
return payload;
}
}

View File

@ -0,0 +1,110 @@
/**
* 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 { BrowserContextBase } from '../../browserContext';
import { Events } from '../../events';
import { BrowserDispatcher } from './browserDispatcher';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher } from './pageDispatcher';
import { PageChannel, BrowserContextChannel } from '../channels';
export class BrowserContextDispatcher extends Dispatcher implements BrowserContextChannel {
private _context: BrowserContextBase;
static from(scope: DispatcherScope, browserContext: BrowserContextBase): BrowserContextDispatcher {
if ((browserContext as any)[scope.dispatcherSymbol])
return (browserContext as any)[scope.dispatcherSymbol];
return new BrowserContextDispatcher(scope, browserContext);
}
constructor(scope: DispatcherScope, context: BrowserContextBase) {
super(scope, context, 'context');
this._initialize({
browser: BrowserDispatcher.from(scope, context._browserBase)
});
this._context = context;
context.on(Events.BrowserContext.Page, page => this._dispatchEvent('page', PageDispatcher.from(this._scope, page)));
context.on(Events.BrowserContext.Close, () => {
this._dispatchEvent('close');
});
}
async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) {
this._context.setDefaultNavigationTimeout(params.timeout);
}
async setDefaultTimeoutNoReply(params: { timeout: number }) {
this._context.setDefaultTimeout(params.timeout);
}
async exposeBinding(params: { name: string }): Promise<void> {
}
async newPage(): Promise<PageChannel> {
return PageDispatcher.from(this._scope, await this._context.newPage());
}
async cookies(params: { urls: string[] }): Promise<types.NetworkCookie[]> {
return await this._context.cookies(params.urls);
}
async addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void> {
await this._context.addCookies(params.cookies);
}
async clearCookies(): Promise<void> {
await this._context.clearCookies();
}
async grantPermissions(params: { permissions: string[], options: { origin?: string } }): Promise<void> {
await this._context.grantPermissions(params.permissions, params.options);
}
async clearPermissions(): Promise<void> {
await this._context.clearPermissions();
}
async setGeolocation(params: { geolocation: types.Geolocation | null }): Promise<void> {
await this._context.setGeolocation(params.geolocation);
}
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
await this._context.setExtraHTTPHeaders(params.headers);
}
async setOffline(params: { offline: boolean }): Promise<void> {
await this._context.setOffline(params.offline);
}
async setHTTPCredentials(params: { httpCredentials: types.Credentials | null }): Promise<void> {
await this._context.setHTTPCredentials(params.httpCredentials);
}
async addInitScript(params: { source: string }): Promise<void> {
await this._context._doAddInitScript(params.source);
}
async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void> {
}
async waitForEvent(params: { event: string }): Promise<any> {
}
async close(): Promise<void> {
this._context.close();
}
}

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 { BrowserBase } from '../../browser';
import { BrowserContextBase } from '../../browserContext';
import * as types from '../../types';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserChannel, BrowserContextChannel, PageChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { PageDispatcher } from './pageDispatcher';
export class BrowserDispatcher extends Dispatcher implements BrowserChannel {
private _browser: BrowserBase;
static from(scope: DispatcherScope, browser: BrowserBase): BrowserDispatcher {
if ((browser as any)[scope.dispatcherSymbol])
return (browser as any)[scope.dispatcherSymbol];
return new BrowserDispatcher(scope, browser);
}
static fromNullable(scope: DispatcherScope, browser: BrowserBase | null): BrowserDispatcher | null {
if (!browser)
return null;
return BrowserDispatcher.from(scope, browser);
}
constructor(scope: DispatcherScope, browser: BrowserBase) {
super(scope, browser, 'browser');
this._initialize({});
this._browser = browser;
}
async newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel> {
return BrowserContextDispatcher.from(this._scope, await this._browser.newContext(params.options) as BrowserContextBase);
}
async newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel> {
return PageDispatcher.from(this._scope, await this._browser.newPage(params.options));
}
async close(): Promise<void> {
await this._browser.close();
}
}

View File

@ -0,0 +1,55 @@
/**
* 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 { BrowserBase } from '../../browser';
import { BrowserTypeBase } from '../../server/browserType';
import * as types from '../../types';
import { BrowserDispatcher } from './browserDispatcher';
import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextBase } from '../../browserContext';
import { BrowserContextDispatcher } from './browserContextDispatcher';
export class BrowserTypeDispatcher extends Dispatcher implements BrowserTypeChannel {
private _browserType: BrowserTypeBase;
static from(scope: DispatcherScope, browserType: BrowserTypeBase): BrowserTypeDispatcher {
if ((browserType as any)[scope.dispatcherSymbol])
return (browserType as any)[scope.dispatcherSymbol];
return new BrowserTypeDispatcher(scope, browserType);
}
constructor(scope: DispatcherScope, browserType: BrowserTypeBase) {
super(scope, browserType, 'browserType', browserType.name());
this._initialize({ executablePath: browserType.executablePath(), name: browserType.name() });
this._browserType = browserType;
}
async launch(params: { options?: types.LaunchOptions }): Promise<BrowserChannel> {
const browser = await this._browserType.launch(params.options || undefined);
return BrowserDispatcher.from(this._scope, browser as BrowserBase);
}
async launchPersistentContext(params: { userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions }): Promise<BrowserContextChannel> {
const browserContext = await this._browserType.launchPersistentContext(params.userDataDir, params.options);
return BrowserContextDispatcher.from(this._scope, browserContext as BrowserContextBase);
}
async connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel> {
const browser = await this._browserType.connect(params.options);
return BrowserDispatcher.from(this._scope, browser as BrowserBase);
}
}

View File

@ -0,0 +1,164 @@
/**
* 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 { ElementHandle } from '../../dom';
import * as js from '../../javascript';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel } from '../channels';
import { DispatcherScope } from '../dispatcher';
import { convertArg, FrameDispatcher } from './frameDispatcher';
import { JSHandleDispatcher } from './jsHandleDispatcher';
export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel {
private _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];
return new ElementHandleDispatcher(scope, handle);
}
static fromNullableElement(scope: DispatcherScope, handle: ElementHandle | null): ElementHandleDispatcher | null {
if (!handle)
return null;
return ElementHandleDispatcher.fromElement(scope, handle);
}
constructor(scope: DispatcherScope, elementHandle: ElementHandle) {
super(scope, elementHandle, true);
this._elementHandle = elementHandle;
this._initialize({ preview: elementHandle.toString(), frame: FrameDispatcher.from(scope, elementHandle._context.frame) });
this._elementHandle = elementHandle;
}
async ownerFrame(): Promise<FrameChannel | null> {
return FrameDispatcher.fromNullable(this._scope, await this._elementHandle.ownerFrame());
}
async contentFrame(): Promise<FrameChannel | null> {
return FrameDispatcher.fromNullable(this._scope, await this._elementHandle.contentFrame());
}
async getAttribute(params: { name: string }): Promise<string | null> {
return this._elementHandle.getAttribute(params.name);
}
async textContent(): Promise<string | null> {
return this._elementHandle.textContent();
}
async innerText(): Promise<string> {
return this._elementHandle.innerText();
}
async innerHTML(): Promise<string> {
return this._elementHandle.innerHTML();
}
async dispatchEvent(params: { type: string, eventInit: Object }) {
await this._elementHandle.dispatchEvent(params.type, params.eventInit);
}
async scrollIntoViewIfNeeded(params: { options?: types.TimeoutOptions }) {
await this._elementHandle.scrollIntoViewIfNeeded(params.options);
}
async hover(params: { options?: types.PointerActionOptions & types.PointerActionWaitOptions }) {
await this._elementHandle.hover(params.options);
}
async click(params: { options?: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions }) {
await this._elementHandle.click(params.options);
}
async dblclick(params: { options?: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions }) {
await this._elementHandle.dblclick(params.options);
}
async selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions }): Promise<string[]> {
return this._elementHandle.selectOption(params.values as any, params.options);
}
async fill(params: { value: string, options: types.NavigatingActionWaitOptions }) {
await this._elementHandle.fill(params.value, params.options);
}
async selectText(params: { options?: types.TimeoutOptions }) {
await this._elementHandle.selectText(params.options);
}
async setInputFiles(params: { files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions }) {
await this._elementHandle.setInputFiles(params.files, params.options);
}
async focus() {
await this._elementHandle.focus();
}
async type(params: { text: string, options: { delay?: number } & types.NavigatingActionWaitOptions }) {
await this._elementHandle.type(params.text, params.options);
}
async press(params: { key: string, options: { delay?: number } & types.NavigatingActionWaitOptions }) {
await this._elementHandle.type(params.key, params.options);
}
async check(params: { options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions }) {
await this._elementHandle.check(params.options);
}
async uncheck(params: { options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions }) {
await this._elementHandle.uncheck(params.options);
}
async boundingBox(): Promise<types.Rect | null> {
return await this._elementHandle.boundingBox();
}
async screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<Buffer> {
return await this._elementHandle.screenshot(params.options);
}
async querySelector(params: { selector: string }): Promise<ElementHandleChannel | null> {
return ElementHandleDispatcher.fromNullableElement(this._scope, await this._elementHandle.$(params.selector));
}
async querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]> {
const elements = await this._elementHandle.$$(params.selector);
return elements.map(e => ElementHandleDispatcher.fromElement(this._scope, e));
}
async $eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
}
async $$eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
}
}

View File

@ -0,0 +1,203 @@
/**
* 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 { Frame } from '../../frames';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, JSHandleChannel, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { JSHandleDispatcher } from './jsHandleDispatcher';
import { ResponseDispatcher } from './networkDispatchers';
import { PageDispatcher } from './pageDispatcher';
export class FrameDispatcher extends Dispatcher implements FrameChannel {
private _frame: Frame;
static from(scope: DispatcherScope, frame: Frame): FrameDispatcher {
if ((frame as any)[scope.dispatcherSymbol])
return (frame as any)[scope.dispatcherSymbol];
return new FrameDispatcher(scope, frame);
}
static fromNullable(scope: DispatcherScope, frame: Frame | null): FrameDispatcher | null {
if (!frame)
return null;
return FrameDispatcher.from(scope, frame);
}
constructor(scope: DispatcherScope, frame: Frame) {
super(scope, frame, 'frame');
this._frame = frame;
const parentFrame = frame.parentFrame();
this._initialize({
page: PageDispatcher.from(this._scope, frame._page),
url: frame.url(),
name: frame.name(),
parentFrame: FrameDispatcher.fromNullable(this._scope, parentFrame),
childFrame: frame.childFrames().map(f => FrameDispatcher.from(this._scope, f)),
isDetached: frame.isDetached()
});
}
async goto(params: { url: string, options: types.GotoOptions }): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._frame.goto(params.url, params.options));
}
async waitForLoadState(params: { state?: 'load' | 'domcontentloaded' | 'networkidle', options?: types.TimeoutOptions }): Promise<void> {
await this._frame.waitForLoadState(params.state, params.options);
}
async waitForNavigation(params: { options?: types.WaitForNavigationOptions }): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._frame.waitForNavigation(params.options));
}
async frameElement(): Promise<ElementHandleChannel> {
return ElementHandleDispatcher.fromElement(this._scope, await this._frame.frameElement());
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._evaluateExpression(params.expression, params.isFunction, convertArg(this._scope, params.arg));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
return ElementHandleDispatcher.fromElement(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, convertArg(this._scope, params.arg)));
}
async waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null> {
return ElementHandleDispatcher.fromNullableElement(this._scope, await this._frame.waitForSelector(params.selector));
}
async dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions }): Promise<void> {
return this._frame.dispatchEvent(params.selector, params.type, params.eventInit, params.options);
}
async $eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
}
async $$eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
}
async querySelector(params: { selector: string }): Promise<ElementHandleChannel | null> {
return ElementHandleDispatcher.fromNullableElement(this._scope, await this._frame.$(params.selector));
}
async querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]> {
const elements = await this._frame.$$(params.selector);
return elements.map(e => ElementHandleDispatcher.fromElement(this._scope, e));
}
async content(): Promise<string> {
return await this._frame.content();
}
async setContent(params: { html: string, options: types.NavigateOptions }): Promise<void> {
await this._frame.setContent(params.html, params.options);
}
async addScriptTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined, type?: string | undefined } }): Promise<ElementHandleChannel> {
return ElementHandleDispatcher.fromElement(this._scope, await this._frame.addScriptTag(params.options));
}
async addStyleTag(params: { options: { url?: string | undefined, path?: string | undefined, content?: string | undefined } }): Promise<ElementHandleChannel> {
return ElementHandleDispatcher.fromElement(this._scope, await this._frame.addStyleTag(params.options));
}
async click(params: { selector: string, options: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void> {
await this._frame.click(params.selector, params.options);
}
async dblclick(params: { selector: string, options: types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }}): Promise<void> {
await this._frame.dblclick(params.selector, params.options);
}
async fill(params: { selector: string, value: string, options: types.NavigatingActionWaitOptions }): Promise<void> {
await this._frame.fill(params.selector, params.value, params.options);
}
async focus(params: { selector: string, options: types.TimeoutOptions }): Promise<void> {
await this._frame.focus(params.selector, params.options);
}
async textContent(params: { selector: string, options: types.TimeoutOptions }): Promise<string | null> {
return await this._frame.textContent(params.selector, params.options);
}
async innerText(params: { selector: string, options: types.TimeoutOptions }): Promise<string> {
return await this._frame.innerText(params.selector, params.options);
}
async innerHTML(params: { selector: string, options: types.TimeoutOptions }): Promise<string> {
return await this._frame.innerHTML(params.selector, params.options);
}
async getAttribute(params: { selector: string, name: string, options: types.TimeoutOptions }): Promise<string | null> {
return await this._frame.getAttribute(params.selector, params.name, params.options);
}
async hover(params: { selector: string, options: types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } }): Promise<void> {
await this._frame.hover(params.selector, params.options);
}
async selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions }): Promise<string[]> {
return this._frame.selectOption(params.selector, params.values as any, params.options);
}
async setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void> {
await this._frame.setInputFiles(params.selector, params.files, params.options);
}
async type(params: { selector: string, text: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void> {
await this._frame.type(params.selector, params.text, params.options);
}
async press(params: { selector: string, key: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void> {
await this._frame.press(params.selector, params.key, params.options);
}
async check(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void> {
await this._frame.check(params.selector, params.options);
}
async uncheck(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void> {
await this._frame.uncheck(params.selector, params.options);
}
async waitForFunction(params: { expression: string, isFunction: boolean, arg: any; options: types.WaitForFunctionOptions }): Promise<JSHandleChannel> {
return ElementHandleDispatcher.from(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, convertArg(this._scope, params.arg), params.options));
}
async title(): Promise<string> {
return await this._frame.title();
}
}
export function convertArg(scope: DispatcherScope, arg: any): any {
if (arg === null)
return null;
if (Array.isArray(arg))
return arg.map(item => convertArg(scope, item));
if (arg instanceof JSHandleDispatcher)
return arg._object;
if (typeof arg === 'object') {
const result: any = {};
for (const key of Object.keys(arg))
result[key] = convertArg(scope, arg[key]);
return result;
}
return arg;
}

View File

@ -0,0 +1,56 @@
/**
* 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 js from '../../javascript';
import { JSHandleChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { convertArg } from './frameDispatcher';
export class JSHandleDispatcher extends Dispatcher implements JSHandleChannel {
readonly _jsHandle: js.JSHandle<any>;
constructor(scope: DispatcherScope, jsHandle: js.JSHandle, omitInit?: boolean) {
super(scope, jsHandle, jsHandle.asElement() ? 'elementHandle' : 'jsHandle');
if (!omitInit)
this._initialize({ preview: jsHandle.toString() });
this._jsHandle = jsHandle;
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._jsHandle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, convertArg(this._scope, params.arg));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
const jsHandle = await this._jsHandle._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, convertArg(this._scope, params.arg));
return new JSHandleDispatcher(this._scope, jsHandle);
}
async getPropertyList(): Promise<{ name: string, value: JSHandleChannel }[]> {
const map = await this._jsHandle.getProperties();
const result = [];
for (const [name, value] of map)
result.push({ name, value: new JSHandleDispatcher(this._scope, value) });
return result;
}
async jsonValue(): Promise<any> {
return this._jsHandle.jsonValue();
}
async dispose() {
await this._jsHandle.dispose();
}
}

View File

@ -0,0 +1,101 @@
/**
* 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 { Request, Response } from '../../network';
import * as types from '../../types';
import { RequestChannel, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { FrameDispatcher } from './frameDispatcher';
export class RequestDispatcher extends Dispatcher implements RequestChannel {
private _request: Request;
static from(scope: DispatcherScope, request: Request): RequestDispatcher {
if ((request as any)[scope.dispatcherSymbol])
return (request as any)[scope.dispatcherSymbol];
return new RequestDispatcher(scope, request);
}
static fromNullable(scope: DispatcherScope, request: Request | null): RequestDispatcher | null {
return request ? RequestDispatcher.from(scope, request) : null;
}
constructor(scope: DispatcherScope, request: Request) {
super(scope, request, 'request');
this._initialize({
url: request.url(),
resourceType: request.resourceType(),
method: request.method(),
postData: request.postData(),
headers: request.headers(),
isNavigationRequest: request.isNavigationRequest(),
failure: request.failure(),
frame: FrameDispatcher.from(this._scope, request.frame()),
redirectedFrom: RequestDispatcher.fromNullable(this._scope, request.redirectedFrom()),
redirectedTo: RequestDispatcher.fromNullable(this._scope, request.redirectedTo()),
});
this._request = request;
}
async continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void> {
}
async fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void> {
}
async abort(params: { errorCode: string }): Promise<void> {
}
async response(): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._request.response());
}
}
export class ResponseDispatcher extends Dispatcher implements ResponseChannel {
private _response: Response;
static from(scope: DispatcherScope, response: Response): ResponseDispatcher {
if ((response as any)[scope.dispatcherSymbol])
return (response as any)[scope.dispatcherSymbol];
return new ResponseDispatcher(scope, response);
}
static fromNullable(scope: DispatcherScope, response: Response | null): ResponseDispatcher | null {
return response ? ResponseDispatcher.from(scope, response) : null;
}
constructor(scope: DispatcherScope, response: Response) {
super(scope, response, 'response');
this._initialize({
frame: FrameDispatcher.from(this._scope, response.frame()),
request: RequestDispatcher.from(this._scope, response.request())!,
url: response.url(),
ok: response.ok(),
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
});
this._response = response;
}
async finished(): Promise<Error | null> {
return await this._response.finished();
}
async body(): Promise<Buffer> {
return await this._response.body();
}
}

View File

@ -0,0 +1,137 @@
/**
* 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 { Frame } from '../../frames';
import { Page } from '../../page';
import * as types from '../../types';
import { PageChannel, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { RequestDispatcher, ResponseDispatcher } from './networkDispatchers';
export class PageDispatcher extends Dispatcher implements PageChannel {
private _page: Page;
static from(scope: DispatcherScope, page: Page): PageDispatcher {
if ((page as any)[scope.dispatcherSymbol])
return (page as any)[scope.dispatcherSymbol];
return new PageDispatcher(scope, page);
}
static fromNullable(scope: DispatcherScope, page: Page | null): PageDispatcher | null {
if (!page)
return null;
return PageDispatcher.from(scope, page);
}
constructor(scope: DispatcherScope, page: Page) {
super(scope, page, 'page');
this._initialize({
browserContext: BrowserContextDispatcher.from(scope, page._browserContext),
mainFrame: FrameDispatcher.from(scope, page.mainFrame()),
frames: page.frames().map(f => FrameDispatcher.from(this._scope, f)),
});
this._page = page;
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));
page.on(Events.Page.Close, () => {
this._dispatchEvent('close');
});
page.on(Events.Page.Request, request => this._dispatchEvent('request', RequestDispatcher.from(this._scope, request)));
page.on(Events.Page.Response, response => this._dispatchEvent('response', ResponseDispatcher.from(this._scope, response)));
page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', ResponseDispatcher.from(this._scope, request)));
page.on(Events.Page.RequestFailed, request => this._dispatchEvent('requestFailed', ResponseDispatcher.from(this._scope, request)));
}
async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) {
this._page.setDefaultNavigationTimeout(params.timeout);
}
async setDefaultTimeoutNoReply(params: { timeout: number }) {
this._page.setDefaultTimeout(params.timeout);
}
async opener(): Promise<PageChannel | null> {
return PageDispatcher.fromNullable(this._scope, await this._page.opener());
}
async exposeBinding(params: { name: string }): Promise<void> {
}
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
await this._page.setExtraHTTPHeaders(params.headers);
}
async reload(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._page.reload(params.options));
}
async waitForEvent(params: { event: string }): Promise<any> {
}
async goBack(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._page.goBack(params.options));
}
async goForward(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null> {
return ResponseDispatcher.fromNullable(this._scope, await this._page.goForward(params.options));
}
async emulateMedia(params: { options: { media?: 'screen' | 'print', colorScheme?: 'dark' | 'light' | 'no-preference' } }): Promise<void> {
await this._page.emulateMedia(params.options);
}
async setViewportSize(params: { viewportSize: types.Size }): Promise<void> {
await this._page.setViewportSize(params.viewportSize);
}
async addInitScript(params: { source: string }): Promise<void> {
await this._page._addInitScriptExpression(params.source);
}
async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void> {
}
async screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer> {
return await this._page.screenshot(params.options);
}
async close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void> {
await this._page.close(params.options);
}
async setFileChooserInterceptedNoReply(params: { intercepted: boolean }) {
}
async title() {
return await this._page.title();
}
_onFrameAttached(frame: Frame) {
this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame));
}
_onFrameNavigated(frame: Frame) {
this._dispatchEvent('frameNavigated', { frame: FrameDispatcher.from(this._scope, frame), url: frame.url() });
}
_onFrameDetached(frame: Frame) {
this._dispatchEvent('frameDetached', FrameDispatcher.from(this._scope, frame));
}
}

View File

@ -19,6 +19,10 @@ const fs = require('fs');
const utils = require('./utils');
const TestRunner = require('../utils/testrunner/');
const {Environment} = require('../utils/testrunner/Test');
const { DispatcherScope } = require('../lib/rpc/dispatcher');
const { Connection } = require('../lib/rpc/connection');
const { helper } = require('../lib/helper');
const { BrowserTypeDispatcher } = require('../lib/rpc/server/browserTypeDispatcher');
function getCLIArgument(argName) {
for (let i = 0; i < process.argv.length; ++i) {
@ -90,11 +94,36 @@ function collect(browserNames) {
testRunner.collector().useEnvironment(e);
global.playwright = playwright;
// Channel substitute
let connection;
let dispatcherScope;
if (process.env.PWCHANNEL) {
dispatcherScope = new DispatcherScope();
connection = new Connection();
dispatcherScope.sendMessageToClientTransport = async message => {
setImmediate(() => connection.dispatchMessageFromServer(message));
};
connection.sendMessageToServerTransport = async message => {
const result = await dispatcherScope.dispatchMessageFromClient(message);
await new Promise(f => setImmediate(f));
return result;
};
}
for (const browserName of browserNames) {
const browserType = playwright[browserName];
let overridenBrowserType = browserType;
// Channel substitute
if (process.env.PWCHANNEL) {
BrowserTypeDispatcher.from(dispatcherScope, browserType);
overridenBrowserType = connection.createRemoteObject('browserType', browserType.name());
}
const browserTypeEnvironment = new Environment('BrowserType');
browserTypeEnvironment.beforeAll(async state => {
state.browserType = browserType;
state.browserType = overridenBrowserType;
});
browserTypeEnvironment.afterAll(async state => {
delete state.browserType;