2020-06-26 02:05:36 +03:00
|
|
|
/**
|
|
|
|
* 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';
|
2020-06-30 20:55:11 +03:00
|
|
|
import { TimeoutError } from '../../errors';
|
2020-06-26 02:05:36 +03:00
|
|
|
import { Events } from '../../events';
|
2020-06-27 21:10:07 +03:00
|
|
|
import { assert, assertMaxArguments, helper, Listener } from '../../helper';
|
2020-06-30 20:55:11 +03:00
|
|
|
import { TimeoutSettings } from '../../timeoutSettings';
|
2020-06-26 02:05:36 +03:00
|
|
|
import * as types from '../../types';
|
2020-07-11 01:39:11 +03:00
|
|
|
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PDFOptions } from '../channels';
|
2020-06-30 20:55:11 +03:00
|
|
|
import { parseError, serializeError } from '../serializers';
|
2020-06-26 04:01:18 +03:00
|
|
|
import { Accessibility } from './accessibility';
|
2020-06-30 20:55:11 +03:00
|
|
|
import { BrowserContext } from './browserContext';
|
|
|
|
import { ChannelOwner } from './channelOwner';
|
2020-06-26 22:28:27 +03:00
|
|
|
import { ConsoleMessage } from './consoleMessage';
|
2020-06-27 03:24:21 +03:00
|
|
|
import { Dialog } from './dialog';
|
|
|
|
import { Download } from './download';
|
2020-06-30 20:55:11 +03:00
|
|
|
import { ElementHandle } from './elementHandle';
|
|
|
|
import { Worker } from './worker';
|
|
|
|
import { Frame, FunctionWithSource, GotoOptions } from './frame';
|
|
|
|
import { Keyboard, Mouse } from './input';
|
|
|
|
import { Func1, FuncOn, SmartHandle } from './jsHandle';
|
|
|
|
import { Request, Response, Route, RouteHandler } from './network';
|
|
|
|
import { FileChooser } from './fileChooser';
|
|
|
|
import { Buffer } from 'buffer';
|
2020-07-10 01:33:01 +03:00
|
|
|
import { Coverage } from './coverage';
|
2020-06-26 02:05:36 +03:00
|
|
|
|
2020-06-26 22:28:27 +03:00
|
|
|
export class Page extends ChannelOwner<PageChannel, PageInitializer> {
|
2020-06-26 02:05:36 +03:00
|
|
|
|
2020-06-27 07:22:03 +03:00
|
|
|
private _browserContext: BrowserContext | undefined;
|
2020-06-27 03:24:21 +03:00
|
|
|
_ownedContext: BrowserContext | undefined;
|
|
|
|
|
2020-06-26 22:28:27 +03:00
|
|
|
private _mainFrame: Frame;
|
2020-06-26 02:05:36 +03:00
|
|
|
private _frames = new Set<Frame>();
|
2020-06-30 20:55:11 +03:00
|
|
|
_workers = new Set<Worker>();
|
2020-06-26 02:05:36 +03:00
|
|
|
private _closed = false;
|
2020-06-26 22:28:27 +03:00
|
|
|
private _viewportSize: types.Size | null;
|
2020-06-26 02:05:36 +03:00
|
|
|
private _routes: { url: types.URLMatch, handler: RouteHandler }[] = [];
|
|
|
|
|
2020-06-26 04:01:18 +03:00
|
|
|
readonly accessibility: Accessibility;
|
|
|
|
readonly keyboard: Keyboard;
|
|
|
|
readonly mouse: Mouse;
|
2020-07-10 01:33:01 +03:00
|
|
|
readonly coverage: Coverage;
|
2020-06-26 21:51:47 +03:00
|
|
|
readonly _bindings = new Map<string, FunctionWithSource>();
|
2020-06-27 07:22:03 +03:00
|
|
|
private _pendingWaitForEvents = new Map<(error: Error) => void, string>();
|
|
|
|
private _timeoutSettings = new TimeoutSettings();
|
2020-06-30 04:58:09 +03:00
|
|
|
_isPageCall = false;
|
2020-06-26 04:01:18 +03:00
|
|
|
|
2020-06-26 02:05:36 +03:00
|
|
|
static from(page: PageChannel): Page {
|
2020-07-02 04:36:09 +03:00
|
|
|
return (page as any)._object;
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static fromNullable(page: PageChannel | null): Page | null {
|
|
|
|
return page ? Page.from(page) : null;
|
|
|
|
}
|
|
|
|
|
2020-07-11 01:11:47 +03:00
|
|
|
constructor(parent: ChannelOwner, guid: string, initializer: PageInitializer) {
|
|
|
|
super(parent, guid, initializer);
|
2020-07-01 23:55:29 +03:00
|
|
|
this.accessibility = new Accessibility(this._channel);
|
|
|
|
this.keyboard = new Keyboard(this._channel);
|
|
|
|
this.mouse = new Mouse(this._channel);
|
2020-07-10 01:33:01 +03:00
|
|
|
this.coverage = new Coverage(this._channel);
|
2020-06-26 02:05:36 +03:00
|
|
|
|
2020-06-26 22:28:27 +03:00
|
|
|
this._mainFrame = Frame.from(initializer.mainFrame);
|
|
|
|
this._mainFrame._page = this;
|
2020-06-26 02:05:36 +03:00
|
|
|
this._frames.add(this._mainFrame);
|
2020-06-26 22:28:27 +03:00
|
|
|
this._viewportSize = initializer.viewportSize;
|
2020-07-10 23:15:39 +03:00
|
|
|
this._closed = initializer.isClosed;
|
2020-06-26 02:05:36 +03:00
|
|
|
|
2020-06-26 21:51:47 +03:00
|
|
|
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)));
|
2020-06-27 07:22:03 +03:00
|
|
|
this._channel.on('crash', () => this._onCrash());
|
2020-06-27 03:24:21 +03:00
|
|
|
this._channel.on('dialog', dialog => this.emit(Events.Page.Dialog, Dialog.from(dialog)));
|
2020-06-27 07:22:03 +03:00
|
|
|
this._channel.on('domcontentloaded', () => this.emit(Events.Page.DOMContentLoaded));
|
2020-06-27 03:24:21 +03:00
|
|
|
this._channel.on('download', download => this.emit(Events.Page.Download, Download.from(download)));
|
2020-06-30 20:55:11 +03:00
|
|
|
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
|
2020-06-26 02:05:36 +03:00
|
|
|
this._channel.on('frameAttached', frame => this._onFrameAttached(Frame.from(frame)));
|
|
|
|
this._channel.on('frameDetached', frame => this._onFrameDetached(Frame.from(frame)));
|
2020-06-26 21:51:47 +03:00
|
|
|
this._channel.on('frameNavigated', ({ frame, url, name }) => this._onFrameNavigated(Frame.from(frame), url, name));
|
2020-06-27 07:22:03 +03:00
|
|
|
this._channel.on('load', () => this.emit(Events.Page.Load));
|
2020-06-26 21:51:47 +03:00
|
|
|
this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error)));
|
2020-06-27 03:24:21 +03:00
|
|
|
this._channel.on('popup', popup => this.emit(Events.Page.Popup, Page.from(popup)));
|
2020-06-26 02:05:36 +03:00
|
|
|
this._channel.on('request', request => this.emit(Events.Page.Request, Request.from(request)));
|
2020-06-26 21:51:47 +03:00
|
|
|
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)));
|
2020-06-26 02:05:36 +03:00
|
|
|
this._channel.on('response', response => this.emit(Events.Page.Response, Response.from(response)));
|
2020-06-26 21:51:47 +03:00
|
|
|
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
|
2020-06-30 20:55:11 +03:00
|
|
|
this._channel.on('worker', worker => this._onWorker(Worker.from(worker)));
|
2020-06-26 21:51:47 +03:00
|
|
|
}
|
|
|
|
|
2020-06-27 07:22:03 +03:00
|
|
|
_setBrowserContext(context: BrowserContext) {
|
|
|
|
this._browserContext = context;
|
|
|
|
this._timeoutSettings = new TimeoutSettings(context._timeoutSettings);
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:51:47 +03:00
|
|
|
private _onRequestFailed(request: Request, failureText: string | null) {
|
|
|
|
request._failureText = failureText;
|
|
|
|
this.emit(Events.Page.RequestFailed, request);
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private _onFrameAttached(frame: Frame) {
|
2020-06-26 22:28:27 +03:00
|
|
|
frame._page = this;
|
2020-06-26 02:05:36 +03:00
|
|
|
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);
|
2020-06-27 03:24:21 +03:00
|
|
|
frame._detached = true;
|
2020-06-26 02:05:36 +03:00
|
|
|
if (frame._parentFrame)
|
|
|
|
frame._parentFrame._childFrames.delete(frame);
|
|
|
|
this.emit(Events.Page.FrameDetached, frame);
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:51:47 +03:00
|
|
|
private _onFrameNavigated(frame: Frame, url: string, name: string) {
|
2020-06-26 02:05:36 +03:00
|
|
|
frame._url = url;
|
2020-06-26 21:51:47 +03:00
|
|
|
frame._name = name;
|
2020-06-26 02:05:36 +03:00
|
|
|
this.emit(Events.Page.FrameNavigated, frame);
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:51:47 +03:00
|
|
|
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) {
|
2020-06-26 22:28:27 +03:00
|
|
|
const func = this._bindings.get(bindingCall._initializer.name);
|
2020-07-04 04:04:08 +03:00
|
|
|
if (func) {
|
2020-06-26 21:51:47 +03:00
|
|
|
bindingCall.call(func);
|
2020-07-04 04:04:08 +03:00
|
|
|
return;
|
|
|
|
}
|
2020-06-26 21:51:47 +03:00
|
|
|
this._browserContext!._onBinding(bindingCall);
|
|
|
|
}
|
|
|
|
|
2020-06-30 20:55:11 +03:00
|
|
|
_onWorker(worker: Worker): void {
|
|
|
|
this._workers.add(worker);
|
|
|
|
worker._page = this;
|
|
|
|
this.emit(Events.Page.Worker, worker);
|
|
|
|
}
|
|
|
|
|
2020-06-26 02:05:36 +03:00
|
|
|
private _onClose() {
|
2020-06-27 07:22:03 +03:00
|
|
|
this._closed = true;
|
2020-06-26 02:05:36 +03:00
|
|
|
this._browserContext!._pages.delete(this);
|
2020-06-27 07:22:03 +03:00
|
|
|
this._rejectPendingOperations(false);
|
2020-06-26 02:05:36 +03:00
|
|
|
this.emit(Events.Page.Close);
|
|
|
|
}
|
|
|
|
|
2020-06-27 07:22:03 +03:00
|
|
|
private _onCrash() {
|
|
|
|
this._rejectPendingOperations(true);
|
|
|
|
this.emit(Events.Page.Crash);
|
|
|
|
}
|
|
|
|
|
|
|
|
private _rejectPendingOperations(isCrash: boolean) {
|
|
|
|
for (const [listener, event] of this._pendingWaitForEvents) {
|
|
|
|
if (event === Events.Page.Close && !isCrash)
|
|
|
|
continue;
|
|
|
|
if (event === Events.Page.Crash && isCrash)
|
|
|
|
continue;
|
|
|
|
listener(new Error(isCrash ? 'Page crashed' : 'Page closed'));
|
|
|
|
}
|
|
|
|
this._pendingWaitForEvents.clear();
|
|
|
|
}
|
|
|
|
|
2020-06-26 02:05:36 +03:00
|
|
|
context(): BrowserContext {
|
|
|
|
return this._browserContext!;
|
|
|
|
}
|
|
|
|
|
|
|
|
async opener(): Promise<Page | null> {
|
|
|
|
return Page.fromNullable(await this._channel.opener());
|
|
|
|
}
|
|
|
|
|
|
|
|
mainFrame(): Frame {
|
2020-06-26 22:28:27 +03:00
|
|
|
return this._mainFrame;
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2020-06-27 07:22:03 +03:00
|
|
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
2020-06-26 02:05:36 +03:00
|
|
|
this._channel.setDefaultTimeoutNoReply({ timeout });
|
|
|
|
}
|
|
|
|
|
2020-06-30 04:58:09 +03:00
|
|
|
private _attributeToPage<T>(func: () => T): T {
|
|
|
|
try {
|
|
|
|
this._isPageCall = true;
|
|
|
|
return func();
|
|
|
|
} finally {
|
|
|
|
this._isPageCall = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 02:05:36 +03:00
|
|
|
async $(selector: string): Promise<ElementHandle<Element> | null> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.$(selector));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<ElementHandle<Element> | null> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.waitForSelector(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
2020-06-30 20:55:11 +03:00
|
|
|
async dispatchEvent(selector: string, type: string, eventInit?: any, options?: types.TimeoutOptions): Promise<void> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.dispatchEvent(selector, type, eventInit, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.evaluateHandle(pageFunction, arg));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.$eval(selector, pageFunction, arg));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.$$eval(selector, pageFunction, arg));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async $$(selector: string): Promise<ElementHandle<Element>[]> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.$$(selector));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise<ElementHandle> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.addScriptTag(options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
|
2020-06-26 22:28:27 +03:00
|
|
|
return await this._mainFrame.addStyleTag(options);
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async exposeFunction(name: string, playwrightFunction: Function) {
|
|
|
|
await this.exposeBinding(name, (options, ...args: any) => playwrightFunction(...args));
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:51:47 +03:00
|
|
|
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);
|
2020-06-26 02:05:36 +03:00
|
|
|
await this._channel.exposeBinding({ name });
|
|
|
|
}
|
|
|
|
|
|
|
|
async setExtraHTTPHeaders(headers: types.Headers) {
|
|
|
|
await this._channel.setExtraHTTPHeaders({ headers });
|
|
|
|
}
|
|
|
|
|
|
|
|
url(): string {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.url());
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async content(): Promise<string> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.content());
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async setContent(html: string, options?: types.NavigateOptions): Promise<void> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.setContent(html, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async goto(url: string, options?: GotoOptions): Promise<Response | null> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.goto(url, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:04:09 +03:00
|
|
|
async reload(options: types.NavigateOptions = {}): Promise<Response | null> {
|
|
|
|
return Response.fromNullable(await this._channel.reload(options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async waitForLoadState(state?: types.LifecycleEvent, options?: types.TimeoutOptions): Promise<void> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.waitForLoadState(state, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async waitForNavigation(options?: types.WaitForNavigationOptions): Promise<Response | null> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.waitForNavigation(options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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> {
|
2020-06-27 07:22:03 +03:00
|
|
|
let reject: () => void;
|
|
|
|
const result = await Promise.race([
|
|
|
|
waitForEvent(this, event, optionsOrPredicate, this._timeoutSettings.timeout(optionsOrPredicate instanceof Function ? {} : optionsOrPredicate)),
|
|
|
|
new Promise((f, r) => { reject = r; this._pendingWaitForEvents.set(reject, event); })
|
|
|
|
]);
|
|
|
|
this._pendingWaitForEvents.delete(reject!);
|
|
|
|
return result;
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:04:09 +03:00
|
|
|
async goBack(options: types.NavigateOptions = {}): Promise<Response | null> {
|
|
|
|
return Response.fromNullable(await this._channel.goBack(options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
2020-07-06 20:04:09 +03:00
|
|
|
async goForward(options: types.NavigateOptions = {}): Promise<Response | null> {
|
|
|
|
return Response.fromNullable(await this._channel.goForward(options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async emulateMedia(options: { media?: types.MediaType, colorScheme?: types.ColorScheme }) {
|
2020-07-06 20:04:09 +03:00
|
|
|
await this._channel.emulateMedia(options);
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async setViewportSize(viewportSize: types.Size) {
|
2020-06-30 02:37:38 +03:00
|
|
|
this._viewportSize = viewportSize;
|
2020-06-26 02:05:36 +03:00
|
|
|
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);
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.evaluate(pageFunction, arg));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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 });
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:04:09 +03:00
|
|
|
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
|
|
|
return Buffer.from(await this._channel.screenshot(options), 'base64');
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async title(): Promise<string> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.title());
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) {
|
2020-07-06 20:04:09 +03:00
|
|
|
await this._channel.close(options);
|
2020-06-27 03:24:21 +03:00
|
|
|
if (this._ownedContext)
|
|
|
|
await this._ownedContext.close();
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
isClosed(): boolean {
|
|
|
|
return this._closed;
|
|
|
|
}
|
|
|
|
|
|
|
|
async click(selector: string, options?: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.click(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async dblclick(selector: string, options?: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.dblclick(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async fill(selector: string, value: string, options?: types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.fill(selector, value, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async focus(selector: string, options?: types.TimeoutOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.focus(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async textContent(selector: string, options?: types.TimeoutOptions): Promise<null|string> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.textContent(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async innerText(selector: string, options?: types.TimeoutOptions): Promise<string> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.innerText(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async innerHTML(selector: string, options?: types.TimeoutOptions): Promise<string> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.innerHTML(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async getAttribute(selector: string, name: string, options?: types.TimeoutOptions): Promise<string | null> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.getAttribute(selector, name, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async hover(selector: string, options?: types.PointerActionOptions & types.PointerActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.hover(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options?: types.NavigatingActionWaitOptions): Promise<string[]> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.selectOption(selector, values, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options?: types.NavigatingActionWaitOptions): Promise<void> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.setInputFiles(selector, files, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async type(selector: string, text: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.type(selector, text, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async press(selector: string, key: string, options?: { delay?: number } & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.press(selector, key, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async check(selector: string, options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.check(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async uncheck(selector: string, options?: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.uncheck(selector, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async waitForTimeout(timeout: number) {
|
2020-06-30 04:58:09 +03:00
|
|
|
await this._mainFrame.waitForTimeout(timeout);
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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>> {
|
2020-06-30 04:58:09 +03:00
|
|
|
return this._attributeToPage(() => this._mainFrame.waitForFunction(pageFunction, arg, options));
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
workers(): Worker[] {
|
2020-06-30 20:55:11 +03:00
|
|
|
return [...this._workers];
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-06 20:04:09 +03:00
|
|
|
async pdf(options: types.PDFOptions = {}): Promise<Buffer> {
|
2020-07-11 01:39:11 +03:00
|
|
|
const transportOptions: PDFOptions = { ...options } as PDFOptions;
|
|
|
|
if (transportOptions.margin)
|
|
|
|
transportOptions.margin = { ...transportOptions.margin };
|
|
|
|
if (typeof options.width === 'number')
|
|
|
|
transportOptions.width = options.width + 'px';
|
|
|
|
if (typeof options.height === 'number')
|
|
|
|
transportOptions.height = options.height + 'px';
|
|
|
|
for (const margin of ['top', 'right', 'bottom', 'left']) {
|
|
|
|
const index = margin as 'top' | 'right' | 'bottom' | 'left';
|
|
|
|
if (options.margin && typeof options.margin[index] === 'number')
|
|
|
|
transportOptions.margin![index] = transportOptions.margin![index] + 'px';
|
|
|
|
}
|
|
|
|
const binary = await this._channel.pdf(transportOptions);
|
2020-06-30 20:55:11 +03:00
|
|
|
return Buffer.from(binary, 'base64');
|
2020-06-26 02:05:36 +03:00
|
|
|
}
|
|
|
|
}
|
2020-06-26 21:51:47 +03:00
|
|
|
|
2020-06-26 22:28:27 +03:00
|
|
|
export class BindingCall extends ChannelOwner<BindingCallChannel, BindingCallInitializer> {
|
2020-06-26 21:51:47 +03:00
|
|
|
static from(channel: BindingCallChannel): BindingCall {
|
2020-07-02 04:36:09 +03:00
|
|
|
return (channel as any)._object;
|
2020-06-26 21:51:47 +03:00
|
|
|
}
|
|
|
|
|
2020-07-11 01:11:47 +03:00
|
|
|
constructor(parent: ChannelOwner, guid: string, initializer: BindingCallInitializer) {
|
|
|
|
super(parent, guid, initializer);
|
2020-06-26 21:51:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async call(func: FunctionWithSource) {
|
|
|
|
try {
|
2020-06-26 22:28:27 +03:00
|
|
|
const frame = Frame.from(this._initializer.frame);
|
|
|
|
const source = {
|
2020-06-27 07:22:03 +03:00
|
|
|
context: frame._page!.context(),
|
2020-06-26 22:28:27 +03:00
|
|
|
page: frame._page!,
|
|
|
|
frame
|
|
|
|
};
|
|
|
|
this._channel.resolve({ result: await func(source, ...this._initializer.args) });
|
2020-06-26 21:51:47 +03:00
|
|
|
} catch (e) {
|
|
|
|
this._channel.reject({ error: serializeError(e) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-27 07:22:03 +03:00
|
|
|
export async function waitForEvent(emitter: EventEmitter, event: string, optionsOrPredicate: types.WaitForEventOptions = {}, defaultTimeout: number): Promise<any> {
|
2020-06-26 21:51:47 +03:00
|
|
|
let predicate: Function | undefined;
|
2020-06-27 07:22:03 +03:00
|
|
|
let timeout = defaultTimeout;
|
|
|
|
if (typeof optionsOrPredicate === 'function') {
|
2020-06-26 21:51:47 +03:00
|
|
|
predicate = optionsOrPredicate;
|
2020-06-27 07:22:03 +03:00
|
|
|
} else if (optionsOrPredicate.predicate) {
|
|
|
|
if (optionsOrPredicate.timeout !== undefined)
|
|
|
|
timeout = optionsOrPredicate.timeout;
|
2020-06-26 21:51:47 +03:00
|
|
|
predicate = optionsOrPredicate.predicate;
|
2020-06-27 07:22:03 +03:00
|
|
|
}
|
2020-06-26 21:51:47 +03:00
|
|
|
let callback: (a: any) => void;
|
|
|
|
const result = new Promise(f => callback = f);
|
|
|
|
const listener = helper.addEventListener(emitter, event, param => {
|
2020-07-02 04:36:09 +03:00
|
|
|
if (predicate && !predicate(param))
|
2020-06-26 21:51:47 +03:00
|
|
|
return;
|
2020-07-02 04:36:09 +03:00
|
|
|
callback(param);
|
2020-06-26 21:51:47 +03:00
|
|
|
helper.removeEventListeners([listener]);
|
|
|
|
});
|
2020-06-27 07:22:03 +03:00
|
|
|
if (timeout === 0)
|
|
|
|
return result;
|
|
|
|
return Promise.race([
|
|
|
|
result,
|
|
|
|
new Promise((f, r) => setTimeout(() => r(new TimeoutError('Timeout while waiting for event')), timeout))
|
|
|
|
]);
|
2020-06-26 21:51:47 +03:00
|
|
|
}
|