simple-headless-chrome
Version:
Headless Chrome abstraction to simplify the interaction with the browser. It may be used for crawling sites, test automation, etc
1,553 lines (1,287 loc) • 86.1 kB
JavaScript
/* global XMLHttpRequest */
'use strict';
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var debug = require('debug')('HeadlessChrome:actions');
var Promise = require('bluebird');
var fs = require('mz/fs');
var _ = require('lodash');
var _require = require('./util'),
browserIsInitialized = _require.browserIsInitialized,
sleep = _require.sleep,
fixSelector = _require.fixSelector,
interleaveArrayToObject = _require.interleaveArrayToObject,
promiseTimeout = _require.promiseTimeout,
objectToEncodedUri = _require.objectToEncodedUri;
/**
* Injects JavaScript in the page
*
* Modules available: jQuery, jquery, jQuery.slim and jquery.slim
* @example inject('jquery')
*
* You can use jsdelivr to inject any npm or github package in the page
* @example inject('https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js')
* @example inject('https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js')
*
* You can inject a local Javascript file
* @example inject('./custom-file.js')
* @example inject(__dirname + '/path/to/file.js')
*
* Note: the path will be resolved with `require.resolve()` so you can include
* files that are in `node_modules` simply by installing them with NPM
* @example inject('jquery/dist/jquery.min')
* @example inject('lodash/dist/lodash.min')
*
* @param {string} moduleOrScript - Javascript code, file, url or name of the
* module to inject.
*/
exports.inject = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(moduleOrScript) {
var file, buff;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
debug(`:: inject => Inject "${moduleOrScript}" on the root document...`);
browserIsInitialized.call(this);
if (!(moduleOrScript.slice(0, 8) === 'https://' || moduleOrScript.slice(0, 7) === 'http://')) {
_context.next = 6;
break;
}
return _context.abrupt('return', this.injectRemoteScript(moduleOrScript));
case 6:
_context.t0 = moduleOrScript;
_context.next = _context.t0 === 'jquery' ? 9 : _context.t0 === 'jQuery' ? 9 : _context.t0 === 'jquery.slim' ? 10 : _context.t0 === 'jQuery.slim' ? 10 : 11;
break;
case 9:
return _context.abrupt('return', this.evaluateAsync(function () {
return new Promise(function (resolve, reject) {
if (typeof window.jQuery === 'undefined') {
var script = document.createElement('script');
script.src = 'https://code.jquery.com/jquery-3.2.1.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
} else {
resolve();
}
});
}));
case 10:
return _context.abrupt('return', this.evaluateAsync(function () {
return new Promise(function (resolve, reject) {
if (typeof window.jQuery === 'undefined') {
var script = document.createElement('script');
script.src = 'https://code.jquery.com/jquery-3.2.1.slim.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
} else {
resolve();
}
});
}));
case 11:
file = require.resolve(moduleOrScript);
_context.next = 14;
return fs.readFile(file);
case 14:
buff = _context.sent;
return _context.abrupt('return', this.injectScript(buff.toString()));
case 16:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function inject(_x) {
return _ref.apply(this, arguments);
}
return inject;
}();
/**
* Injects a remote script in the page
* @example injectRemoteScript(https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js)
* @param {string} src - Url to remote JavaScript file
*/
exports.injectRemoteScript = function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(src) {
var result;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
debug(`:: injectRemoteScript => Inject "${src}" on the root document...`);
browserIsInitialized.call(this);
_context2.next = 4;
return this.evaluateAsync(function (src) {
return new Promise(function (resolve, reject) {
var script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}, src);
case 4:
result = _context2.sent;
debug(`:: injectRemoteScript => script "${src}" was injected successfully!`);
return _context2.abrupt('return', result);
case 7:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function injectRemoteScript(_x2) {
return _ref2.apply(this, arguments);
}
return injectRemoteScript;
}();
/**
* Injects code in the DOM as script tag
* @param {string} script - Code to be injected and evaluated in the DOM
*/
exports.injectScript = function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(script) {
var result;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
debug(`:: injectScript => Injecting "${script.slice(0, 10)}..." on the root document...`);
browserIsInitialized.call(this);
_context3.next = 4;
return this.evaluate(function (text) {
var script = document.createElement('script');
script.text = text;
document.head.appendChild(script);
}, script);
case 4:
result = _context3.sent;
debug(`:: injectScript => script "${script.slice(0, 10)}..." was injected successfully!`);
return _context3.abrupt('return', result);
case 7:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
function injectScript(_x3) {
return _ref3.apply(this, arguments);
}
return injectScript;
}();
/**
* Evaluates a fn in the context of the browser
* @param fn {function} - The function to evaluate in the browser
* @param args {*} - The arguments to pass to the function
*/
exports.evaluate = function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(fn) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var exp, result;
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
debug.apply(undefined, [`:: evaluate => ${fn}`].concat(_toConsumableArray(args)));
browserIsInitialized.call(this);
exp = args && args.length > 0 ? `(${String(fn)}).apply(null, ${JSON.stringify(args)})` : `(${String(fn)}).apply(null)`;
_context4.next = 5;
return this.client.Runtime.evaluate({
expression: exp,
returnByValue: true
});
case 5:
result = _context4.sent;
debug(`:: evaluate => Function ${fn} evaluated successfully!`, result);
return _context4.abrupt('return', result);
case 8:
case 'end':
return _context4.stop();
}
}
}, _callee4, this);
}));
function evaluate(_x4) {
return _ref4.apply(this, arguments);
}
return evaluate;
}();
/**
* Evaluates an async fn in the context of the browser
* @param fn {function} - The function to evaluate in the browser
* @param args {*} - The arguments to pass to the function
*/
exports.evaluateAsync = function () {
var _ref5 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(fn) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
var exp;
return regeneratorRuntime.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
debug.apply(undefined, [`:: evaluate => ${fn}`].concat(_toConsumableArray(args)));
browserIsInitialized.call(this);
exp = args && args.length > 0 ? `(${String(fn)}).apply(null, ${JSON.stringify(args)})` : `(${String(fn)}).apply(null)`;
return _context5.abrupt('return', this.client.Runtime.evaluate({
expression: exp,
returnByValue: true,
awaitPromise: true
}));
case 4:
case 'end':
return _context5.stop();
}
}
}, _callee5, this);
}));
function evaluate(_x5) {
return _ref5.apply(this, arguments);
}
return evaluate;
}();
/**
* Evaluates a fn in the context of a passed node
* @param {NodeObject} node - The Node Object used to get the context
* @param fn {function} - The function to evaluate in the browser
* @param args {*} - The arguments to pass to the function
*/
exports.evaluateOnNode = function () {
var _ref6 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(node, fn) {
for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
args[_key3 - 2] = arguments[_key3];
}
var exp, resolvedNode;
return regeneratorRuntime.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
debug.apply(undefined, [`:: evaluateOnNode => Evaluate on node "${node.nodeId}", fn: ${fn}`].concat(_toConsumableArray(args)));
browserIsInitialized.call(this);
exp = args && args.length > 0 ? `function() {(${String(fn)}).apply(null, ${JSON.stringify(args)})}` : `function() {(${String(fn)}).apply(null)}`;
_context6.next = 5;
return this.client.DOM.resolveNode(node);
case 5:
resolvedNode = _context6.sent;
return _context6.abrupt('return', this.client.Runtime.callFunctionOn({
objectId: resolvedNode.object.objectId,
functionDeclaration: exp
}));
case 7:
case 'end':
return _context6.stop();
}
}
}, _callee6, this);
}));
function evaluateOnNode(_x6, _x7) {
return _ref6.apply(this, arguments);
}
return evaluateOnNode;
}();
/**
* Navigates to a URL
* @param {string} url - The URL to navigate to
* @param {object} options - The options object.
* options:
* @property {number} timeout - Time in ms that this method has to wait until the
* "pageLoaded" event is triggered. If the value is 0 or false, it means that it doesn't
* have to wait after calling the "Page.navigate" method
*/
exports.goTo = function () {
var _ref7 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee7(url) {
var _this = this;
var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var options;
return regeneratorRuntime.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
debug(`:: goTo => URL "${url}" | Options: ${JSON.stringify(opt, null, 2)}`);
browserIsInitialized.call(this);
options = Object.assign({
timeout: this.options.browser.loadPageTimeout
}, opt);
if (options.bypassCertificate) {
this.client.Security.certificateError(function (_ref8) {
var eventId = _ref8.eventId;
_this.client.Security.handleCertificateError({
eventId,
action: 'continue'
});
});
this.client.Security.setOverrideCertificateErrors({ override: true });
}
_context7.next = 6;
return this.client.Page.navigate({ url });
case 6:
if (!(options.timeout && typeof options.timeout)) {
_context7.next = 9;
break;
}
_context7.next = 9;
return this.waitForPageToLoad(options.timeout);
case 9:
debug(`:: goTo => URL "${url}" navigated!`);
case 10:
case 'end':
return _context7.stop();
}
}
}, _callee7, this);
}));
return function (_x9) {
return _ref7.apply(this, arguments);
};
}();
/**
* Get the value of an Node.
* @param {NodeObject} node - The Node Object
* @return {object} - Object containing type and value of the element
*/
exports.getNodeValue = function () {
var _ref9 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(node) {
var nodeAttrs, value;
return regeneratorRuntime.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
debug(`:: getNodeValue => Get value from Node "${node.nodeId}"`);
browserIsInitialized.call(this);
_context8.t0 = interleaveArrayToObject;
_context8.next = 5;
return this.client.DOM.getAttributes(node);
case 5:
_context8.t1 = _context8.sent.attributes;
nodeAttrs = (0, _context8.t0)(_context8.t1);
value = nodeAttrs.value;
debug(`:: getNodeValue => Value from Node "${node.nodeId}":`, value);
return _context8.abrupt('return', value);
case 10:
case 'end':
return _context8.stop();
}
}
}, _callee8, this);
}));
return function (_x10) {
return _ref9.apply(this, arguments);
};
}();
/**
* Get the value of an element.
* @param {string} selector - The target selector
* @param {string} frameId - The FrameID where the selector should be searched
* @return {object} - Object containing type and value of the element
*/
exports.getValue = function () {
var _ref10 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee9(selector, frameId) {
var node, htmlObjectType, value, textareaEvaluate;
return regeneratorRuntime.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
debug(`:: getValue => Get value from element matching "${selector}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context9.next = 5;
return this.querySelector(selector, frameId);
case 5:
node = _context9.sent;
_context9.next = 8;
return this.client.DOM.resolveNode(node);
case 8:
htmlObjectType = _context9.sent.object.className;
value = void 0;
// If the node object type is HTMLTextAreaElement, then get the value from the console.
/**
* TODO: Take the value from the DOM Node. For some reason, there're some pages where is not possible
* to get the textarea value, as its nodeId refreshes all the time
*/
if (!(htmlObjectType === 'HTMLTextAreaElement')) {
_context9.next = 18;
break;
}
// Escape selector for onConsole use
selector = selector.replace(/\\/g, '\\');
_context9.next = 14;
return this.evaluate(function (selector) {
return document.querySelector(selector).value;
}, selector);
case 14:
textareaEvaluate = _context9.sent;
value = textareaEvaluate.result.value;
_context9.next = 21;
break;
case 18:
_context9.next = 20;
return this.getNodeValue(node);
case 20:
value = _context9.sent;
case 21:
debug(`:: getValue => Value from element matching "${selector}":`, value);
return _context9.abrupt('return', value);
case 23:
case 'end':
return _context9.stop();
}
}
}, _callee9, this);
}));
return function (_x11, _x12) {
return _ref10.apply(this, arguments);
};
}();
/**
* Set the value of an element.
* @param {NodeObject} node - The Node Object
* @param {string} value - The value to set the node to (it may be an array of values when the node is a multiple "HTMLSelectElement")
*/
exports.setNodeValue = function () {
var _ref11 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee10(node, value) {
var htmlObjectType, selectOptions, selectedValuesArray, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, option, optionValue;
return regeneratorRuntime.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
debug(`:: setNodeValue => Set node value "${value}" to node "${node.nodeId}"`);
browserIsInitialized.call(this);
_context10.next = 4;
return this.client.DOM.resolveNode(node);
case 4:
htmlObjectType = _context10.sent.object.className;
if (!(htmlObjectType === 'HTMLSelectElement')) {
_context10.next = 47;
break;
}
_context10.next = 8;
return this.client.DOM.querySelectorAll({
nodeId: node.nodeId,
selector: 'option'
});
case 8:
selectOptions = _context10.sent;
// Ensure the selectedValuesArray is an array
selectedValuesArray = value;
if (!Array.isArray(value)) {
selectedValuesArray = [value];
}
// Iterate over all the options of the select
_iteratorNormalCompletion = true;
_didIteratorError = false;
_iteratorError = undefined;
_context10.prev = 14;
_iterator = selectOptions.nodeIds[Symbol.iterator]();
case 16:
if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
_context10.next = 31;
break;
}
option = _step.value;
_context10.next = 20;
return this.getNodeValue({ nodeId: option });
case 20:
optionValue = _context10.sent;
if (!(selectedValuesArray.indexOf(optionValue) > -1)) {
_context10.next = 26;
break;
}
_context10.next = 24;
return this.client.DOM.setAttributeValue({
nodeId: option,
name: 'selected',
value: 'true'
});
case 24:
_context10.next = 28;
break;
case 26:
_context10.next = 28;
return this.client.DOM.removeAttribute({
nodeId: option,
name: 'selected'
});
case 28:
_iteratorNormalCompletion = true;
_context10.next = 16;
break;
case 31:
_context10.next = 37;
break;
case 33:
_context10.prev = 33;
_context10.t0 = _context10['catch'](14);
_didIteratorError = true;
_iteratorError = _context10.t0;
case 37:
_context10.prev = 37;
_context10.prev = 38;
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
case 40:
_context10.prev = 40;
if (!_didIteratorError) {
_context10.next = 43;
break;
}
throw _iteratorError;
case 43:
return _context10.finish(40);
case 44:
return _context10.finish(37);
case 45:
_context10.next = 49;
break;
case 47:
_context10.next = 49;
return this.client.DOM.setAttributeValue({
nodeId: node.nodeId,
name: 'value',
value: value
});
case 49:
case 'end':
return _context10.stop();
}
}
}, _callee10, this, [[14, 33, 37, 45], [38,, 40, 44]]);
}));
return function (_x13, _x14) {
return _ref11.apply(this, arguments);
};
}();
/**
* Set the value of an element.
* @param {string} selector - The selector to set the value of.
* @param {string} [value] - The value to set the selector to
* @param {string} frameId - The FrameID where the selector should be searched
*/
exports.setValue = function () {
var _ref12 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee11(selector, value, frameId) {
var node, htmlObjectType;
return regeneratorRuntime.wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
debug(`:: setValue => Set value "${value}" to element matching selector "${selector}" in frame "${frameId || 'root'}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context11.next = 5;
return this.querySelector(selector, frameId);
case 5:
node = _context11.sent;
_context11.next = 8;
return this.client.DOM.resolveNode(node);
case 8:
htmlObjectType = _context11.sent.object.className;
if (!(htmlObjectType === 'HTMLTextAreaElement')) {
_context11.next = 15;
break;
}
// Escape selector for onConsole use
selector = selector.replace(/\\/g, '\\');
_context11.next = 13;
return this.evaluate(function (selector, value) {
document.querySelector(selector).value = value;
}, selector, value);
case 13:
_context11.next = 17;
break;
case 15:
_context11.next = 17;
return this.setNodeValue(node, value);
case 17:
case 'end':
return _context11.stop();
}
}
}, _callee11, this);
}));
return function (_x15, _x16, _x17) {
return _ref12.apply(this, arguments);
};
}();
/**
* Fills a selector of an input or textarea element with the passed value
* @param {string} selector - The selector
* @param {string} value - The value to fill the element matched in the selector
* @param {string} frameId - The FrameID where the selector should be searched
*/
exports.fill = function () {
var _ref13 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(selector, value, frameId) {
var node, nodeAttrs, htmlObjectType, type, supportedTypes, isValidInput, isTextArea;
return regeneratorRuntime.wrap(function _callee12$(_context12) {
while (1) {
switch (_context12.prev = _context12.next) {
case 0:
debug(`:: fill => Fill selector "${selector}" with value "${value}" in frame "${frameId || 'root'}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context12.next = 5;
return this.querySelector(selector, frameId);
case 5:
node = _context12.sent;
_context12.t0 = interleaveArrayToObject;
_context12.next = 9;
return this.client.DOM.getAttributes(node);
case 9:
_context12.t1 = _context12.sent.attributes;
nodeAttrs = (0, _context12.t0)(_context12.t1);
_context12.next = 13;
return this.client.DOM.resolveNode(node);
case 13:
htmlObjectType = _context12.sent.object.className;
type = nodeAttrs.type;
supportedTypes = ['color', 'date', 'datetime', 'datetime-local', 'email', 'hidden', 'month', 'number', 'password', 'range', 'search', 'tel', 'text', 'time', 'url', 'week'];
// Check https://developer.mozilla.org/en-US/docs/Web/API for more info about HTML Objects
isValidInput = htmlObjectType === 'HTMLInputElement' && (typeof type === 'undefined' || supportedTypes.indexOf(type) !== -1);
isTextArea = htmlObjectType === 'HTMLTextAreaElement';
if (!(isTextArea || isValidInput)) {
_context12.next = 23;
break;
}
_context12.next = 21;
return this.setNodeValue(node, value);
case 21:
_context12.next = 24;
break;
case 23:
throw new Error(`DOM element ${selector} can not be filled`);
case 24:
case 'end':
return _context12.stop();
}
}
}, _callee12, this);
}));
return function (_x18, _x19, _x20) {
return _ref13.apply(this, arguments);
};
}();
/**
* Clear an input field.
* @param {string} selector - The selector to clear.
* @param {string} frameId - The FrameID where the selector should be searched
*/
exports.clear = function () {
var _ref14 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(selector, frameId) {
var node;
return regeneratorRuntime.wrap(function _callee13$(_context13) {
while (1) {
switch (_context13.prev = _context13.next) {
case 0:
debug(`:: clear => Clear the field of the selector "${selector}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context13.next = 5;
return this.querySelector(selector, frameId);
case 5:
node = _context13.sent;
_context13.next = 8;
return this.setNodeValue(node, '');
case 8:
case 'end':
return _context13.stop();
}
}
}, _callee13, this);
}));
return function (_x21, _x22) {
return _ref14.apply(this, arguments);
};
}();
/**
* Returns the node associated to the passed selector
* @param {string} selector - The selector to find
* @param {string} frameId - The FrameID where the selector should be searched
* @return {NodeId Object} - NodeId Object (+info: https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-NodeId)
*/
exports.querySelector = function () {
var _ref15 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee14(selector, frameId) {
var DOM, _document, node;
return regeneratorRuntime.wrap(function _callee14$(_context14) {
while (1) {
switch (_context14.prev = _context14.next) {
case 0:
debug(`:: querySelector => Get element from selector "${selector}" in frame "${frameId || 'root'}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
DOM = this.client.DOM;
_context14.prev = 4;
if (!frameId) {
_context14.next = 11;
break;
}
_context14.next = 8;
return DOM.getFlattenedDocument();
case 8:
_context14.t0 = _context14.sent;
_context14.next = 14;
break;
case 11:
_context14.next = 13;
return DOM.getDocument();
case 13:
_context14.t0 = _context14.sent;
case 14:
_document = _context14.t0;
node = frameId ? _.find(_document.nodes, { frameId: frameId }) : _document.root;
// If the node is an iFrame, the #document node is in the contentDocument attribute of the node
if (node.nodeName === 'IFRAME' && node.contentDocument) {
node = node.contentDocument;
}
_context14.next = 19;
return DOM.querySelector({
nodeId: node.nodeId,
selector
});
case 19:
return _context14.abrupt('return', _context14.sent);
case 22:
_context14.prev = 22;
_context14.t1 = _context14['catch'](4);
_context14.t1.name = `Can not query selector ${selector} in frame "${frameId || 'root'}": ${_context14.t1.name}`;
throw _context14.t1;
case 26:
case 'end':
return _context14.stop();
}
}
}, _callee14, this, [[4, 22]]);
}));
return function (_x23, _x24) {
return _ref15.apply(this, arguments);
};
}();
/**
* Focus on an element matching the selector
* @param {string} selector - The selector to find the element
* @param {string} frameId - The FrameID where the selector should be searched
*/
exports.focus = function () {
var _ref16 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee15(selector, frameId) {
var node;
return regeneratorRuntime.wrap(function _callee15$(_context15) {
while (1) {
switch (_context15.prev = _context15.next) {
case 0:
debug(`:: focus => Focusing on selector "${selector}" in frame "${frameId || 'root'}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context15.prev = 3;
_context15.next = 6;
return this.querySelector(selector, frameId);
case 6:
node = _context15.sent;
_context15.next = 9;
return this.client.DOM.focus({ nodeId: node.nodeId });
case 9:
_context15.next = 14;
break;
case 11:
_context15.prev = 11;
_context15.t0 = _context15['catch'](3);
throw new Error(`Can not focus to ${selector}. Error: ${_context15.t0}`);
case 14:
case 'end':
return _context15.stop();
}
}
}, _callee15, this, [[3, 11]]);
}));
return function (_x25, _x26) {
return _ref16.apply(this, arguments);
};
}();
/**
* Simulate a keypress on a selector
* @param {string} selector - The selector to type into.
* @param {string} text - The text to type.
* @param {string} frameId - The FrameID where the selector should be searched
* @param {object} options - Lets you send keys like control & shift
*/
exports.type = function () {
var _ref17 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee16(selector, text, frameId, opts) {
var options, computeModifier, modifiers, isSpecialChar, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, key;
return regeneratorRuntime.wrap(function _callee16$(_context16) {
while (1) {
switch (_context16.prev = _context16.next) {
case 0:
isSpecialChar = function isSpecialChar(key) {
if (key === '\r') {
return true;
}
};
computeModifier = function computeModifier(modifierString) {
var modifiers = {
'ctrl': 0x04000000,
'shift': 0x02000000,
'alt': 0x08000000,
'meta': 0x10000000,
'keypad': 0x20000000
};
var modifier = 0;
var checkKey = function checkKey(key) {
if (key in modifiers) {
return;
}
debug(key + 'is not a supported key modifier');
};
if (!modifierString) {
return modifier;
}
var keys = modifierString.split('+');
keys.forEach(checkKey);
return keys.reduce(function (acc, key) {
return acc | modifiers[key];
}, modifier);
};
options = Object.assign({
reset: false, // Clear the field first
eventType: 'char' // Enum: [ 'keyDown', 'keyUp', 'rawKeyDown', 'char' ]
}, opts);
debug(`:: type => Type text "${text}" in selector "${selector}" | Options: ${JSON.stringify(options, null, 2)}`);
selector = fixSelector(selector);
modifiers = computeModifier(options && options.modifiers);
// Clear the field in the selector, if needed
if (!options.reset) {
_context16.next = 9;
break;
}
_context16.next = 9;
return this.clear(selector, frameId);
case 9:
_context16.next = 11;
return this.focus(selector, frameId);
case 11:
// Type on the selector
_iteratorNormalCompletion2 = true;
_didIteratorError2 = false;
_iteratorError2 = undefined;
_context16.prev = 14;
_iterator2 = text.split('')[Symbol.iterator]();
case 16:
if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) {
_context16.next = 32;
break;
}
key = _step2.value;
if (!isSpecialChar(key)) {
_context16.next = 27;
break;
}
_context16.next = 21;
return this.keyboardEvent('rawKeyDown', '', modifiers, 13);
case 21:
_context16.next = 23;
return this.keyboardEvent(options.eventType, '\r', modifiers);
case 23:
_context16.next = 25;
return this.keyboardEvent('keyUp', '', modifiers, 13);
case 25:
_context16.next = 29;
break;
case 27:
_context16.next = 29;
return this.keyboardEvent(options.eventType, key, modifiers);
case 29:
_iteratorNormalCompletion2 = true;
_context16.next = 16;
break;
case 32:
_context16.next = 38;
break;
case 34:
_context16.prev = 34;
_context16.t0 = _context16['catch'](14);
_didIteratorError2 = true;
_iteratorError2 = _context16.t0;
case 38:
_context16.prev = 38;
_context16.prev = 39;
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
case 41:
_context16.prev = 41;
if (!_didIteratorError2) {
_context16.next = 44;
break;
}
throw _iteratorError2;
case 44:
return _context16.finish(41);
case 45:
return _context16.finish(38);
case 46:
case 'end':
return _context16.stop();
}
}
}, _callee16, this, [[14, 34, 38, 46], [39,, 41, 45]]);
}));
return function (_x27, _x28, _x29, _x30) {
return _ref17.apply(this, arguments);
};
}();
/**
* Types text (doesn't matter where it is)
* @param {string} text - The text to type.
* @param {object} options - Lets you send keys like control & shift
*/
exports.typeText = function () {
var _ref18 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee17(text, opts) {
var options, computeModifier, modifiers, isSpecialChar, _iteratorNormalCompletion3, _didIteratorError3, _iteratorError3, _iterator3, _step3, key;
return regeneratorRuntime.wrap(function _callee17$(_context17) {
while (1) {
switch (_context17.prev = _context17.next) {
case 0:
isSpecialChar = function isSpecialChar(key) {
if (key === '\r') {
return true;
}
};
computeModifier = function computeModifier(modifierString) {
var modifiers = {
'ctrl': 0x04000000,
'shift': 0x02000000,
'alt': 0x08000000,
'meta': 0x10000000,
'keypad': 0x20000000
};
var modifier = 0;
var checkKey = function checkKey(key) {
if (key in modifiers) {
return;
}
debug(key + 'is not a supported key modifier');
};
if (!modifierString) {
return modifier;
}
var keys = modifierString.split('+');
keys.forEach(checkKey);
return keys.reduce(function (acc, key) {
return acc | modifiers[key];
}, modifier);
};
options = Object.assign({
reset: false, // Clear the field first
eventType: 'char' // Enum: [ 'keyDown', 'keyUp', 'rawKeyDown', 'char' ]
}, opts);
debug(`:: typeText => Type text "${text}" | Options: ${JSON.stringify(options, null, 2)}`);
modifiers = computeModifier(options && options.modifiers);
// Type on the selector
_iteratorNormalCompletion3 = true;
_didIteratorError3 = false;
_iteratorError3 = undefined;
_context17.prev = 8;
_iterator3 = text.split('')[Symbol.iterator]();
case 10:
if (_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done) {
_context17.next = 26;
break;
}
key = _step3.value;
if (!isSpecialChar(key)) {
_context17.next = 21;
break;
}
_context17.next = 15;
return this.keyboardEvent('rawKeyDown', '', modifiers, 13);
case 15:
_context17.next = 17;
return this.keyboardEvent(options.eventType, '\r', modifiers);
case 17:
_context17.next = 19;
return this.keyboardEvent('keyUp', '', modifiers, 13);
case 19:
_context17.next = 23;
break;
case 21:
_context17.next = 23;
return this.keyboardEvent(options.eventType, key, modifiers);
case 23:
_iteratorNormalCompletion3 = true;
_context17.next = 10;
break;
case 26:
_context17.next = 32;
break;
case 28:
_context17.prev = 28;
_context17.t0 = _context17['catch'](8);
_didIteratorError3 = true;
_iteratorError3 = _context17.t0;
case 32:
_context17.prev = 32;
_context17.prev = 33;
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
case 35:
_context17.prev = 35;
if (!_didIteratorError3) {
_context17.next = 38;
break;
}
throw _iteratorError3;
case 38:
return _context17.finish(35);
case 39:
return _context17.finish(32);
case 40:
case 'end':
return _context17.stop();
}
}
}, _callee17, this, [[8, 28, 32, 40], [33,, 35, 39]]);
}));
return function (_x31, _x32) {
return _ref18.apply(this, arguments);
};
}();
/**
* Select a value in an html select element.
* @param {string} selector - The identifier for the select element.
* @param {string} value - The value to select.
* @param {string} frameId - The FrameID where the selector should be searched
*/
exports.select = function () {
var _ref19 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee18(selector, value, frameId) {
var node;
return regeneratorRuntime.wrap(function _callee18$(_context18) {
while (1) {
switch (_context18.prev = _context18.next) {
case 0:
debug(`:: select => ${typeof value !== 'undefined' ? 'Set select value to "' + value + '"' : 'Get select value'} in the select matching selector "${selector}" for frame "${frameId || 'root'}"`);
browserIsInitialized.call(this);
selector = fixSelector(selector);
_context18.next = 5;
return this.querySelector(selector, frameId);
case 5:
node = _context18.sent;
if (!(typeof value !== 'undefined')) {
_context18.next = 10;
break;
}
return _context18.abrupt('return', this.setNodeValue(node, value));
case 10:
return _context18.abrupt('return', this.getNodeValue(node));
case 11:
case 'end':
return _context18.stop();
}
}
}, _callee18, this);
}));
return function (_x33, _x34, _x35) {
return _ref19.apply(this, arguments);
};
}();
/**
* Fire a key event.
* @param {string} [type=keypress] - The type of key event.
* @param {string} [key=null] - The key to use for the event.
* @param {number} [modifier=0] - The keyboard modifier to use.
* @see {@link https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchKeyEvent}
*/
exports.keyboardEvent = function () {
var _ref20 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee19(type, key, modifier) {
var windowsVirtualKeyCode = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var parameters;
return regeneratorRuntime.wrap(function _callee19$(_context19) {
while (1) {
switch (_context19.prev = _context19.next) {
case 0:
debug(`:: keyboardEvent => Event type "${type}" | Key "${key}" | Modifier: "${modifier}"`);
browserIsInitialized.call(this);
type = typeof type === 'undefined' ? 'char' : type;
key = typeof key === 'undefined' ? null : key;
modifier = modifier || 0;
parameters = {
type: type,
modifiers: modifier,
text: key
};
if (windowsVirtualKeyCode) {
parameters.windowsVirtualKeyCode = windowsVirtualKeyCode;
parameters.nativeVirtualKeyCode = windowsVirtualKeyCode;
}
_context19.next = 9;
return this.client.Input.dispatchKeyEvent(parameters);
case 9:
case 'end':
return _context19.stop();
}
}
}, _callee19, this);
}));
return function (_x37, _x38, _x39) {
return _ref20.apply(this, arguments);
};
}();
/**
* Waits certain amount of ms
* @param {number} time - Ammount of ms to wait
*/
exports.wait = function () {
var _ref21 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee20(time) {
return regeneratorRuntime.wrap(function _callee20$(_context20) {
while (1) {
switch (_context20.prev = _context20.next) {
case 0:
debug(`:: wait => Waiting ${time} ms...`);
browserIsInitialized.call(this);
return _context20.abrupt('return', sleep(time));
case 3:
case 'end':
return _context20.stop();
}
}
}, _callee20, this);
}));
return function (_x40) {
return _ref21.apply(this, arguments);
};
}();
/**
* Binding callback to handle console messages
*
* @param listener is a callback for handling console message
*
*/
exports.onConsole = function () {
var _ref22 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee21(listener) {
return regeneratorRuntime.wrap(function _callee21$(_context21) {
while (1) {
switch (_context21.prev = _context21.next) {
case 0:
debug(`:: onConsole => Binding new listener to console`);
browserIsInitialized.call(this);
this.on('onConsoleMessage', listener);
case 3:
case 'end':
return _context21.stop();
}
}
}, _callee21, this);
}));
return function (_x41) {
return _ref22.apply(this, arguments);
};
}();
/**
* Waits for a page to finish loading. Throws error after timeout
* @param {number} timeout - The timeout in ms. (Default: "loadPageTimeout" property in the browser instance options)
*/
exports.waitForPageToLoad = function () {
var _ref23 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee22(timeout) {
var _this2 = this;
return regeneratorRuntime.wrap(function _callee22$(_context22) {
while (1) {
switch (_context22.prev = _context22.next) {
case 0:
debug(`:: waitForPageToLoad => Waiting page load...`);
browserIsInitialized.call(this);
return _context22.abrupt('return', promiseTimeout(new Promise(function (resolve, reject) {
var listener = function listener() {
debug(`:: waitForPageToLoad => Page loaded!`);
_this2.removeListener('pageLoaded', listener);
resolve(true);
};
_this2.on('pageLoaded', listener);
}), timeout || this.options.browser.loadPageTimeout));
case 3:
case 'end':
return _context22.stop();
}
}
}, _callee22, this);
}));
return function (_x42) {
return _ref23.apply(this, arguments);
};
}();
/**
* Waits for all the frames in the page to finish loading. Returns the list of frames after that
* @param {regexp|string} url - The URL that must be waited for load
* @return {object} - List of frames, with childFrames
*/
exports.waitForFrameToLoad = function () {
var _ref24 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee23(url, timeout) {
var _this3 = this;
var frames, frame;
return regeneratorRuntime.wrap(function _callee23$(_context23) {
while (1) {
switch (_context23.prev = _context23.next) {
case 0:
if (url) {
_context23.next = 2;
break;
}
throw new Error(`"url" parameter must be passed `);
case 2:
debug(`:: waitForFramesToLoad => Waiting page frame ${url} load...`);
browserIsInitialized.call(this);
_context23.next = 6;
return this.getFrames();
case 6:
frames = _context23.sent;
frame = _.find(frames, function (f) {
if (typeof url === 'string' && f.url === url || typeof url !== 'string' && f.url.match(url)) {
return f;
}
});
if (frame) {
_context23.next = 12;
break;
}
_context23.next = 11;
return promiseTimeout(new Promise(function (resolve, reject) {
var listener = function listener(data) {
debug(`:: waitForPageToLoad => Page loaded!`);
_this3.removeListener('frameNavigated', listener);
if (typeof url === 'string' && data.frame.url === url || typeof url !== 'string' && data.frame.url.match(url)) {
resolve(data.frame);
}
};
_this3.on('frameNavigated', listener);
}), timeout || this.options.browser.loadPageTimeout);
case 11:
frame = _context23.sent;
case 12:
return _context23.abrupt('return', frame);
case 13:
case 'end':
return _context23.stop();
}
}
}, _callee23, this);
}));
return function (_x43, _x44) {
return _ref24.apply(this, arguments);
};
}();
/**
* Waits for a selector to finish loading. Throws error after timeout
* @param {string} selector - The identifier for the select element.
* @param {number} interval - The interval in ms. (Default: "loadPageTimeout" property in the browser instance options)
* @param {number} timeout - The timeout in ms. (Default: "loadPageTimeout" property in the browser instance options)
*/
exports.waitForSelectorToLoad = function () {
var _ref25 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee24(selector, interval, timeout) {
var startDate, exists, diff;
return regeneratorRuntime.wrap(function _callee24$(_context24) {
while (1) {
switch (_context24.prev = _co