mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
e2cad568d6
This lets the user pass `...args` into `page.waitFor`. It also clarifies that the docs that `options` is not optional if `...args` are specified. Fixes #770
858 lines
26 KiB
JavaScript
858 lines
26 KiB
JavaScript
/**
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const EventEmitter = require('events');
|
|
const mime = require('mime');
|
|
const NetworkManager = require('./NetworkManager');
|
|
const NavigatorWatcher = require('./NavigatorWatcher');
|
|
const Dialog = require('./Dialog');
|
|
const EmulationManager = require('./EmulationManager');
|
|
const FrameManager = require('./FrameManager');
|
|
const {Keyboard, Mouse, Touchscreen} = require('./Input');
|
|
const Tracing = require('./Tracing');
|
|
const {helper, debugError} = require('./helper');
|
|
|
|
class Page extends EventEmitter {
|
|
/**
|
|
* @param {!Session} client
|
|
* @param {string} sessionId
|
|
* @param {boolean} ignoreHTTPSErrors
|
|
* @param {!TaskQueue} screenshotTaskQueue
|
|
* @return {!Promise<!Page>}
|
|
*/
|
|
static async create(client, ignoreHTTPSErrors, screenshotTaskQueue) {
|
|
await Promise.all([
|
|
client.send('Network.enable', {}),
|
|
client.send('Page.enable', {}),
|
|
client.send('Runtime.enable', {}),
|
|
client.send('Security.enable', {}),
|
|
]);
|
|
if (ignoreHTTPSErrors)
|
|
await client.send('Security.setOverrideCertificateErrors', {override: true});
|
|
const page = new Page(client, ignoreHTTPSErrors, screenshotTaskQueue);
|
|
await page.goto('about:blank');
|
|
// Initialize default page size.
|
|
await page.setViewport({width: 800, height: 600});
|
|
return page;
|
|
}
|
|
|
|
/**
|
|
* @param {!Session} client
|
|
* @param {boolean} ignoreHTTPSErrors
|
|
* @param {!TaskQueue} screenshotTaskQueue
|
|
*/
|
|
constructor(client, ignoreHTTPSErrors, screenshotTaskQueue) {
|
|
super();
|
|
this._client = client;
|
|
this._keyboard = new Keyboard(client);
|
|
this._mouse = new Mouse(client, this._keyboard);
|
|
this._touchscreen = new Touchscreen(client, this._keyboard);
|
|
this._frameManager = new FrameManager(client, this._mouse, this._touchscreen);
|
|
this._networkManager = new NetworkManager(client);
|
|
this._emulationManager = new EmulationManager(client);
|
|
this._tracing = new Tracing(client);
|
|
/** @type {!Map<string, function>} */
|
|
this._pageBindings = new Map();
|
|
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
|
|
|
|
this._screenshotTaskQueue = screenshotTaskQueue;
|
|
|
|
this._frameManager.on(FrameManager.Events.FrameAttached, event => this.emit(Page.Events.FrameAttached, event));
|
|
this._frameManager.on(FrameManager.Events.FrameDetached, event => this.emit(Page.Events.FrameDetached, event));
|
|
this._frameManager.on(FrameManager.Events.FrameNavigated, event => this.emit(Page.Events.FrameNavigated, event));
|
|
|
|
this._networkManager.on(NetworkManager.Events.Request, event => this.emit(Page.Events.Request, event));
|
|
this._networkManager.on(NetworkManager.Events.Response, event => this.emit(Page.Events.Response, event));
|
|
this._networkManager.on(NetworkManager.Events.RequestFailed, event => this.emit(Page.Events.RequestFailed, event));
|
|
this._networkManager.on(NetworkManager.Events.RequestFinished, event => this.emit(Page.Events.RequestFinished, event));
|
|
|
|
client.on('Page.loadEventFired', event => this.emit(Page.Events.Load));
|
|
client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
|
client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
|
client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
|
|
client.on('Security.certificateError', event => this._onCertificateError(event));
|
|
client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
|
}
|
|
|
|
_onTargetCrashed() {
|
|
this.emit('error', new Error('Page crashed!'));
|
|
}
|
|
|
|
/**
|
|
* @return {!Frame}
|
|
*/
|
|
mainFrame() {
|
|
return this._frameManager.mainFrame();
|
|
}
|
|
|
|
/**
|
|
* @return {!Keyboard}
|
|
*/
|
|
get keyboard() {
|
|
return this._keyboard;
|
|
}
|
|
|
|
/**
|
|
* @return {!Touchscreen}
|
|
*/
|
|
get touchscreen() {
|
|
return this._touchscreen;
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
async tap(selector) {
|
|
const handle = await this.$(selector);
|
|
console.assert(handle, 'No node found for selector: ' + selector);
|
|
await handle.tap();
|
|
await handle.dispose();
|
|
}
|
|
|
|
/**
|
|
* @return {!Tracing}
|
|
*/
|
|
get tracing() {
|
|
return this._tracing;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!Frame>}
|
|
*/
|
|
frames() {
|
|
return this._frameManager.frames();
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} value
|
|
*/
|
|
async setRequestInterceptionEnabled(value) {
|
|
return this._networkManager.setRequestInterceptionEnabled(value);
|
|
}
|
|
|
|
/**
|
|
* @param {!Object} event
|
|
*/
|
|
_onCertificateError(event) {
|
|
if (!this._ignoreHTTPSErrors)
|
|
return;
|
|
this._client.send('Security.handleCertificateError', {
|
|
eventId: event.eventId,
|
|
action: 'continue'
|
|
}).catch(debugError);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @return {!Promise<?ElementHandle>}
|
|
*/
|
|
async $(selector) {
|
|
return this.mainFrame().$(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {function()|string} pageFunction
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<(!Object|undefined)>}
|
|
*/
|
|
async $eval(selector, pageFunction, ...args) {
|
|
return this.mainFrame().$eval(selector, pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @return {!Promise<!Array<!ElementHandle>>}
|
|
*/
|
|
async $$(selector) {
|
|
return this.mainFrame().$$(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<string>} urls
|
|
* @return {!Promise<!Array<Network.Cookie>>}
|
|
*/
|
|
async cookies(...urls) {
|
|
return (await this._client.send('Network.getCookies', {
|
|
urls: urls.length ? urls : [this.url()]
|
|
})).cookies;
|
|
}
|
|
|
|
/**
|
|
* @param {Array<Network.CookieParam>} cookies
|
|
*/
|
|
async deleteCookie(...cookies) {
|
|
const pageURL = this.url();
|
|
for (const cookie of cookies) {
|
|
const item = Object.assign({}, cookie);
|
|
if (!cookie.url && pageURL.startsWith('http'))
|
|
item.url = pageURL;
|
|
await this._client.send('Network.deleteCookies', item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Array<Network.CookieParam>} cookies
|
|
*/
|
|
async setCookie(...cookies) {
|
|
const items = cookies.map(cookie => {
|
|
const item = Object.assign({}, cookie);
|
|
const pageURL = this.url();
|
|
if (!item.url && pageURL.startsWith('http'))
|
|
item.url = this.url();
|
|
return item;
|
|
});
|
|
await this.deleteCookie(...items);
|
|
if (items.length)
|
|
await this._client.send('Network.setCookies', { cookies: items });
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
*/
|
|
async addScriptTag(url) {
|
|
return this.mainFrame().addScriptTag(url);
|
|
}
|
|
|
|
/**
|
|
* @param {string} filePath
|
|
*/
|
|
async injectFile(filePath) {
|
|
return this.mainFrame().injectFile(filePath);
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {function(?)} puppeteerFunction
|
|
*/
|
|
async exposeFunction(name, puppeteerFunction) {
|
|
if (this._pageBindings[name])
|
|
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
|
this._pageBindings[name] = puppeteerFunction;
|
|
|
|
const expression = helper.evaluationString(addPageBinding, name);
|
|
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: expression });
|
|
await this._client.send('Runtime.evaluate', { expression, returnByValue: true });
|
|
|
|
function addPageBinding(bindingName) {
|
|
window[bindingName] = async(...args) => {
|
|
const me = window[bindingName];
|
|
let callbacks = me['callbacks'];
|
|
if (!callbacks) {
|
|
callbacks = new Map();
|
|
me['callbacks'] = callbacks;
|
|
}
|
|
const seq = (me['lastSeq'] || 0) + 1;
|
|
me['lastSeq'] = seq;
|
|
const promise = new Promise(fulfill => callbacks.set(seq, fulfill));
|
|
// eslint-disable-next-line no-console
|
|
console.debug('driver:page-binding', JSON.stringify({name: bindingName, seq, args}));
|
|
return promise;
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {?{username: string, password: string}} credentials
|
|
*/
|
|
async authenticate(credentials) {
|
|
return this._networkManager.authenticate(credentials);
|
|
}
|
|
|
|
/**
|
|
* @param {!Object<string, string>} headers
|
|
*/
|
|
async setExtraHTTPHeaders(headers) {
|
|
return this._networkManager.setExtraHTTPHeaders(headers);
|
|
}
|
|
|
|
/**
|
|
* @param {string} userAgent
|
|
*/
|
|
async setUserAgent(userAgent) {
|
|
return this._networkManager.setUserAgent(userAgent);
|
|
}
|
|
|
|
/**
|
|
* @param {!Object} exceptionDetails
|
|
*/
|
|
_handleException(exceptionDetails) {
|
|
const message = helper.getExceptionMessage(exceptionDetails);
|
|
this.emit(Page.Events.PageError, new Error(message));
|
|
}
|
|
|
|
async _onConsoleAPI(event) {
|
|
if (event.type === 'debug' && event.args.length && event.args[0].value === 'driver:page-binding') {
|
|
const {name, seq, args} = JSON.parse(event.args[1].value);
|
|
const result = await this._pageBindings[name](...args);
|
|
const expression = helper.evaluationString(deliverResult, name, seq, result);
|
|
this._client.send('Runtime.evaluate', { expression }).catch(debugError);
|
|
|
|
function deliverResult(name, seq, result) {
|
|
window[name]['callbacks'].get(seq)(result);
|
|
window[name]['callbacks'].delete(seq);
|
|
}
|
|
return;
|
|
}
|
|
if (!this.listenerCount(Page.Events.Console)) {
|
|
event.args.map(arg => helper.releaseObject(this._client, arg));
|
|
return;
|
|
}
|
|
const values = await Promise.all(event.args.map(arg => helper.serializeRemoteObject(this._client, arg)));
|
|
this.emit(Page.Events.Console, ...values);
|
|
}
|
|
|
|
_onDialog(event) {
|
|
let dialogType = null;
|
|
if (event.type === 'alert')
|
|
dialogType = Dialog.Type.Alert;
|
|
else if (event.type === 'confirm')
|
|
dialogType = Dialog.Type.Confirm;
|
|
else if (event.type === 'prompt')
|
|
dialogType = Dialog.Type.Prompt;
|
|
else if (event.type === 'beforeunload')
|
|
dialogType = Dialog.Type.BeforeUnload;
|
|
console.assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
|
|
const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
|
|
this.emit(Page.Events.Dialog, dialog);
|
|
}
|
|
|
|
/**
|
|
* @return {!string}
|
|
*/
|
|
url() {
|
|
return this.mainFrame().url();
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<String>}
|
|
*/
|
|
async content() {
|
|
return await this.evaluate(() => {
|
|
let retVal = '';
|
|
if (document.doctype)
|
|
retVal = new XMLSerializer().serializeToString(document.doctype);
|
|
if (document.documentElement)
|
|
retVal += document.documentElement.outerHTML;
|
|
return retVal;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} html
|
|
*/
|
|
async setContent(html) {
|
|
await this.evaluate(html => {
|
|
document.open();
|
|
document.write(html);
|
|
document.close();
|
|
}, html);
|
|
}
|
|
|
|
/**
|
|
* @param {string} html
|
|
* @param {!Object=} options
|
|
* @return {!Promise<?Response>}
|
|
*/
|
|
async goto(url, options) {
|
|
const watcher = new NavigatorWatcher(this._client, this._ignoreHTTPSErrors, options);
|
|
const responses = new Map();
|
|
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response));
|
|
const navigationPromise = watcher.waitForNavigation();
|
|
|
|
const referrer = this._networkManager.extraHTTPHeaders()['referer'];
|
|
try {
|
|
// Await for the command to throw exception in case of illegal arguments.
|
|
await this._client.send('Page.navigate', {url, referrer});
|
|
} catch (e) {
|
|
watcher.cancel();
|
|
throw e;
|
|
}
|
|
const error = await navigationPromise;
|
|
helper.removeEventListeners([listener]);
|
|
if (error)
|
|
throw error;
|
|
if (this._frameManager.isMainFrameLoadingFailed())
|
|
throw new Error('Failed to navigate: ' + url);
|
|
return responses.get(this.mainFrame().url()) || null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<?Response>}
|
|
*/
|
|
async reload(options) {
|
|
const [response] = await Promise.all([
|
|
this.waitForNavigation(options),
|
|
this._client.send('Page.reload')
|
|
]);
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<!Response>}
|
|
*/
|
|
async waitForNavigation(options) {
|
|
const watcher = new NavigatorWatcher(this._client, this._ignoreHTTPSErrors, options);
|
|
|
|
const responses = new Map();
|
|
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response));
|
|
const error = await watcher.waitForNavigation();
|
|
helper.removeEventListeners([listener]);
|
|
if (error)
|
|
throw error;
|
|
return responses.get(this.mainFrame().url()) || null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<?Response>}
|
|
*/
|
|
async goBack(options) {
|
|
return this._go(-1, options);
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<?Response>}
|
|
*/
|
|
async goForward(options) {
|
|
return this._go(+1, options);
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<?Response>}
|
|
*/
|
|
async _go(delta, options) {
|
|
const history = await this._client.send('Page.getNavigationHistory');
|
|
const entry = history.entries[history.currentIndex + delta];
|
|
if (!entry)
|
|
return null;
|
|
const [response] = await Promise.all([
|
|
this.waitForNavigation(options),
|
|
this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}),
|
|
]);
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @param {!Object} options
|
|
*/
|
|
async emulate(options) {
|
|
return Promise.all([
|
|
this.setViewport(options.viewport),
|
|
this.setUserAgent(options.userAgent)
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} enabled
|
|
*/
|
|
async setJavaScriptEnabled(enabled) {
|
|
await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
|
|
}
|
|
|
|
/**
|
|
* @param {?string} mediaType
|
|
*/
|
|
async emulateMedia(mediaType) {
|
|
console.assert(mediaType === 'screen' || mediaType === 'print' || mediaType === null, 'Unsupported media type: ' + mediaType);
|
|
await this._client.send('Emulation.setEmulatedMedia', {media: mediaType || ''});
|
|
}
|
|
|
|
/**
|
|
* @param {!Page.Viewport} viewport
|
|
*/
|
|
async setViewport(viewport) {
|
|
const needsReload = await this._emulationManager.emulateViewport(this._client, viewport);
|
|
this._viewport = viewport;
|
|
if (needsReload)
|
|
await this.reload();
|
|
}
|
|
|
|
/**
|
|
* @return {!Page.Viewport}
|
|
*/
|
|
viewport() {
|
|
return this._viewport;
|
|
}
|
|
|
|
/**
|
|
* @param {function()} pageFunction
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<(!Object|undefined)>}
|
|
*/
|
|
async evaluate(pageFunction, ...args) {
|
|
return this._frameManager.mainFrame().evaluate(pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {function()|string} pageFunction
|
|
* @param {!Array<*>} args
|
|
*/
|
|
async evaluateOnNewDocument(pageFunction, ...args) {
|
|
const source = helper.evaluationString(pageFunction, ...args);
|
|
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<!Buffer>}
|
|
*/
|
|
async screenshot(options = {}) {
|
|
let screenshotType = null;
|
|
if (options.path) {
|
|
const mimeType = mime.lookup(options.path);
|
|
if (mimeType === 'image/png')
|
|
screenshotType = 'png';
|
|
else if (mimeType === 'image/jpeg')
|
|
screenshotType = 'jpeg';
|
|
console.assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType);
|
|
}
|
|
if (options.type) {
|
|
console.assert(!screenshotType || options.type === screenshotType, `Passed screenshot type '${options.type}' does not match the type inferred from the file path: '${screenshotType}'`);
|
|
console.assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
|
screenshotType = options.type;
|
|
}
|
|
if (!screenshotType)
|
|
screenshotType = 'png';
|
|
|
|
if (options.quality) {
|
|
console.assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots');
|
|
console.assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
|
|
console.assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
|
|
console.assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
|
|
}
|
|
console.assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive');
|
|
if (options.clip) {
|
|
console.assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x));
|
|
console.assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y));
|
|
console.assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width));
|
|
console.assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height));
|
|
}
|
|
return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options));
|
|
}
|
|
|
|
/**
|
|
* @param {string} format
|
|
* @param {!Object=} options
|
|
* @return {!Promise<!Buffer>}
|
|
*/
|
|
async _screenshotTask(format, options) {
|
|
await this._client.send('Target.activateTarget', {targetId: this._client.targetId()});
|
|
let clip = options.clip ? Object.assign({}, options['clip']) : undefined;
|
|
if (clip)
|
|
clip.scale = 1;
|
|
|
|
if (options.fullPage) {
|
|
const metrics = await this._client.send('Page.getLayoutMetrics');
|
|
const width = Math.ceil(metrics.contentSize.width);
|
|
const height = Math.ceil(metrics.contentSize.height);
|
|
|
|
// Overwrite clip for full page at all times.
|
|
clip = { x: 0, y: 0, width, height, scale: 1 };
|
|
const mobile = this._viewport.isMobile || false;
|
|
const deviceScaleFactor = this._viewport.deviceScaleFactor || 1;
|
|
const landscape = this._viewport.isLandscape || false;
|
|
const screenOrientation = landscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
|
|
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile, width, height, deviceScaleFactor, screenOrientation });
|
|
}
|
|
|
|
if (options.omitBackground)
|
|
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } });
|
|
const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
|
|
if (options.omitBackground)
|
|
await this._client.send('Emulation.setDefaultBackgroundColorOverride');
|
|
|
|
if (options.fullPage)
|
|
await this.setViewport(this._viewport);
|
|
|
|
const buffer = new Buffer(result.data, 'base64');
|
|
if (options.path)
|
|
fs.writeFileSync(options.path, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* @param {!Object=} options
|
|
* @return {!Promise<!Buffer>}
|
|
*/
|
|
async pdf(options = {}) {
|
|
const scale = options.scale || 1;
|
|
const displayHeaderFooter = !!options.displayHeaderFooter;
|
|
const printBackground = !!options.printBackground;
|
|
const landscape = !!options.landscape;
|
|
const pageRanges = options.pageRanges || '';
|
|
|
|
let paperWidth = 8.5;
|
|
let paperHeight = 11;
|
|
if (options.format) {
|
|
const format = Page.PaperFormats[options.format.toLowerCase()];
|
|
console.assert(format, 'Unknown paper format: ' + options.format);
|
|
paperWidth = format.width;
|
|
paperHeight = format.height;
|
|
} else {
|
|
paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
|
|
paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
|
|
}
|
|
|
|
const marginOptions = options.margin || {};
|
|
const marginTop = convertPrintParameterToInches(marginOptions.top) || 0;
|
|
const marginLeft = convertPrintParameterToInches(marginOptions.left) || 0;
|
|
const marginBottom = convertPrintParameterToInches(marginOptions.bottom) || 0;
|
|
const marginRight = convertPrintParameterToInches(marginOptions.right) || 0;
|
|
|
|
const result = await this._client.send('Page.printToPDF', {
|
|
landscape: landscape,
|
|
displayHeaderFooter: displayHeaderFooter,
|
|
printBackground: printBackground,
|
|
scale: scale,
|
|
paperWidth: paperWidth,
|
|
paperHeight: paperHeight,
|
|
marginTop: marginTop,
|
|
marginBottom: marginBottom,
|
|
marginLeft: marginLeft,
|
|
marginRight: marginRight,
|
|
pageRanges: pageRanges
|
|
});
|
|
const buffer = new Buffer(result.data, 'base64');
|
|
if (options.path)
|
|
fs.writeFileSync(options.path, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<string>}
|
|
*/
|
|
async plainText() {
|
|
return this.evaluate(() => document.body.innerText);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<string>}
|
|
*/
|
|
async title() {
|
|
return this.mainFrame().title();
|
|
}
|
|
|
|
async close() {
|
|
await this._client.dispose();
|
|
}
|
|
|
|
/**
|
|
* @return {!Mouse}
|
|
*/
|
|
get mouse() {
|
|
return this._mouse;
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {!Object} options
|
|
*/
|
|
async click(selector, options) {
|
|
const handle = await this.$(selector);
|
|
console.assert(handle, 'No node found for selector: ' + selector);
|
|
await handle.click(options);
|
|
await handle.dispose();
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
async hover(selector) {
|
|
const handle = await this.$(selector);
|
|
console.assert(handle, 'No node found for selector: ' + selector);
|
|
await handle.hover();
|
|
await handle.dispose();
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
async focus(selector) {
|
|
const handle = await this.$(selector);
|
|
console.assert(handle, 'No node found for selector: ' + selector);
|
|
await this.evaluate(element => element.focus(), handle);
|
|
await handle.dispose();
|
|
}
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @param {{delay: (number|undefined)}=} options
|
|
*/
|
|
async type(text, options) {
|
|
let delay = 0;
|
|
if (options && options.delay)
|
|
delay = options.delay;
|
|
let last;
|
|
for (const char of text) {
|
|
last = this.press(char, {text: char, delay});
|
|
if (delay)
|
|
await new Promise(f => setTimeout(f, delay));
|
|
}
|
|
await last;
|
|
}
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @param {!Object=} options
|
|
*/
|
|
async press(key, options) {
|
|
this._keyboard.down(key, options);
|
|
if (options && options.delay)
|
|
await new Promise(f => setTimeout(f, options.delay));
|
|
await this._keyboard.up(key);
|
|
}
|
|
|
|
/**
|
|
* @param {(string|number|function())} selectorOrTimeout
|
|
* @param {!Object=} options
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise}
|
|
*/
|
|
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
|
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {!Object=} options
|
|
* @return {!Promise}
|
|
*/
|
|
waitForSelector(selector, options = {}) {
|
|
return this.mainFrame().waitForSelector(selector, options);
|
|
}
|
|
|
|
/**
|
|
* @param {function()} pageFunction
|
|
* @param {!Object=} options
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise}
|
|
*/
|
|
waitForFunction(pageFunction, options = {}, ...args) {
|
|
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
|
}
|
|
}
|
|
|
|
/** @enum {string} */
|
|
Page.PaperFormats = {
|
|
letter: {width: 8.5, height: 11},
|
|
legal: {width: 8.5, height: 14},
|
|
tabloid: {width: 11, height: 17},
|
|
ledger: {width: 17, height: 11},
|
|
a0: {width: 33.1, height: 46.8 },
|
|
a1: {width: 23.4, height: 33.1 },
|
|
a2: {width: 16.5, height: 23.4 },
|
|
a3: {width: 11.7, height: 16.5 },
|
|
a4: {width: 8.27, height: 11.7 },
|
|
a5: {width: 5.83, height: 8.27 },
|
|
};
|
|
|
|
const unitToPixels = {
|
|
'px': 1,
|
|
'in': 96,
|
|
'cm': 37.8,
|
|
'mm': 3.78
|
|
};
|
|
|
|
/**
|
|
* @param {(string|number|undefined)} parameter
|
|
* @return {(number|undefined)}
|
|
*/
|
|
function convertPrintParameterToInches(parameter) {
|
|
if (typeof parameter === 'undefined')
|
|
return undefined;
|
|
let pixels;
|
|
if (helper.isNumber(parameter)) {
|
|
// Treat numbers as pixel values to be aligned with phantom's paperSize.
|
|
pixels = /** @type {number} */ (parameter);
|
|
} else if (helper.isString(parameter)) {
|
|
const text = parameter;
|
|
let unit = text.substring(text.length - 2).toLowerCase();
|
|
let valueText = '';
|
|
if (unitToPixels.hasOwnProperty(unit)) {
|
|
valueText = text.substring(0, text.length - 2);
|
|
} else {
|
|
// In case of unknown unit try to parse the whole parameter as number of pixels.
|
|
// This is consistent with phantom's paperSize behavior.
|
|
unit = 'px';
|
|
valueText = text;
|
|
}
|
|
const value = Number(valueText);
|
|
console.assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
|
|
pixels = value * unitToPixels[unit];
|
|
} else {
|
|
throw new Error('page.pdf() Cannot handle parameter type: ' + (typeof parameter));
|
|
}
|
|
return pixels / 96;
|
|
}
|
|
|
|
Page.Events = {
|
|
Console: 'console',
|
|
Dialog: 'dialog',
|
|
Error: 'error',
|
|
// Can'e use just 'error' due to node.js special treatment of error events.
|
|
// @see https://nodejs.org/api/events.html#events_error_events
|
|
PageError: 'pageerror',
|
|
Request: 'request',
|
|
Response: 'response',
|
|
RequestFailed: 'requestfailed',
|
|
RequestFinished: 'requestfinished',
|
|
FrameAttached: 'frameattached',
|
|
FrameDetached: 'framedetached',
|
|
FrameNavigated: 'framenavigated',
|
|
Load: 'load',
|
|
};
|
|
|
|
/** @typedef {{width: number, height: number, deviceScaleFactor: number|undefined, isMobile: boolean|undefined, isLandscape: boolean, hasTouch: boolean|undefined}} */
|
|
Page.Viewport;
|
|
|
|
/**
|
|
* @typedef {Object} Network.Cookie
|
|
* @property {string} name
|
|
* @property {string} value
|
|
* @property {string} domain
|
|
* @property {string} path
|
|
* @property {number} expires
|
|
* @property {number} size
|
|
* @property {boolean} httpOnly
|
|
* @property {boolean} secure
|
|
* @property {boolean} session
|
|
* @property {("Strict"|"Lax")=} sameSite
|
|
*/
|
|
|
|
|
|
/**
|
|
* @typedef {Object} Network.CookieParam
|
|
* @property {string} name
|
|
* @property {string=} value
|
|
* @property {string=} url
|
|
* @property {string=} domain
|
|
* @property {string=} path
|
|
* @property {number=} expires
|
|
* @property {boolean=} httpOnly
|
|
* @property {boolean=} secure
|
|
* @property {("Strict"|"Lax")=} sameSite
|
|
*/
|
|
|
|
|
|
module.exports = Page;
|
|
helper.tracePublicAPI(Page);
|