mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
chore: introduce the experimental rpc implementation (#2720)
This commit is contained in:
parent
5d5cf26a0e
commit
bab6833232
@ -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> {
|
||||
|
@ -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
175
src/rpc/channels.ts
Normal 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
65
src/rpc/client/browser.ts
Normal 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();
|
||||
}
|
||||
}
|
136
src/rpc/client/browserContext.ts
Normal file
136
src/rpc/client/browserContext.ts
Normal 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);
|
||||
}
|
||||
}
|
56
src/rpc/client/browserType.ts
Normal file
56
src/rpc/client/browserType.ts
Normal 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 }));
|
||||
}
|
||||
}
|
34
src/rpc/client/channelOwner.ts
Normal file
34
src/rpc/client/channelOwner.ts
Normal 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;
|
||||
}
|
150
src/rpc/client/elementHandle.ts
Normal file
150
src/rpc/client/elementHandle.ts
Normal 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
231
src/rpc/client/frame.ts
Normal 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
122
src/rpc/client/jsHandle.ts
Normal 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
251
src/rpc/client/network.ts
Normal 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
413
src/rpc/client/page.ts
Normal 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
145
src/rpc/connection.ts
Normal 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
86
src/rpc/dispatcher.ts
Normal 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;
|
||||
}
|
||||
}
|
110
src/rpc/server/browserContextDispatcher.ts
Normal file
110
src/rpc/server/browserContextDispatcher.ts
Normal 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();
|
||||
}
|
||||
}
|
57
src/rpc/server/browserDispatcher.ts
Normal file
57
src/rpc/server/browserDispatcher.ts
Normal 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();
|
||||
}
|
||||
}
|
55
src/rpc/server/browserTypeDispatcher.ts
Normal file
55
src/rpc/server/browserTypeDispatcher.ts
Normal 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);
|
||||
}
|
||||
}
|
164
src/rpc/server/elementHandlerDispatcher.ts
Normal file
164
src/rpc/server/elementHandlerDispatcher.ts
Normal 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));
|
||||
}
|
||||
}
|
203
src/rpc/server/frameDispatcher.ts
Normal file
203
src/rpc/server/frameDispatcher.ts
Normal 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;
|
||||
}
|
56
src/rpc/server/jsHandleDispatcher.ts
Normal file
56
src/rpc/server/jsHandleDispatcher.ts
Normal 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();
|
||||
}
|
||||
}
|
101
src/rpc/server/networkDispatchers.ts
Normal file
101
src/rpc/server/networkDispatchers.ts
Normal 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();
|
||||
}
|
||||
}
|
137
src/rpc/server/pageDispatcher.ts
Normal file
137
src/rpc/server/pageDispatcher.ts
Normal 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));
|
||||
}
|
||||
}
|
31
test/test.js
31
test/test.js
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user