chore(rpc): support routes and bindings (#2725)
This commit is contained in:
parent
064a0a1154
commit
18d6140d3e
|
|
@ -362,6 +362,21 @@ export function logPolitely(toBeLogged: string) {
|
|||
console.log(toBeLogged); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
export function serializeError(e: any): types.Error {
|
||||
if (e instanceof Error)
|
||||
return { message: e.message, stack: e.stack };
|
||||
return { value: e };
|
||||
}
|
||||
|
||||
export function parseError(error: types.Error): any {
|
||||
if (error.message !== undefined) {
|
||||
const e = new Error(error.message);
|
||||
e.stack = error.stack;
|
||||
return e;
|
||||
}
|
||||
return error.value;
|
||||
}
|
||||
|
||||
const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']);
|
||||
|
||||
export const helper = Helper;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export class Request {
|
|||
private _redirectedTo: Request | null = null;
|
||||
readonly _documentId?: string;
|
||||
readonly _isFavicon: boolean;
|
||||
private _failureText: string | null = null;
|
||||
_failureText: string | null = null;
|
||||
private _url: string;
|
||||
private _resourceType: string;
|
||||
private _method: string;
|
||||
|
|
@ -182,7 +182,7 @@ export class Request {
|
|||
return this._redirectedTo;
|
||||
}
|
||||
|
||||
failure(): { errorText: string; } | null {
|
||||
failure(): { errorText: string } | null {
|
||||
if (this._failureText === null)
|
||||
return null;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -24,65 +24,68 @@ export interface Channel extends EventEmitter {
|
|||
}
|
||||
|
||||
export interface BrowserTypeChannel extends Channel {
|
||||
connect(params: { options: types.ConnectOptions }): Promise<BrowserChannel>;
|
||||
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 {
|
||||
close(): Promise<void>;
|
||||
newContext(params: { options?: types.BrowserContextOptions }): Promise<BrowserContextChannel>;
|
||||
newPage(params: { options?: types.BrowserContextOptions }): Promise<PageChannel>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface BrowserContextChannel extends Channel {
|
||||
addCookies(params: { cookies: types.SetNetworkCookieParam[] }): Promise<void>;
|
||||
addInitScript(params: { source: string }): Promise<void>;
|
||||
clearCookies(): Promise<void>;
|
||||
clearPermissions(): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
cookies(params: { urls: string[] }): Promise<types.NetworkCookie[]>;
|
||||
exposeBinding(params: { name: string }): Promise<void>;
|
||||
grantPermissions(params: { permissions: string[]; options?: { origin?: string } }): Promise<void>;
|
||||
newPage(): Promise<PageChannel>;
|
||||
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>;
|
||||
setGeolocation(params: { geolocation: types.Geolocation | null }): Promise<void>;
|
||||
setHTTPCredentials(params: { httpCredentials: types.Credentials | null }): Promise<void>;
|
||||
addInitScript(params: { source: string }): Promise<void>;
|
||||
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
|
||||
setOffline(params: { offline: 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: 'bindingCall', callback: (params: BindingCallChannel) => void): this;
|
||||
on(event: 'close', callback: () => void): this;
|
||||
on(event: 'console', callback: (params: ConsoleMessageChannel) => void): this;
|
||||
on(event: 'frameAttached', callback: (params: FrameChannel) => void): this;
|
||||
on(event: 'frameDetached', callback: (params: FrameChannel) => void): this;
|
||||
on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this;
|
||||
on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this;
|
||||
on(event: 'pageError', callback: (params: { error: types.Error }) => void): this;
|
||||
on(event: 'request', callback: (params: RequestChannel) => void): this;
|
||||
on(event: 'requestFailed', callback: (params: { request: RequestChannel, failureText: string | null }) => void): this;
|
||||
on(event: 'requestFinished', callback: (params: RequestChannel) => void): this;
|
||||
on(event: 'response', callback: (params: ResponseChannel) => void): this;
|
||||
on(event: 'route', callback: (params: { route: RouteChannel, request: RequestChannel }) => void): this;
|
||||
|
||||
setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
|
||||
setDefaultTimeoutNoReply(params: { timeout: number }): Promise<void>;
|
||||
setFileChooserInterceptedNoReply(params: { intercepted: boolean }): Promise<void>;
|
||||
|
||||
opener(): Promise<PageChannel | null>;
|
||||
addInitScript(params: { source: string }): Promise<void>;
|
||||
close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void>;
|
||||
emulateMedia(params: { options: { media?: 'screen' | 'print', colorScheme?: 'dark' | 'light' | 'no-preference' } }): Promise<void>;
|
||||
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>;
|
||||
opener(): Promise<PageChannel | null>;
|
||||
reload(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
|
||||
screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer>;
|
||||
close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void>;
|
||||
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
|
||||
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
|
||||
setViewportSize(params: { viewportSize: types.Size }): Promise<void>;
|
||||
|
||||
// Input
|
||||
keyboardDown(params: { key: string }): Promise<void>;
|
||||
|
|
@ -100,87 +103,86 @@ export interface PageChannel extends Channel {
|
|||
}
|
||||
|
||||
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>;
|
||||
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
|
||||
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>;
|
||||
check(params: { selector: string, options: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
click(params: { selector: string, options: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
content(): Promise<string>;
|
||||
dblclick(params: { selector: string, options: types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }}): Promise<void>;
|
||||
dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions }): Promise<void>;
|
||||
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any>;
|
||||
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel>;
|
||||
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>;
|
||||
frameElement(): Promise<ElementHandleChannel>;
|
||||
getAttribute(params: { selector: string, name: string, options: types.TimeoutOptions }): Promise<string | null>;
|
||||
goto(params: { url: string, options: types.GotoOptions }): Promise<ResponseChannel | 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>;
|
||||
innerHTML(params: { selector: string, options: types.TimeoutOptions }): Promise<string>;
|
||||
innerText(params: { selector: string, options: types.TimeoutOptions }): Promise<string>;
|
||||
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>;
|
||||
querySelector(params: { selector: string }): Promise<ElementHandleChannel | null>;
|
||||
querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]>;
|
||||
selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions }): Promise<string[]>;
|
||||
setContent(params: { html: string, options: types.NavigateOptions }): Promise<void>;
|
||||
setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void>;
|
||||
textContent(params: { selector: string, options: types.TimeoutOptions }): Promise<string | null>;
|
||||
title(): Promise<string>;
|
||||
type(params: { selector: string, text: string, options: { delay?: number | undefined } & types.TimeoutOptions & { 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>;
|
||||
waitForLoadState(params: { state: types.LifecycleEvent, options: types.TimeoutOptions }): Promise<void>;
|
||||
waitForNavigation(params: { options: types.WaitForNavigationOptions }): Promise<ResponseChannel | null>;
|
||||
waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null>;
|
||||
}
|
||||
|
||||
export interface JSHandleChannel extends Channel {
|
||||
dispose(): Promise<void>;
|
||||
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>;
|
||||
$$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
|
||||
$eval(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<any>;
|
||||
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>;
|
||||
click(params: { options?: types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
contentFrame(): Promise<FrameChannel | null>;
|
||||
dblclick(params: { options?: types.PointerActionOptions & types.MouseMultiClickOptions & 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>;
|
||||
|
||||
fill(params: { value: string; options?: types.NavigatingActionWaitOptions }): Promise<void>;
|
||||
focus(): Promise<void>;
|
||||
getAttribute(params: { name: string }): Promise<string | null>;
|
||||
hover(params: { options?: types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } }): Promise<void>;
|
||||
innerHTML(): Promise<string>;
|
||||
innerText(): Promise<string>;
|
||||
ownerFrame(): Promise<FrameChannel | null>;
|
||||
press(params: { key: string; options?: { delay?: number } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
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>;
|
||||
screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<Buffer>;
|
||||
scrollIntoViewIfNeeded(params: { options?: types.TimeoutOptions }): Promise<void>;
|
||||
selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null; options?: types.NavigatingActionWaitOptions }): string[] | Promise<string[]>;
|
||||
selectText(params: { options?: types.TimeoutOptions }): Promise<void>;
|
||||
setInputFiles(params: { files: string | string[] | types.FilePayload | types.FilePayload[], options?: types.NavigatingActionWaitOptions }): Promise<void>;
|
||||
textContent(): Promise<string | null>;
|
||||
type(params: { text: string; options?: { delay?: number } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
uncheck(params: { options?: types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } }): Promise<void>;
|
||||
}
|
||||
|
||||
export interface RequestChannel extends Channel {
|
||||
response(): Promise<ResponseChannel | null>;
|
||||
}
|
||||
|
||||
export interface RouteChannel extends Channel {
|
||||
abort(params: { errorCode: string }): Promise<void>;
|
||||
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 {
|
||||
|
|
@ -190,3 +192,8 @@ export interface ResponseChannel extends Channel {
|
|||
|
||||
export interface ConsoleMessageChannel extends Channel {
|
||||
}
|
||||
|
||||
export interface BindingCallChannel extends Channel {
|
||||
reject(params: { error: types.Error }): void;
|
||||
resolve(params: { result: any }): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
import * as frames from './frame';
|
||||
import { Page } from './page';
|
||||
import { Page, BindingCall, waitForEvent } from './page';
|
||||
import * as types from '../../types';
|
||||
import * as network from './network';
|
||||
import { BrowserContextChannel } from '../channels';
|
||||
|
|
@ -29,6 +29,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
|
|||
_pages = new Set<Page>();
|
||||
private _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
|
||||
_browser: Browser | undefined;
|
||||
readonly _bindings = new Map<string, frames.FunctionWithSource>();
|
||||
|
||||
static from(context: BrowserContextChannel): BrowserContext {
|
||||
return context._object;
|
||||
|
|
@ -42,7 +43,23 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
|
|||
super(connection, channel);
|
||||
}
|
||||
|
||||
_initialize() {
|
||||
_initialize() {}
|
||||
|
||||
_onRoute(route: network.Route, request: network.Request) {
|
||||
for (const {url, handler} of this._routes) {
|
||||
if (helper.urlMatches(request.url(), url)) {
|
||||
handler(route, request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
route.continue();
|
||||
}
|
||||
|
||||
async _onBinding(bindingCall: BindingCall) {
|
||||
const func = this._bindings.get(bindingCall.name);
|
||||
if (!func)
|
||||
return;
|
||||
bindingCall.call(func);
|
||||
}
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
|
|
@ -106,7 +123,15 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
|
|||
await this._channel.addInitScript({ source });
|
||||
}
|
||||
|
||||
async exposeBinding(name: string, playwrightBinding: frames.FunctionWithSource): Promise<void> {
|
||||
async exposeBinding(name: string, binding: frames.FunctionWithSource): Promise<void> {
|
||||
for (const page of this.pages()) {
|
||||
if (page._bindings.has(name))
|
||||
throw new Error(`Function "${name}" has been already registered in one of the pages`);
|
||||
}
|
||||
if (this._bindings.has(name))
|
||||
throw new Error(`Function "${name}" has been already registered`);
|
||||
this._bindings.set(name, binding);
|
||||
await this._channel.exposeBinding({ name });
|
||||
}
|
||||
|
||||
async exposeFunction(name: string, playwrightFunction: Function): Promise<void> {
|
||||
|
|
@ -126,7 +151,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel> {
|
|||
}
|
||||
|
||||
async waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise<any> {
|
||||
return await this._channel.waitForEvent({ event });
|
||||
return waitForEvent(this, event, optionsOrPredicate);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
|||
}
|
||||
|
||||
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 });
|
||||
return await this._elementChannel.selectOption({ values: convertSelectOptionValues(values), options });
|
||||
}
|
||||
|
||||
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
|
|
@ -148,3 +148,11 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
|
|||
return await this._elementChannel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSelectOptionValues(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null): string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null {
|
||||
if (values instanceof ElementHandle)
|
||||
return values._elementChannel;
|
||||
if (Array.isArray(values) && values.length && values[0] instanceof ElementHandle)
|
||||
return (values as ElementHandle[]).map((v: ElementHandle) => v._elementChannel);
|
||||
return values as any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import * as types from '../../types';
|
|||
import { FrameChannel } from '../channels';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { ElementHandle } from './elementHandle';
|
||||
import { ElementHandle, convertSelectOptionValues } from './elementHandle';
|
||||
import { JSHandle, Func1, FuncOn, SmartHandle, convertArg } from './jsHandle';
|
||||
import * as network from './network';
|
||||
import { Response } from './network';
|
||||
|
|
@ -36,9 +36,9 @@ export type FunctionWithSource = (source: { context: BrowserContext, page: Page,
|
|||
export class Frame extends ChannelOwner<FrameChannel> {
|
||||
_parentFrame: Frame | null = null;
|
||||
_url = '';
|
||||
_name = '';
|
||||
private _detached = false;
|
||||
_childFrames = new Set<Frame>();
|
||||
private _name = '';
|
||||
|
||||
static from(frame: FrameChannel): Frame {
|
||||
return frame._object;
|
||||
|
|
@ -52,10 +52,12 @@ export class Frame extends ChannelOwner<FrameChannel> {
|
|||
super(connection, channel);
|
||||
}
|
||||
|
||||
_initialize(payload: { parentFrame: FrameChannel | null }) {
|
||||
this._parentFrame = payload.parentFrame ? payload.parentFrame._object : null;
|
||||
_initialize(params: { name: string, url: string, parentFrame: FrameChannel | null }) {
|
||||
this._parentFrame = params.parentFrame ? params.parentFrame._object : null;
|
||||
if (this._parentFrame)
|
||||
this._parentFrame._childFrames.add(this);
|
||||
this._name = params.name;
|
||||
this._url = params.url;
|
||||
}
|
||||
|
||||
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {
|
||||
|
|
@ -192,7 +194,7 @@ export class Frame extends ChannelOwner<FrameChannel> {
|
|||
}
|
||||
|
||||
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 });
|
||||
return await this._channel.selectOption({ selector, values: convertSelectOptionValues(values), options });
|
||||
}
|
||||
|
||||
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { URLSearchParams } from 'url';
|
||||
import * as types from '../../types';
|
||||
import { RequestChannel, ResponseChannel, FrameChannel } from '../channels';
|
||||
import { RequestChannel, ResponseChannel, FrameChannel, RouteChannel } from '../channels';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Frame } from './frame';
|
||||
import { Connection } from '../connection';
|
||||
|
|
@ -48,7 +48,7 @@ export class Request extends ChannelOwner<RequestChannel> {
|
|||
private _redirectedFrom: Request | null = null;
|
||||
private _redirectedTo: Request | null = null;
|
||||
private _isNavigationRequest = false;
|
||||
private _failureText: string | null = null;
|
||||
_failureText: string | null = null;
|
||||
private _url: string = '';
|
||||
private _resourceType = '';
|
||||
private _method = '';
|
||||
|
|
@ -150,27 +150,35 @@ export class Request extends ChannelOwner<RequestChannel> {
|
|||
}
|
||||
}
|
||||
|
||||
export class Route {
|
||||
private _request: Request;
|
||||
export class Route extends ChannelOwner<RouteChannel> {
|
||||
private _request: Request | undefined;
|
||||
|
||||
constructor(request: Request) {
|
||||
this._request = request;
|
||||
static from(route: RouteChannel): Route {
|
||||
return route._object;
|
||||
}
|
||||
|
||||
constructor(connection: Connection, channel: RouteChannel) {
|
||||
super(connection, channel);
|
||||
}
|
||||
|
||||
_initialize(params: { request: RequestChannel }) {
|
||||
this._request = Request.from(params.request);
|
||||
}
|
||||
|
||||
request(): Request {
|
||||
return this._request;
|
||||
return this._request!;
|
||||
}
|
||||
|
||||
async abort(errorCode: string = 'failed') {
|
||||
await this._request._channel.abort({ errorCode });
|
||||
await this._channel.abort({ errorCode });
|
||||
}
|
||||
|
||||
async fulfill(response: types.FulfillResponse & { path?: string }) {
|
||||
await this._request._channel.fulfill({ response });
|
||||
await this._channel.fulfill({ response });
|
||||
}
|
||||
|
||||
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) {
|
||||
await this._request._channel.continue({ overrides });
|
||||
await this._channel.continue({ overrides });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@
|
|||
|
||||
import { EventEmitter } from 'events';
|
||||
import { Events } from '../../events';
|
||||
import { assert, assertMaxArguments, helper, Listener } from '../../helper';
|
||||
import { assert, assertMaxArguments, helper, Listener, serializeError, parseError } from '../../helper';
|
||||
import * as types from '../../types';
|
||||
import { BrowserContextChannel, FrameChannel, PageChannel } from '../channels';
|
||||
import { BrowserContextChannel, FrameChannel, PageChannel, BindingCallChannel, Channel } 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 { Request, Response, RouteHandler, Route } from './network';
|
||||
import { Connection } from '../connection';
|
||||
import { Keyboard, Mouse } from './input';
|
||||
import { Accessibility } from './accessibility';
|
||||
|
|
@ -45,6 +45,7 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
readonly accessibility: Accessibility;
|
||||
readonly keyboard: Keyboard;
|
||||
readonly mouse: Mouse;
|
||||
readonly _bindings = new Map<string, FunctionWithSource>();
|
||||
|
||||
static from(page: PageChannel): Page {
|
||||
return page._object;
|
||||
|
|
@ -67,15 +68,23 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
this._frames.add(this._mainFrame);
|
||||
this._viewportSize = payload.viewportSize;
|
||||
|
||||
this._channel.on('bindingCall', bindingCall => this._onBinding(BindingCall.from(bindingCall)));
|
||||
this._channel.on('close', () => this._onClose());
|
||||
this._channel.on('console', message => this.emit(Events.Page.Console, ConsoleMessage.from(message)));
|
||||
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('frameNavigated', ({ frame, url, name }) => this._onFrameNavigated(Frame.from(frame), url, name));
|
||||
this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error)));
|
||||
this._channel.on('request', request => this.emit(Events.Page.Request, Request.from(request)));
|
||||
this._channel.on('requestFailed', ({ request, failureText }) => this._onRequestFailed(Request.from(request), failureText));
|
||||
this._channel.on('requestFinished', request => this.emit(Events.Page.RequestFinished, Request.from(request)));
|
||||
this._channel.on('response', response => this.emit(Events.Page.Response, Response.from(response)));
|
||||
this._channel.on('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('console', message => this.emit(Events.Page.Console, ConsoleMessage.from(message)));
|
||||
this._channel.on('close', () => this._onClose());
|
||||
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
|
||||
}
|
||||
|
||||
private _onRequestFailed(request: Request, failureText: string | null) {
|
||||
request._failureText = failureText;
|
||||
this.emit(Events.Page.RequestFailed, request);
|
||||
}
|
||||
|
||||
private _onFrameAttached(frame: Frame) {
|
||||
|
|
@ -92,11 +101,29 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
this.emit(Events.Page.FrameDetached, frame);
|
||||
}
|
||||
|
||||
private _onFrameNavigated(frame: Frame, url: string) {
|
||||
private _onFrameNavigated(frame: Frame, url: string, name: string) {
|
||||
frame._url = url;
|
||||
frame._name = name;
|
||||
this.emit(Events.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
||||
private _onRoute(route: Route, request: Request) {
|
||||
for (const {url, handler} of this._routes) {
|
||||
if (helper.urlMatches(request.url(), url)) {
|
||||
handler(route, request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._browserContext!._onRoute(route, request);
|
||||
}
|
||||
|
||||
async _onBinding(bindingCall: BindingCall) {
|
||||
const func = this._bindings.get(bindingCall.name);
|
||||
if (func)
|
||||
bindingCall.call(func);
|
||||
this._browserContext!._onBinding(bindingCall);
|
||||
}
|
||||
|
||||
private _onClose() {
|
||||
this._browserContext!._pages.delete(this);
|
||||
this.emit(Events.Page.Close);
|
||||
|
|
@ -111,7 +138,7 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
}
|
||||
|
||||
mainFrame(): Frame {
|
||||
return this._mainFrame!!;
|
||||
return this._mainFrame!;
|
||||
}
|
||||
|
||||
frame(options: string | { name?: string, url?: types.URLMatch }): Frame | null {
|
||||
|
|
@ -186,7 +213,12 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args));
|
||||
}
|
||||
|
||||
async exposeBinding(name: string, playwrightBinding: FunctionWithSource) {
|
||||
async exposeBinding(name: string, binding: FunctionWithSource) {
|
||||
if (this._bindings.has(name))
|
||||
throw new Error(`Function "${name}" has been already registered`);
|
||||
if (this._browserContext!._bindings.has(name))
|
||||
throw new Error(`Function "${name}" has been already registered in the browser context`);
|
||||
this._bindings.set(name, binding);
|
||||
await this._channel.exposeBinding({ name });
|
||||
}
|
||||
|
||||
|
|
@ -241,9 +273,7 @@ export class Page extends ChannelOwner<PageChannel> {
|
|||
}
|
||||
|
||||
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
|
||||
const result = await this._channel.waitForEvent({ event });
|
||||
if (result._object)
|
||||
return result._object;
|
||||
return waitForEvent(this, event, optionsOrPredicate);
|
||||
}
|
||||
|
||||
async goBack(options?: types.NavigateOptions): Promise<Response | null> {
|
||||
|
|
@ -424,3 +454,54 @@ export class Worker extends EventEmitter {
|
|||
return await this._channel.evaluateHandle({ pageFunction, arg });
|
||||
}
|
||||
}
|
||||
|
||||
export class BindingCall extends ChannelOwner<BindingCallChannel> {
|
||||
name: string = '';
|
||||
source: { context: BrowserContext, page: Page, frame: Frame } | undefined;
|
||||
args: any[] = [];
|
||||
static from(channel: BindingCallChannel): BindingCall {
|
||||
return channel._object;
|
||||
}
|
||||
|
||||
constructor(connection: Connection, channel: BindingCallChannel) {
|
||||
super(connection, channel);
|
||||
}
|
||||
|
||||
_initialize(params: { name: string, context: BrowserContextChannel, page: PageChannel, frame: FrameChannel, args: any[] }) {
|
||||
this.name = params.name;
|
||||
this.source = {
|
||||
context: BrowserContext.from(params.context),
|
||||
page: Page.from(params.page),
|
||||
frame: Frame.from(params.frame)
|
||||
};
|
||||
this.args = params.args;
|
||||
}
|
||||
|
||||
async call(func: FunctionWithSource) {
|
||||
try {
|
||||
this._channel.resolve({ result: await func(this.source!, ...this.args) });
|
||||
} catch (e) {
|
||||
this._channel.reject({ error: serializeError(e) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForEvent(emitter: EventEmitter, event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
|
||||
// TODO: support timeout
|
||||
let predicate: Function | undefined;
|
||||
if (typeof optionsOrPredicate === 'function')
|
||||
predicate = optionsOrPredicate;
|
||||
else if (optionsOrPredicate.predicate)
|
||||
predicate = optionsOrPredicate.predicate;
|
||||
let callback: (a: any) => void;
|
||||
const result = new Promise(f => callback = f);
|
||||
const listener = helper.addEventListener(emitter, event, param => {
|
||||
// TODO: do not detect channel by guid.
|
||||
const object = param._guid ? (param as Channel)._object : param;
|
||||
if (predicate && !predicate(object))
|
||||
return;
|
||||
callback(object);
|
||||
helper.removeEventListeners([listener]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ 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 { Request, Response, Route } from './client/network';
|
||||
import { Page, BindingCall } from './client/page';
|
||||
import debug = require('debug');
|
||||
import { Channel } from './channels';
|
||||
import { ConsoleMessage } from './client/console';
|
||||
|
|
@ -60,6 +60,12 @@ export class Connection {
|
|||
case 'response':
|
||||
result = new Response(this, channel);
|
||||
break;
|
||||
case 'route':
|
||||
result = new Route(this, channel);
|
||||
break;
|
||||
case 'bindingCall':
|
||||
result = new BindingCall(this, channel);
|
||||
break;
|
||||
case 'jsHandle':
|
||||
result = new JSHandle(this, channel);
|
||||
break;
|
||||
|
|
@ -132,6 +138,9 @@ export class Connection {
|
|||
return payload.map(p => this._replaceChannelsWithGuids(p));
|
||||
if (payload._guid)
|
||||
return { guid: payload._guid };
|
||||
// TODO: send base64
|
||||
if (payload instanceof Buffer)
|
||||
return payload;
|
||||
if (typeof payload === 'object')
|
||||
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceChannelsWithGuids(v)]));
|
||||
return payload;
|
||||
|
|
@ -144,6 +153,9 @@ export class Connection {
|
|||
return payload.map(p => this._replaceGuidsWithChannels(p));
|
||||
if (payload.guid && this._channels.has(payload.guid))
|
||||
return this._channels.get(payload.guid);
|
||||
// TODO: send base64
|
||||
if (payload instanceof Buffer)
|
||||
return payload;
|
||||
if (typeof payload === 'object')
|
||||
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithChannels(v)]));
|
||||
return payload;
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ export class DispatcherScope {
|
|||
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;
|
||||
return this._replaceDispatchersWithGuids(value);
|
||||
}
|
||||
|
||||
private _replaceDispatchersWithGuids(payload: any): any {
|
||||
|
|
@ -67,6 +66,9 @@ export class DispatcherScope {
|
|||
return { guid: payload._guid };
|
||||
if (Array.isArray(payload))
|
||||
return payload.map(p => this._replaceDispatchersWithGuids(p));
|
||||
// TODO: send base64
|
||||
if (payload instanceof Buffer)
|
||||
return payload;
|
||||
if (typeof payload === 'object')
|
||||
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceDispatchersWithGuids(v)]));
|
||||
return payload;
|
||||
|
|
@ -79,6 +81,9 @@ export class DispatcherScope {
|
|||
return payload.map(p => this._replaceGuidsWithDispatchers(p));
|
||||
if (payload.guid && this.dispatchers.has(payload.guid))
|
||||
return this.dispatchers.get(payload.guid);
|
||||
// TODO: send base64
|
||||
if (payload instanceof Buffer)
|
||||
return payload;
|
||||
if (typeof payload === 'object')
|
||||
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithDispatchers(v)]));
|
||||
return payload;
|
||||
|
|
|
|||
|
|
@ -15,20 +15,22 @@
|
|||
*/
|
||||
|
||||
import * as types from '../../types';
|
||||
import { BrowserContextBase } from '../../browserContext';
|
||||
import { BrowserContextBase, BrowserContext } from '../../browserContext';
|
||||
import { Events } from '../../events';
|
||||
import { BrowserDispatcher } from './browserDispatcher';
|
||||
import { Dispatcher, DispatcherScope } from '../dispatcher';
|
||||
import { PageDispatcher } from './pageDispatcher';
|
||||
import { PageDispatcher, BindingCallDispatcher } from './pageDispatcher';
|
||||
import { PageChannel, BrowserContextChannel } from '../channels';
|
||||
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||
import { Page } from '../../page';
|
||||
|
||||
export class BrowserContextDispatcher extends Dispatcher implements BrowserContextChannel {
|
||||
private _context: BrowserContextBase;
|
||||
|
||||
static from(scope: DispatcherScope, browserContext: BrowserContextBase): BrowserContextDispatcher {
|
||||
static from(scope: DispatcherScope, browserContext: BrowserContext): BrowserContextDispatcher {
|
||||
if ((browserContext as any)[scope.dispatcherSymbol])
|
||||
return (browserContext as any)[scope.dispatcherSymbol];
|
||||
return new BrowserContextDispatcher(scope, browserContext);
|
||||
return new BrowserContextDispatcher(scope, browserContext as BrowserContextBase);
|
||||
}
|
||||
|
||||
constructor(scope: DispatcherScope, context: BrowserContextBase) {
|
||||
|
|
@ -52,6 +54,11 @@ export class BrowserContextDispatcher extends Dispatcher implements BrowserConte
|
|||
}
|
||||
|
||||
async exposeBinding(params: { name: string }): Promise<void> {
|
||||
this._context.exposeBinding(params.name, (source, ...args) => {
|
||||
const bindingCall = new BindingCallDispatcher(this._scope, params.name, source, args);
|
||||
this._dispatchEvent('bindingCall', bindingCall);
|
||||
return bindingCall.promise();
|
||||
});
|
||||
}
|
||||
|
||||
async newPage(): Promise<PageChannel> {
|
||||
|
|
@ -99,9 +106,20 @@ export class BrowserContextDispatcher extends Dispatcher implements BrowserConte
|
|||
}
|
||||
|
||||
async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void> {
|
||||
if (!params.enabled) {
|
||||
await this._context.unroute('**/*');
|
||||
return;
|
||||
}
|
||||
this._context.route('**/*', (route, request) => {
|
||||
this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
|
||||
});
|
||||
}
|
||||
|
||||
async waitForEvent(params: { event: string }): Promise<any> {
|
||||
const result = await this._context.waitForEvent(params.event);
|
||||
if (result instanceof Page)
|
||||
return PageDispatcher.from(this._scope, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { convertArg, FrameDispatcher } from './frameDispatcher';
|
|||
import { JSHandleDispatcher } from './jsHandleDispatcher';
|
||||
|
||||
export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel {
|
||||
private _elementHandle: ElementHandle;
|
||||
readonly _elementHandle: ElementHandle;
|
||||
|
||||
static from(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
|
||||
if ((handle as any)[scope.dispatcherSymbol])
|
||||
|
|
@ -102,7 +102,7 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
|
|||
}
|
||||
|
||||
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);
|
||||
return this._elementHandle.selectOption(convertSelectOptionValues(params.values), params.options);
|
||||
}
|
||||
|
||||
async fill(params: { value: string, options: types.NavigatingActionWaitOptions }) {
|
||||
|
|
@ -162,3 +162,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
|
|||
return this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSelectOptionValues(values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null): string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null {
|
||||
if (values instanceof ElementHandleDispatcher)
|
||||
return values._elementHandle;
|
||||
if (Array.isArray(values) && values.length && values[0] instanceof ElementHandle)
|
||||
return (values as ElementHandleDispatcher[]).map((v: ElementHandleDispatcher) => v._elementHandle);
|
||||
return values as any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ 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 { ElementHandleDispatcher, convertSelectOptionValues } from './elementHandlerDispatcher';
|
||||
import { JSHandleDispatcher } from './jsHandleDispatcher';
|
||||
import { ResponseDispatcher } from './networkDispatchers';
|
||||
import { PageDispatcher } from './pageDispatcher';
|
||||
|
||||
export class FrameDispatcher extends Dispatcher implements FrameChannel {
|
||||
private _frame: Frame;
|
||||
|
|
@ -43,12 +42,9 @@ export class FrameDispatcher extends Dispatcher implements FrameChannel {
|
|||
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()
|
||||
parentFrame: FrameDispatcher.fromNullable(this._scope, parentFrame)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +150,7 @@ export class FrameDispatcher extends Dispatcher implements FrameChannel {
|
|||
}
|
||||
|
||||
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);
|
||||
return this._frame.selectOption(params.selector, convertSelectOptionValues(params.values), params.options);
|
||||
}
|
||||
|
||||
async setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Request, Response } from '../../network';
|
||||
import { Request, Response, Route } from '../../network';
|
||||
import * as types from '../../types';
|
||||
import { RequestChannel, ResponseChannel } from '../channels';
|
||||
import { RequestChannel, ResponseChannel, RouteChannel } from '../channels';
|
||||
import { Dispatcher, DispatcherScope } from '../dispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
|
||||
|
|
@ -50,15 +50,6 @@ export class RequestDispatcher extends Dispatcher implements RequestChannel {
|
|||
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());
|
||||
}
|
||||
|
|
@ -99,3 +90,35 @@ export class ResponseDispatcher extends Dispatcher implements ResponseChannel {
|
|||
return await this._response.body();
|
||||
}
|
||||
}
|
||||
|
||||
export class RouteDispatcher extends Dispatcher implements RouteChannel {
|
||||
private _route: Route;
|
||||
|
||||
static from(scope: DispatcherScope, route: Route): RouteDispatcher {
|
||||
if ((route as any)[scope.dispatcherSymbol])
|
||||
return (route as any)[scope.dispatcherSymbol];
|
||||
return new RouteDispatcher(scope, route);
|
||||
}
|
||||
|
||||
static fromNullable(scope: DispatcherScope, route: Route | null): RouteDispatcher | null {
|
||||
return route ? RouteDispatcher.from(scope, route) : null;
|
||||
}
|
||||
|
||||
constructor(scope: DispatcherScope, route: Route) {
|
||||
super(scope, route, 'route');
|
||||
this._initialize({ request: RequestDispatcher.from(this._scope, route.request()) });
|
||||
this._route = route;
|
||||
}
|
||||
|
||||
async continue(params: { overrides: { method?: string, headers?: types.Headers, postData?: string } }): Promise<void> {
|
||||
await this._route.continue(params.overrides);
|
||||
}
|
||||
|
||||
async fulfill(params: { response: types.FulfillResponse & { path?: string } }): Promise<void> {
|
||||
await this._route.fulfill(params.response);
|
||||
}
|
||||
|
||||
async abort(params: { errorCode: string }): Promise<void> {
|
||||
await this._route.abort(params.errorCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConsoleMessage } from '../../console';
|
||||
import { Events } from '../../events';
|
||||
import { Request } from '../../network';
|
||||
import { Frame } from '../../frames';
|
||||
import { Page } from '../../page';
|
||||
import * as types from '../../types';
|
||||
import { ElementHandleChannel, PageChannel, ResponseChannel } from '../channels';
|
||||
import { ElementHandleChannel, PageChannel, ResponseChannel, BindingCallChannel } from '../channels';
|
||||
import { Dispatcher, DispatcherScope } from '../dispatcher';
|
||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
import { RequestDispatcher, ResponseDispatcher } from './networkDispatchers';
|
||||
import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
|
||||
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { serializeError, parseError } from '../../helper';
|
||||
|
||||
export class PageDispatcher extends Dispatcher implements PageChannel {
|
||||
private _page: Page;
|
||||
|
|
@ -49,17 +51,19 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
|
|||
frames: page.frames().map(f => FrameDispatcher.from(this._scope, f)),
|
||||
});
|
||||
this._page = page;
|
||||
page.on(Events.Page.Close, () => this._dispatchEvent('close'));
|
||||
page.on(Events.Page.Console, message => this._dispatchEvent('console', ConsoleMessageDispatcher.from(this._scope, message)));
|
||||
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.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) }));
|
||||
page.on(Events.Page.Request, request => this._dispatchEvent('request', RequestDispatcher.from(this._scope, request)));
|
||||
page.on(Events.Page.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', {
|
||||
request: RequestDispatcher.from(this._scope, request),
|
||||
failureText: request._failureText
|
||||
}));
|
||||
page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', RequestDispatcher.from(this._scope, request)));
|
||||
page.on(Events.Page.Response, response => this._dispatchEvent('response', ResponseDispatcher.from(this._scope, response)));
|
||||
page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', ResponseDispatcher.from(this._scope, request)));
|
||||
page.on(Events.Page.RequestFailed, request => this._dispatchEvent('requestFailed', ResponseDispatcher.from(this._scope, request)));
|
||||
page.on(Events.Page.Console, message => this._dispatchEvent('console', ConsoleMessageDispatcher.from(this._scope, message)));
|
||||
}
|
||||
|
||||
async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) {
|
||||
|
|
@ -75,6 +79,11 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
|
|||
}
|
||||
|
||||
async exposeBinding(params: { name: string }): Promise<void> {
|
||||
this._page.exposeBinding(params.name, (source, ...args) => {
|
||||
const bindingCall = new BindingCallDispatcher(this._scope, params.name, source, args);
|
||||
this._dispatchEvent('bindingCall', bindingCall);
|
||||
return bindingCall.promise();
|
||||
});
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
|
||||
|
|
@ -85,12 +94,6 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
|
|||
return ResponseDispatcher.fromNullable(this._scope, await this._page.reload(params.options));
|
||||
}
|
||||
|
||||
async waitForEvent(params: { event: string }): Promise<any> {
|
||||
const result = await this._page.waitForEvent(params.event);
|
||||
if (result instanceof ConsoleMessage)
|
||||
return ConsoleMessageDispatcher.from(this._scope, result);
|
||||
}
|
||||
|
||||
async goBack(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null> {
|
||||
return ResponseDispatcher.fromNullable(this._scope, await this._page.goBack(params.options));
|
||||
}
|
||||
|
|
@ -112,6 +115,13 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
|
|||
}
|
||||
|
||||
async setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void> {
|
||||
if (!params.enabled) {
|
||||
await this._page.unroute('**/*');
|
||||
return;
|
||||
}
|
||||
this._page.route('**/*', (route, request) => {
|
||||
this._dispatchEvent('route', { route: RouteDispatcher.from(this._scope, route), request: RequestDispatcher.from(this._scope, request) });
|
||||
});
|
||||
}
|
||||
|
||||
async screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer> {
|
||||
|
|
@ -177,10 +187,44 @@ export class PageDispatcher extends Dispatcher implements PageChannel {
|
|||
}
|
||||
|
||||
_onFrameNavigated(frame: Frame) {
|
||||
this._dispatchEvent('frameNavigated', { frame: FrameDispatcher.from(this._scope, frame), url: frame.url() });
|
||||
this._dispatchEvent('frameNavigated', { frame: FrameDispatcher.from(this._scope, frame), url: frame.url(), name: frame.name() });
|
||||
}
|
||||
|
||||
_onFrameDetached(frame: Frame) {
|
||||
this._dispatchEvent('frameDetached', FrameDispatcher.from(this._scope, frame));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class BindingCallDispatcher extends Dispatcher implements BindingCallChannel {
|
||||
private _resolve: ((arg: any) => void) | undefined;
|
||||
private _reject: ((error: any) => void) | undefined;
|
||||
private _promise: Promise<any>;
|
||||
|
||||
constructor(scope: DispatcherScope, name: string, source: { context: BrowserContext, page: Page, frame: Frame }, args: any[]) {
|
||||
super(scope, {}, 'bindingCall');
|
||||
this._initialize({
|
||||
name,
|
||||
context: BrowserContextDispatcher.from(scope, source.context),
|
||||
page: PageDispatcher.from(scope, source.page),
|
||||
frame: FrameDispatcher.from(scope, source.frame),
|
||||
args
|
||||
});
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
promise() {
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
resolve(params: { result: any }) {
|
||||
this._resolve!(params.result);
|
||||
}
|
||||
|
||||
reject(params: { error: types.Error }) {
|
||||
this._reject!(parseError(params.error));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -305,3 +305,9 @@ export type ConsoleMessageLocation = {
|
|||
lineNumber?: number,
|
||||
columnNumber?: number,
|
||||
};
|
||||
|
||||
export type Error = {
|
||||
message?: string,
|
||||
stack?: string,
|
||||
value?: any
|
||||
};
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ function collect(browserNames) {
|
|||
const browserType = playwright[browserName];
|
||||
let overridenBrowserType = browserType;
|
||||
|
||||
// Channel substitute
|
||||
if (process.env.PWCHANNEL) {
|
||||
// Channel substitute
|
||||
if (process.env.PWCHANNEL) {
|
||||
BrowserTypeDispatcher.from(dispatcherScope, browserType);
|
||||
overridenBrowserType = connection.createRemoteObject('browserType', browserType.name());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue