81d2600236
This corresponds to Chromium 79.0.3942.0. This roll includes: - Support CSS media feature emulation in CDP https://chromium-review.googlesource.com/c/chromium/src/+/1821608 - Implement timezone ID verification https://chromium-review.googlesource.com/c/chromium/src/+/1822557 - Allow aria-hidden objects into the browser-side ax tree https://chromium-review.googlesource.com/c/chromium/src/+/1760862 - Remove pre-BlinkGenPropertyTrees codepaths (affects screenshot clipping) https://chromium-review.googlesource.com/c/chromium/src/+/1752244 - Terminate some asynchronous waiting commands on cross-process navigation https://chromium-review.googlesource.com/c/chromium/src/+/1766001/21#message-a6a61261b97ffca6ecb180c0a2303b538f7a6476 Per upstream Chromium changes, `page.screenshot` now clips elements to the viewport. This matches the clipping behavior of elements in inner scrollers (i.e., document and overflow scroll clipping now work the same).
811 lines
25 KiB
JavaScript
811 lines
25 KiB
JavaScript
const {helper, debugError, assert} = require('./helper');
|
|
const {Keyboard, Mouse, Touchscreen} = require('./Input');
|
|
const {Dialog} = require('./Dialog');
|
|
const {TimeoutError} = require('./Errors');
|
|
const fs = require('fs');
|
|
const mime = require('mime');
|
|
const EventEmitter = require('events');
|
|
const {createHandle} = require('./JSHandle');
|
|
const {Events} = require('./Events');
|
|
const {Connection} = require('./Connection');
|
|
const {FrameManager, normalizeWaitUntil} = require('./FrameManager');
|
|
const {NetworkManager} = require('./NetworkManager');
|
|
const {TimeoutSettings} = require('./TimeoutSettings');
|
|
const {NavigationWatchdog} = require('./NavigationWatchdog');
|
|
const {Accessibility} = require('./Accessibility');
|
|
|
|
const writeFileAsync = helper.promisify(fs.writeFile);
|
|
|
|
class Page extends EventEmitter {
|
|
/**
|
|
*
|
|
* @param {!Puppeteer.JugglerSession} connection
|
|
* @param {!Puppeteer.Target} target
|
|
* @param {?Puppeteer.Viewport} defaultViewport
|
|
*/
|
|
static async create(session, target, defaultViewport) {
|
|
const page = new Page(session, target);
|
|
await Promise.all([
|
|
session.send('Runtime.enable'),
|
|
session.send('Network.enable'),
|
|
session.send('Page.enable'),
|
|
]);
|
|
|
|
if (defaultViewport)
|
|
await page.setViewport(defaultViewport);
|
|
return page;
|
|
}
|
|
|
|
/**
|
|
* @param {!PageSession} session
|
|
* @param {!Puppeteer.Target} target
|
|
*/
|
|
constructor(session, target) {
|
|
super();
|
|
this._timeoutSettings = new TimeoutSettings();
|
|
this._session = session;
|
|
this._target = target;
|
|
this._keyboard = new Keyboard(session);
|
|
this._mouse = new Mouse(session, this._keyboard);
|
|
this._touchscreen = new Touchscreen(session, this._keyboard, this._mouse);
|
|
this._accessibility = new Accessibility(session);
|
|
this._closed = false;
|
|
/** @type {!Map<string, Function>} */
|
|
this._pageBindings = new Map();
|
|
this._networkManager = new NetworkManager(session);
|
|
this._frameManager = new FrameManager(session, this, this._networkManager, this._timeoutSettings);
|
|
this._networkManager.setFrameManager(this._frameManager);
|
|
this._eventListeners = [
|
|
helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
|
|
helper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)),
|
|
helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
|
|
helper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.Load, () => this.emit(Events.Page.Load)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.DOMContentLoaded, () => this.emit(Events.Page.DOMContentLoaded)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.FrameAttached, frame => this.emit(Events.Page.FrameAttached, frame)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, frame => this.emit(Events.Page.FrameDetached, frame)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigated, frame => this.emit(Events.Page.FrameNavigated, frame)),
|
|
helper.addEventListener(this._networkManager, Events.NetworkManager.Request, request => this.emit(Events.Page.Request, request)),
|
|
helper.addEventListener(this._networkManager, Events.NetworkManager.Response, response => this.emit(Events.Page.Response, response)),
|
|
helper.addEventListener(this._networkManager, Events.NetworkManager.RequestFinished, request => this.emit(Events.Page.RequestFinished, request)),
|
|
helper.addEventListener(this._networkManager, Events.NetworkManager.RequestFailed, request => this.emit(Events.Page.RequestFailed, request)),
|
|
];
|
|
this._viewport = null;
|
|
this._target._isClosedPromise.then(() => {
|
|
this._closed = true;
|
|
this._frameManager.dispose();
|
|
this._networkManager.dispose();
|
|
helper.removeEventListeners(this._eventListeners);
|
|
this.emit(Events.Page.Close);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<string>} urls
|
|
* @return {!Promise<!Array<Network.Cookie>>}
|
|
*/
|
|
async cookies(...urls) {
|
|
const connection = Connection.fromSession(this._session);
|
|
return (await connection.send('Browser.getCookies', {
|
|
browserContextId: this._target._context._browserContextId || undefined,
|
|
urls: urls.length ? urls : [this.url()]
|
|
})).cookies;
|
|
}
|
|
|
|
/**
|
|
* @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
|
|
*/
|
|
async deleteCookie(...cookies) {
|
|
const pageURL = this.url();
|
|
const items = [];
|
|
for (const cookie of cookies) {
|
|
const item = {
|
|
url: cookie.url,
|
|
domain: cookie.domain,
|
|
path: cookie.path,
|
|
name: cookie.name,
|
|
};
|
|
if (!item.url && pageURL.startsWith('http'))
|
|
item.url = pageURL;
|
|
items.push(item);
|
|
}
|
|
|
|
const connection = Connection.fromSession(this._session);
|
|
await connection.send('Browser.deleteCookies', {
|
|
browserContextId: this._target._context._browserContextId || undefined,
|
|
cookies: items,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {Array<Network.CookieParam>} cookies
|
|
*/
|
|
async setCookie(...cookies) {
|
|
const pageURL = this.url();
|
|
const startsWithHTTP = pageURL.startsWith('http');
|
|
const items = cookies.map(cookie => {
|
|
const item = Object.assign({}, cookie);
|
|
if (!item.url && startsWithHTTP)
|
|
item.url = pageURL;
|
|
assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
|
|
assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
|
|
return item;
|
|
});
|
|
await this.deleteCookie(...items);
|
|
if (items.length) {
|
|
const connection = Connection.fromSession(this._session);
|
|
await connection.send('Browser.setCookies', {
|
|
browserContextId: this._target._context._browserContextId || undefined,
|
|
cookies: items
|
|
});
|
|
}
|
|
}
|
|
|
|
async setRequestInterception(enabled) {
|
|
await this._networkManager.setRequestInterception(enabled);
|
|
}
|
|
|
|
async setExtraHTTPHeaders(headers) {
|
|
await this._networkManager.setExtraHTTPHeaders(headers);
|
|
}
|
|
|
|
/**
|
|
* @param {?string} mediaType
|
|
*/
|
|
async emulateMedia(mediaType) {
|
|
assert(mediaType === 'screen' || mediaType === 'print' || mediaType === null, 'Unsupported media type: ' + mediaType);
|
|
await this._session.send('Page.setEmulatedMedia', {media: mediaType || ''});
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {Function} puppeteerFunction
|
|
*/
|
|
async exposeFunction(name, puppeteerFunction) {
|
|
if (this._pageBindings.has(name))
|
|
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
|
this._pageBindings.set(name, puppeteerFunction);
|
|
|
|
const expression = helper.evaluationString(addPageBinding, name);
|
|
await this._session.send('Page.addBinding', {name: name});
|
|
await this._session.send('Page.addScriptToEvaluateOnNewDocument', {script: expression});
|
|
await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
|
|
|
|
function addPageBinding(bindingName) {
|
|
const binding = window[bindingName];
|
|
window[bindingName] = (...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((resolve, reject) => callbacks.set(seq, {resolve, reject}));
|
|
binding(JSON.stringify({name: bindingName, seq, args}));
|
|
return promise;
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Protocol.Runtime.bindingCalledPayload} event
|
|
*/
|
|
async _onBindingCalled(event) {
|
|
const {name, seq, args} = JSON.parse(event.payload);
|
|
let expression = null;
|
|
try {
|
|
const result = await this._pageBindings.get(name)(...args);
|
|
expression = helper.evaluationString(deliverResult, name, seq, result);
|
|
} catch (error) {
|
|
if (error instanceof Error)
|
|
expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
|
|
else
|
|
expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
|
}
|
|
this._session.send('Runtime.evaluate', { expression, executionContextId: event.executionContextId }).catch(debugError);
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {number} seq
|
|
* @param {*} result
|
|
*/
|
|
function deliverResult(name, seq, result) {
|
|
window[name]['callbacks'].get(seq).resolve(result);
|
|
window[name]['callbacks'].delete(seq);
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {number} seq
|
|
* @param {string} message
|
|
* @param {string} stack
|
|
*/
|
|
function deliverError(name, seq, message, stack) {
|
|
const error = new Error(message);
|
|
error.stack = stack;
|
|
window[name]['callbacks'].get(seq).reject(error);
|
|
window[name]['callbacks'].delete(seq);
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {number} seq
|
|
* @param {*} value
|
|
*/
|
|
function deliverErrorValue(name, seq, value) {
|
|
window[name]['callbacks'].get(seq).reject(value);
|
|
window[name]['callbacks'].delete(seq);
|
|
}
|
|
}
|
|
|
|
_sessionClosePromise() {
|
|
if (!this._disconnectPromise)
|
|
this._disconnectPromise = new Promise(fulfill => this._session.once(Events.JugglerSession.Disconnected, () => fulfill(new Error('Target closed'))));
|
|
return this._disconnectPromise;
|
|
}
|
|
|
|
/**
|
|
* @param {(string|Function)} urlOrPredicate
|
|
* @param {!{timeout?: number}=} options
|
|
* @return {!Promise<!Puppeteer.Request>}
|
|
*/
|
|
async waitForRequest(urlOrPredicate, options = {}) {
|
|
const {
|
|
timeout = this._timeoutSettings.timeout(),
|
|
} = options;
|
|
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Request, request => {
|
|
if (helper.isString(urlOrPredicate))
|
|
return (urlOrPredicate === request.url());
|
|
if (typeof urlOrPredicate === 'function')
|
|
return !!(urlOrPredicate(request));
|
|
return false;
|
|
}, timeout, this._sessionClosePromise());
|
|
}
|
|
|
|
/**
|
|
* @param {(string|Function)} urlOrPredicate
|
|
* @param {!{timeout?: number}=} options
|
|
* @return {!Promise<!Puppeteer.Response>}
|
|
*/
|
|
async waitForResponse(urlOrPredicate, options = {}) {
|
|
const {
|
|
timeout = this._timeoutSettings.timeout(),
|
|
} = options;
|
|
return helper.waitForEvent(this._networkManager, Events.NetworkManager.Response, response => {
|
|
if (helper.isString(urlOrPredicate))
|
|
return (urlOrPredicate === response.url());
|
|
if (typeof urlOrPredicate === 'function')
|
|
return !!(urlOrPredicate(response));
|
|
return false;
|
|
}, timeout, this._sessionClosePromise());
|
|
}
|
|
|
|
/**
|
|
* @param {number} timeout
|
|
*/
|
|
setDefaultNavigationTimeout(timeout) {
|
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
|
}
|
|
|
|
/**
|
|
* @param {number} timeout
|
|
*/
|
|
setDefaultTimeout(timeout) {
|
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
|
}
|
|
|
|
/**
|
|
* @param {string} userAgent
|
|
*/
|
|
async setUserAgent(userAgent) {
|
|
await this._session.send('Page.setUserAgent', {userAgent});
|
|
}
|
|
|
|
/**
|
|
* @param {string} userAgent
|
|
*/
|
|
async setJavaScriptEnabled(enabled) {
|
|
await this._session.send('Page.setJavascriptEnabled', {enabled});
|
|
}
|
|
|
|
/**
|
|
* @param {string} userAgent
|
|
*/
|
|
async setCacheEnabled(enabled) {
|
|
await this._session.send('Page.setCacheDisabled', {cacheDisabled: !enabled});
|
|
}
|
|
|
|
/**
|
|
* @param {{viewport: !Puppeteer.Viewport, userAgent: string}} options
|
|
*/
|
|
async emulate(options) {
|
|
await Promise.all([
|
|
this.setViewport(options.viewport),
|
|
this.setUserAgent(options.userAgent),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return {BrowserContext}
|
|
*/
|
|
browserContext() {
|
|
return this._target.browserContext();
|
|
}
|
|
|
|
_onUncaughtError(params) {
|
|
const error = new Error(params.message);
|
|
error.stack = params.stack;
|
|
this.emit(Events.Page.PageError, error);
|
|
}
|
|
|
|
viewport() {
|
|
return this._viewport;
|
|
}
|
|
|
|
/**
|
|
* @param {!Puppeteer.Viewport} viewport
|
|
*/
|
|
async setViewport(viewport) {
|
|
const {
|
|
width,
|
|
height,
|
|
isMobile = false,
|
|
deviceScaleFactor = 1,
|
|
hasTouch = false,
|
|
isLandscape = false,
|
|
} = viewport;
|
|
await this._session.send('Page.setViewport', {
|
|
viewport: { width, height, isMobile, deviceScaleFactor, hasTouch, isLandscape },
|
|
});
|
|
const oldIsMobile = this._viewport ? this._viewport.isMobile : false;
|
|
const oldHasTouch = this._viewport ? this._viewport.hasTouch : false;
|
|
this._viewport = viewport;
|
|
if (oldIsMobile !== isMobile || oldHasTouch !== hasTouch)
|
|
await this.reload();
|
|
}
|
|
|
|
/**
|
|
* @param {function()|string} pageFunction
|
|
* @param {!Array<*>} args
|
|
*/
|
|
async evaluateOnNewDocument(pageFunction, ...args) {
|
|
const script = helper.evaluationString(pageFunction, ...args);
|
|
await this._session.send('Page.addScriptToEvaluateOnNewDocument', { script });
|
|
}
|
|
|
|
browser() {
|
|
return this._target.browser();
|
|
}
|
|
|
|
target() {
|
|
return this._target;
|
|
}
|
|
|
|
url() {
|
|
return this._frameManager.mainFrame().url();
|
|
}
|
|
|
|
frames() {
|
|
return this._frameManager.frames();
|
|
}
|
|
|
|
_onDialogOpened(params) {
|
|
this.emit(Events.Page.Dialog, new Dialog(this._session, params));
|
|
}
|
|
|
|
mainFrame() {
|
|
return this._frameManager.mainFrame();
|
|
}
|
|
|
|
get accessibility() {
|
|
return this._accessibility;
|
|
}
|
|
|
|
get keyboard(){
|
|
return this._keyboard;
|
|
}
|
|
|
|
get mouse(){
|
|
return this._mouse;
|
|
}
|
|
|
|
get touchscreen(){
|
|
return this._touchscreen;
|
|
}
|
|
|
|
/**
|
|
* @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
|
|
*/
|
|
async waitForNavigation(options = {}) {
|
|
return this._frameManager.mainFrame().waitForNavigation(options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
|
|
*/
|
|
async goto(url, options = {}) {
|
|
return this._frameManager.mainFrame().goto(url, options);
|
|
}
|
|
|
|
/**
|
|
* @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
|
|
*/
|
|
async goBack(options = {}) {
|
|
const {
|
|
timeout = this._timeoutSettings.navigationTimeout(),
|
|
waitUntil = ['load'],
|
|
} = options;
|
|
const frame = this._frameManager.mainFrame();
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
const {navigationId, navigationURL} = await this._session.send('Page.goBack', {
|
|
frameId: frame._frameId,
|
|
});
|
|
if (!navigationId)
|
|
return null;
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
let timeoutCallback;
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
|
const error = await Promise.race([
|
|
timeoutPromise,
|
|
watchDog.promise(),
|
|
]);
|
|
watchDog.dispose();
|
|
clearTimeout(timeoutId);
|
|
if (error)
|
|
throw error;
|
|
return watchDog.navigationResponse();
|
|
}
|
|
|
|
/**
|
|
* @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
|
|
*/
|
|
async goForward(options = {}) {
|
|
const {
|
|
timeout = this._timeoutSettings.navigationTimeout(),
|
|
waitUntil = ['load'],
|
|
} = options;
|
|
const frame = this._frameManager.mainFrame();
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
const {navigationId, navigationURL} = await this._session.send('Page.goForward', {
|
|
frameId: frame._frameId,
|
|
});
|
|
if (!navigationId)
|
|
return null;
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
let timeoutCallback;
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
|
const error = await Promise.race([
|
|
timeoutPromise,
|
|
watchDog.promise(),
|
|
]);
|
|
watchDog.dispose();
|
|
clearTimeout(timeoutId);
|
|
if (error)
|
|
throw error;
|
|
return watchDog.navigationResponse();
|
|
}
|
|
|
|
/**
|
|
* @param {!{timeout?: number, waitUntil?: string|!Array<string>}} options
|
|
*/
|
|
async reload(options = {}) {
|
|
const {
|
|
timeout = this._timeoutSettings.navigationTimeout(),
|
|
waitUntil = ['load'],
|
|
} = options;
|
|
const frame = this._frameManager.mainFrame();
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
const {navigationId, navigationURL} = await this._session.send('Page.reload', {
|
|
frameId: frame._frameId,
|
|
});
|
|
if (!navigationId)
|
|
return null;
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
let timeoutCallback;
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
|
const error = await Promise.race([
|
|
timeoutPromise,
|
|
watchDog.promise(),
|
|
]);
|
|
watchDog.dispose();
|
|
clearTimeout(timeoutId);
|
|
if (error)
|
|
throw error;
|
|
return watchDog.navigationResponse();
|
|
}
|
|
|
|
/**
|
|
* @param {{fullPage?: boolean, clip?: {width: number, height: number, x: number, y: number}, encoding?: string, path?: string}} options
|
|
* @return {Promise<string|Buffer>}
|
|
*/
|
|
async screenshot(options = {}) {
|
|
const {data} = await this._session.send('Page.screenshot', {
|
|
mimeType: getScreenshotMimeType(options),
|
|
fullPage: options.fullPage,
|
|
clip: processClip(options.clip),
|
|
});
|
|
const buffer = options.encoding === 'base64' ? data : Buffer.from(data, 'base64');
|
|
if (options.path)
|
|
await writeFileAsync(options.path, buffer);
|
|
return buffer;
|
|
|
|
function processClip(clip) {
|
|
if (!clip)
|
|
return undefined;
|
|
const x = Math.round(clip.x);
|
|
const y = Math.round(clip.y);
|
|
const width = Math.round(clip.width + clip.x - x);
|
|
const height = Math.round(clip.height + clip.y - y);
|
|
return {x, y, width, height};
|
|
}
|
|
}
|
|
|
|
async evaluate(pageFunction, ...args) {
|
|
return await this._frameManager.mainFrame().evaluate(pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {!{content?: string, path?: string, type?: string, url?: string}} options
|
|
* @return {!Promise<!ElementHandle>}
|
|
*/
|
|
async addScriptTag(options) {
|
|
return await this._frameManager.mainFrame().addScriptTag(options);
|
|
}
|
|
|
|
/**
|
|
* @param {!{content?: string, path?: string, url?: string}} options
|
|
* @return {!Promise<!ElementHandle>}
|
|
*/
|
|
async addStyleTag(options) {
|
|
return await this._frameManager.mainFrame().addStyleTag(options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {!{delay?: number, button?: string, clickCount?: number}=} options
|
|
*/
|
|
async click(selector, options = {}) {
|
|
return await this._frameManager.mainFrame().click(selector, options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
tap(selector) {
|
|
return this.mainFrame().tap(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {string} text
|
|
* @param {{delay: (number|undefined)}=} options
|
|
*/
|
|
async type(selector, text, options) {
|
|
return await this._frameManager.mainFrame().type(selector, text, options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
async focus(selector) {
|
|
return await this._frameManager.mainFrame().focus(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
async hover(selector) {
|
|
return await this._frameManager.mainFrame().hover(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {(string|number|Function)} selectorOrFunctionOrTimeout
|
|
* @param {!{polling?: string|number, timeout?: number, visible?: boolean, hidden?: boolean}=} options
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<!JSHandle>}
|
|
*/
|
|
async waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
|
return await this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {Function|string} pageFunction
|
|
* @param {!{polling?: string|number, timeout?: number}=} options
|
|
* @return {!Promise<!JSHandle>}
|
|
*/
|
|
async waitForFunction(pageFunction, options = {}, ...args) {
|
|
return await this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
|
|
* @return {!Promise<!ElementHandle>}
|
|
*/
|
|
async waitForSelector(selector, options = {}) {
|
|
return await this._frameManager.mainFrame().waitForSelector(selector, options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} xpath
|
|
* @param {!{timeout?: number, visible?: boolean, hidden?: boolean}=} options
|
|
* @return {!Promise<!ElementHandle>}
|
|
*/
|
|
async waitForXPath(xpath, options = {}) {
|
|
return await this._frameManager.mainFrame().waitForXPath(xpath, options);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<string>}
|
|
*/
|
|
async title() {
|
|
return await this._frameManager.mainFrame().title();
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @return {!Promise<?ElementHandle>}
|
|
*/
|
|
async $(selector) {
|
|
return await this._frameManager.mainFrame().$(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @return {!Promise<!Array<!ElementHandle>>}
|
|
*/
|
|
async $$(selector) {
|
|
return await this._frameManager.mainFrame().$$(selector);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {Function|String} pageFunction
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<(!Object|undefined)>}
|
|
*/
|
|
async $eval(selector, pageFunction, ...args) {
|
|
return await this._frameManager.mainFrame().$eval(selector, pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {Function|String} pageFunction
|
|
* @param {!Array<*>} args
|
|
* @return {!Promise<(!Object|undefined)>}
|
|
*/
|
|
async $$eval(selector, pageFunction, ...args) {
|
|
return await this._frameManager.mainFrame().$$eval(selector, pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} expression
|
|
* @return {!Promise<!Array<!ElementHandle>>}
|
|
*/
|
|
async $x(expression) {
|
|
return await this._frameManager.mainFrame().$x(expression);
|
|
}
|
|
|
|
async evaluateHandle(pageFunction, ...args) {
|
|
return await this._frameManager.mainFrame().evaluateHandle(pageFunction, ...args);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {!Array<string>} values
|
|
* @return {!Promise<!Array<string>>}
|
|
*/
|
|
async select(selector, ...values) {
|
|
return await this._frameManager.mainFrame().select(selector, ...values);
|
|
}
|
|
|
|
async close(options = {}) {
|
|
const {
|
|
runBeforeUnload = false,
|
|
} = options;
|
|
await this._session.send('Page.close', { runBeforeUnload });
|
|
if (!runBeforeUnload)
|
|
await this._target._isClosedPromise;
|
|
}
|
|
|
|
async content() {
|
|
return await this._frameManager.mainFrame().content();
|
|
}
|
|
|
|
/**
|
|
* @param {string} html
|
|
*/
|
|
async setContent(html) {
|
|
return await this._frameManager.mainFrame().setContent(html);
|
|
}
|
|
|
|
_onConsole({type, args, executionContextId, location}) {
|
|
const context = this._frameManager.executionContextById(executionContextId);
|
|
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(context, arg)), location));
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
isClosed() {
|
|
return this._closed;
|
|
}
|
|
}
|
|
|
|
class ConsoleMessage {
|
|
/**
|
|
* @param {string} type
|
|
* @param {!Array<!JSHandle>} args
|
|
*/
|
|
constructor(type, args, location) {
|
|
this._type = type;
|
|
this._args = args;
|
|
this._location = location;
|
|
}
|
|
|
|
location() {
|
|
return this._location;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
type() {
|
|
return this._type;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!JSHandle>}
|
|
*/
|
|
args() {
|
|
return this._args;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
text() {
|
|
return this._args.map(arg => {
|
|
if (arg._objectId)
|
|
return arg.toString();
|
|
return arg._deserializeValue(arg._protocolValue);
|
|
}).join(' ');
|
|
}
|
|
}
|
|
|
|
function getScreenshotMimeType(options) {
|
|
// options.type takes precedence over inferring the type from options.path
|
|
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
|
|
if (options.type) {
|
|
if (options.type === 'png')
|
|
return 'image/png';
|
|
if (options.type === 'jpeg')
|
|
return 'image/jpeg';
|
|
throw new Error('Unknown options.type value: ' + options.type);
|
|
}
|
|
if (options.path) {
|
|
const fileType = mime.getType(options.path);
|
|
if (fileType === 'image/png' || fileType === 'image/jpeg')
|
|
return fileType;
|
|
throw new Error('Unsupported screenshot mime type: ' + fileType);
|
|
}
|
|
return 'image/png';
|
|
}
|
|
|
|
module.exports = {Page, ConsoleMessage};
|