2012-11-16 04:48:15 +04:00
|
|
|
/*
|
2012-11-19 03:58:51 +04:00
|
|
|
This file is part of the GhostDriver by Ivan De Marino <http://ivandemarino.me>.
|
2012-11-16 04:48:15 +04:00
|
|
|
|
2014-07-24 21:25:46 +04:00
|
|
|
Copyright (c) 2012-2014, Ivan De Marino <http://ivandemarino.me>
|
2012-11-16 04:48:15 +04:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
|
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
|
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
var ghostdriver = ghostdriver || {};
|
|
|
|
|
|
|
|
ghostdriver.Session = function(desiredCapabilities) {
|
|
|
|
// private:
|
2013-03-18 05:30:24 +04:00
|
|
|
const
|
|
|
|
_const = {
|
2014-01-04 21:20:21 +04:00
|
|
|
TIMEOUT_NAMES : {
|
|
|
|
SCRIPT : "script",
|
|
|
|
IMPLICIT : "implicit",
|
|
|
|
PAGE_LOAD : "page load"
|
2013-03-18 05:30:24 +04:00
|
|
|
},
|
2014-01-04 21:20:21 +04:00
|
|
|
ONE_SHOT_POSTFIX : "OneShot",
|
|
|
|
LOG_TYPES : {
|
|
|
|
HAR : "har",
|
|
|
|
BROWSER : "browser"
|
2014-07-24 21:25:46 +04:00
|
|
|
},
|
|
|
|
PROXY_TYPES : {
|
|
|
|
MANUAL : "manual",
|
|
|
|
DIRECT : "direct"
|
2014-01-04 21:20:21 +04:00
|
|
|
}
|
2013-03-18 05:30:24 +04:00
|
|
|
};
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
var
|
|
|
|
_defaultCapabilities = { // TODO - Actually try to match the "desiredCapabilities" instead of ignoring them
|
|
|
|
"browserName" : "phantomjs",
|
2013-03-18 05:30:24 +04:00
|
|
|
"version" : phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch,
|
|
|
|
"driverName" : "ghostdriver",
|
|
|
|
"driverVersion" : ghostdriver.version,
|
2012-11-16 04:48:15 +04:00
|
|
|
"platform" : ghostdriver.system.os.name + '-' + ghostdriver.system.os.version + '-' + ghostdriver.system.os.architecture,
|
|
|
|
"javascriptEnabled" : true,
|
|
|
|
"takesScreenshot" : true,
|
|
|
|
"handlesAlerts" : false, //< TODO
|
|
|
|
"databaseEnabled" : false, //< TODO
|
|
|
|
"locationContextEnabled" : false, //< TODO Target is 1.1
|
|
|
|
"applicationCacheEnabled" : false, //< TODO Support for AppCache (?)
|
|
|
|
"browserConnectionEnabled" : false, //< TODO
|
|
|
|
"cssSelectorsEnabled" : true,
|
|
|
|
"webStorageEnabled" : false, //< TODO support for LocalStorage/SessionStorage
|
|
|
|
"rotatable" : false, //< TODO Target is 1.1
|
|
|
|
"acceptSslCerts" : false, //< TODO
|
|
|
|
"nativeEvents" : true, //< TODO Only some commands are Native Events currently
|
|
|
|
"proxy" : { //< TODO Support more proxy options - PhantomJS does allow setting from command line
|
2014-07-24 21:25:46 +04:00
|
|
|
"proxyType" : _const.PROXY_TYPES.DIRECT
|
|
|
|
},
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
_negotiatedCapabilities = {
|
|
|
|
"browserName" : _defaultCapabilities.browserName,
|
|
|
|
"version" : _defaultCapabilities.version,
|
2013-03-18 05:30:24 +04:00
|
|
|
"driverName" : _defaultCapabilities.driverName,
|
|
|
|
"driverVersion" : _defaultCapabilities.driverVersion,
|
2012-11-16 04:48:15 +04:00
|
|
|
"platform" : _defaultCapabilities.platform,
|
2014-01-04 21:20:21 +04:00
|
|
|
"javascriptEnabled" : _defaultCapabilities.javascriptEnabled,
|
2012-11-16 04:48:15 +04:00
|
|
|
"takesScreenshot" : typeof(desiredCapabilities.takesScreenshot) === "undefined" ?
|
|
|
|
_defaultCapabilities.takesScreenshot :
|
|
|
|
desiredCapabilities.takesScreenshot,
|
|
|
|
"handlesAlerts" : _defaultCapabilities.handlesAlerts,
|
|
|
|
"databaseEnabled" : _defaultCapabilities.databaseEnabled,
|
|
|
|
"locationContextEnabled" : _defaultCapabilities.locationContextEnabled,
|
|
|
|
"applicationCacheEnabled" : _defaultCapabilities.applicationCacheEnabled,
|
|
|
|
"browserConnectionEnabled" : _defaultCapabilities.browserConnectionEnabled,
|
|
|
|
"cssSelectorsEnabled" : _defaultCapabilities.cssSelectorsEnabled,
|
|
|
|
"webStorageEnabled" : _defaultCapabilities.webStorageEnabled,
|
|
|
|
"rotatable" : _defaultCapabilities.rotatable,
|
|
|
|
"acceptSslCerts" : _defaultCapabilities.acceptSslCerts,
|
|
|
|
"nativeEvents" : _defaultCapabilities.nativeEvents,
|
|
|
|
"proxy" : typeof(desiredCapabilities.proxy) === "undefined" ?
|
|
|
|
_defaultCapabilities.proxy :
|
|
|
|
desiredCapabilities.proxy
|
|
|
|
},
|
2012-12-15 23:00:23 +04:00
|
|
|
// NOTE: This value is needed for Timeouts Upper-bound limit.
|
|
|
|
// "setTimeout/setInterval" accept only 32 bit integers, even though Number are all Doubles (go figure!)
|
|
|
|
// Interesting details here: {@link http://stackoverflow.com/a/4995054}.
|
|
|
|
_max32bitInt = Math.pow(2, 31) -1, //< Max 32bit Int
|
2012-11-16 04:48:15 +04:00
|
|
|
_timeouts = {
|
2012-12-15 23:00:23 +04:00
|
|
|
"script" : _max32bitInt,
|
2014-01-04 21:20:21 +04:00
|
|
|
"implicit" : 200, //< 200ms
|
2012-12-15 23:00:23 +04:00
|
|
|
"page load" : _max32bitInt,
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
_windows = {}, //< NOTE: windows are "webpage" in Phantom-dialect
|
|
|
|
_currentWindowHandle = null,
|
2014-01-13 01:08:21 +04:00
|
|
|
_cookieJar = require('cookiejar').create(),
|
2012-11-16 04:48:15 +04:00
|
|
|
_id = require("./third_party/uuid.js").v1(),
|
|
|
|
_inputs = ghostdriver.Inputs(),
|
2012-12-15 23:00:23 +04:00
|
|
|
_capsPageSettingsPref = "phantomjs.page.settings.",
|
2013-07-26 02:24:53 +04:00
|
|
|
_capsPageCustomHeadersPref = "phantomjs.page.customHeaders.",
|
2014-07-24 21:25:46 +04:00
|
|
|
_capsPageSettingsProxyPref = "proxy",
|
2012-12-15 23:00:23 +04:00
|
|
|
_pageSettings = {},
|
2014-07-24 21:25:46 +04:00
|
|
|
_additionalPageSettings = {
|
|
|
|
userName: null,
|
|
|
|
password: null
|
|
|
|
},
|
2013-07-26 02:24:53 +04:00
|
|
|
_pageCustomHeaders = {},
|
2013-03-18 05:30:24 +04:00
|
|
|
_log = ghostdriver.logger.create("Session [" + _id + "]"),
|
2014-07-24 21:25:46 +04:00
|
|
|
k, settingKey, headerKey, proxySettings;
|
|
|
|
|
|
|
|
var
|
|
|
|
/**
|
|
|
|
* Parses proxy JSON object and return proxy settings for phantom
|
|
|
|
*
|
|
|
|
* @param proxyCapability proxy JSON Object: @see https://code.google.com/p/selenium/wiki/DesiredCapabilities
|
|
|
|
*/
|
|
|
|
_getProxySettingsFromCapabilities = function(proxyCapability) {
|
|
|
|
var proxySettings = {};
|
|
|
|
if (proxyCapability["proxyType"].toLowerCase() == _const.PROXY_TYPES.MANUAL) { //< TODO: support other options
|
|
|
|
if (proxyCapability["httpProxy"] !== "null") { //< TODO: support other proxy types
|
|
|
|
var urlParts = proxyCapability["httpProxy"].split(':');
|
|
|
|
proxySettings["ip"] = urlParts[0];
|
|
|
|
proxySettings["port"] = urlParts[1];
|
|
|
|
proxySettings["proxyType"] = "http";
|
|
|
|
proxySettings["user"] = "";
|
|
|
|
proxySettings["password"] = "";
|
|
|
|
|
|
|
|
return proxySettings;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return proxySettings;
|
|
|
|
};
|
2012-12-15 23:00:23 +04:00
|
|
|
|
2013-07-26 02:24:53 +04:00
|
|
|
// Searching for `phantomjs.settings.* and phantomjs.customHeaders.*` in the Desired Capabilities and merging with the Negotiated Capabilities
|
|
|
|
// Possible values for settings: @see https://github.com/ariya/phantomjs/wiki/API-Reference#wiki-webpage-settings.
|
|
|
|
// Possible values for customHeaders: @see https://github.com/ariya/phantomjs/wiki/API-Reference-WebPage#wiki-webpage-customHeaders.
|
2012-12-15 23:00:23 +04:00
|
|
|
for (k in desiredCapabilities) {
|
|
|
|
if (k.indexOf(_capsPageSettingsPref) === 0) {
|
|
|
|
settingKey = k.substring(_capsPageSettingsPref.length);
|
|
|
|
if (settingKey.length > 0) {
|
|
|
|
_negotiatedCapabilities[k] = desiredCapabilities[k];
|
|
|
|
_pageSettings[settingKey] = desiredCapabilities[k];
|
|
|
|
}
|
|
|
|
}
|
2013-07-26 02:24:53 +04:00
|
|
|
if (k.indexOf(_capsPageCustomHeadersPref) === 0) {
|
|
|
|
headerKey = k.substring(_capsPageCustomHeadersPref.length);
|
|
|
|
if (headerKey.length > 0) {
|
|
|
|
_negotiatedCapabilities[k] = desiredCapabilities[k];
|
|
|
|
_pageCustomHeaders[headerKey] = desiredCapabilities[k];
|
|
|
|
}
|
|
|
|
}
|
2014-07-24 21:25:46 +04:00
|
|
|
if (k.indexOf(_capsPageSettingsProxyPref) === 0) {
|
|
|
|
proxySettings = _getProxySettingsFromCapabilities(desiredCapabilities[k]);
|
|
|
|
phantom.setProxy(proxySettings["ip"], proxySettings["port"], proxySettings["proxyType"], proxySettings["user"], proxySettings["password"]);
|
|
|
|
}
|
2012-12-15 23:00:23 +04:00
|
|
|
}
|
2012-11-16 04:48:15 +04:00
|
|
|
|
2012-12-15 23:00:23 +04:00
|
|
|
var
|
2012-11-16 04:48:15 +04:00
|
|
|
/**
|
|
|
|
* Executes a function and waits for Load to happen.
|
|
|
|
*
|
2013-03-18 05:30:24 +04:00
|
|
|
* @param code Code to execute: a Function or just plain code
|
2012-11-16 04:48:15 +04:00
|
|
|
* @param onLoadFunc Function to execute when page finishes Loading
|
|
|
|
* @param onErrorFunc Function to execute in case of error
|
2013-03-18 05:30:24 +04:00
|
|
|
* (eg. Javascript error, page load problem or timeout).
|
2012-11-16 04:48:15 +04:00
|
|
|
* @param execTypeOpt Decides if to "apply" the function directly or page."eval" it.
|
|
|
|
* Optional. Default value is "apply".
|
|
|
|
*/
|
2013-03-18 05:30:24 +04:00
|
|
|
_execFuncAndWaitForLoadDecorator = function(code, onLoadFunc, onErrorFunc, execTypeOpt) {
|
2012-11-16 04:48:15 +04:00
|
|
|
// convert 'arguments' to a real Array
|
|
|
|
var args = Array.prototype.splice.call(arguments, 0),
|
2013-03-18 05:30:24 +04:00
|
|
|
thisPage = this,
|
|
|
|
onLoadFinishedArgs = null,
|
|
|
|
onErrorArgs = null;
|
2012-11-16 04:48:15 +04:00
|
|
|
|
|
|
|
// Normalize "execTypeOpt" value
|
2013-03-18 05:30:24 +04:00
|
|
|
if (typeof(execTypeOpt) === "undefined" ||
|
|
|
|
(execTypeOpt !== "apply" && execTypeOpt !== "eval")) {
|
2012-11-16 04:48:15 +04:00
|
|
|
execTypeOpt = "apply";
|
|
|
|
}
|
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
// Register Callbacks to grab any async event we are interested in
|
|
|
|
this.setOneShotCallback("onLoadFinished", function (status) {
|
|
|
|
_log.debug("_execFuncAndWaitForLoadDecorator", "onLoadFinished: " + status);
|
2012-11-16 04:48:15 +04:00
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
onLoadFinishedArgs = Array.prototype.slice.call(arguments);
|
2012-11-16 04:48:15 +04:00
|
|
|
});
|
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
// Execute "code"
|
2012-11-16 04:48:15 +04:00
|
|
|
if (execTypeOpt === "eval") {
|
2013-03-18 05:30:24 +04:00
|
|
|
// Remove arguments used by this function before providing them to the target code.
|
|
|
|
// NOTE: Passing 'code' (to evaluate) and '0' (timeout) to 'evaluateAsync'.
|
|
|
|
args.splice(0, 3, code, 0);
|
2012-11-16 04:48:15 +04:00
|
|
|
// Invoke the Page Eval with the provided function
|
|
|
|
this.evaluateAsync.apply(this, args);
|
|
|
|
} else {
|
2013-03-18 05:30:24 +04:00
|
|
|
// Remove arguments used by this function before providing them to the target function.
|
|
|
|
args.splice(0, 3);
|
2012-11-16 04:48:15 +04:00
|
|
|
// "Apply" the provided function
|
2013-03-18 05:30:24 +04:00
|
|
|
code.apply(this, args);
|
2012-11-16 04:48:15 +04:00
|
|
|
}
|
2013-03-18 05:30:24 +04:00
|
|
|
|
|
|
|
// Wait 10ms before proceeding any further: in this window of time
|
|
|
|
// the page can react and start loading (if it has to).
|
|
|
|
setTimeout(function() {
|
2014-01-04 21:20:21 +04:00
|
|
|
var loadingStartedTs = new Date().getTime(),
|
2013-03-18 05:30:24 +04:00
|
|
|
checkLoadingFinished;
|
|
|
|
|
|
|
|
checkLoadingFinished = function() {
|
|
|
|
if (!_isLoading()) { //< page finished loading
|
|
|
|
_log.debug("_execFuncAndWaitForLoadDecorator", "Page Loading in Session: false");
|
|
|
|
|
|
|
|
if (onLoadFinishedArgs !== null) {
|
|
|
|
// Report the result of the "Load Finished" event
|
|
|
|
onLoadFunc.apply(thisPage, onLoadFinishedArgs);
|
|
|
|
} else {
|
|
|
|
// No page load was caused: just report "success"
|
|
|
|
onLoadFunc.call(thisPage, "success");
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
} // else:
|
|
|
|
_log.debug("_execFuncAndWaitForLoadDecorator", "Page Loading in Session: true");
|
|
|
|
|
|
|
|
// Timeout error?
|
|
|
|
if (new Date().getTime() - loadingStartedTs > _getPageLoadTimeout()) {
|
|
|
|
// Report the "Timeout" event
|
|
|
|
onErrorFunc.call(thisPage, "timeout");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retry in 100ms
|
|
|
|
setTimeout(checkLoadingFinished, 100);
|
|
|
|
};
|
|
|
|
checkLoadingFinished();
|
2013-07-26 02:24:53 +04:00
|
|
|
}, 10); //< 10ms
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
/**
|
|
|
|
* Wait for Page to be done Loading before executing of callback.
|
|
|
|
* Also, it considers "Page Timeout" to avoid waiting indefinitely.
|
|
|
|
* NOTE: This is useful for cases where it's not certain a certain action
|
|
|
|
* just executed MIGHT cause a page to start loading.
|
|
|
|
* It's a "best effort" approach and the user is given the use of
|
|
|
|
* "Page Timeout" to tune to their needs.
|
|
|
|
*
|
|
|
|
* @param callback Function to execute when done or timed out
|
|
|
|
*/
|
|
|
|
_waitIfLoadingDecorator = function(callback) {
|
|
|
|
var thisPage = this,
|
|
|
|
waitStartedTs = new Date().getTime(),
|
|
|
|
checkDoneLoading;
|
|
|
|
|
|
|
|
checkDoneLoading = function() {
|
|
|
|
if (!_isLoading() //< Session is not loading (any more?)
|
|
|
|
|| (new Date().getTime() - waitStartedTs > _getPageLoadTimeout())) { //< OR Page Timeout expired
|
|
|
|
callback.call(thisPage);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_log.debug("_waitIfLoading", "Still loading (wait using Implicit Timeout)");
|
|
|
|
|
|
|
|
// Retry in 10ms
|
|
|
|
setTimeout(checkDoneLoading, 10);
|
|
|
|
};
|
|
|
|
checkDoneLoading();
|
|
|
|
},
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
_oneShotCallbackFactory = function(page, callbackName) {
|
|
|
|
return function() {
|
2014-01-04 21:20:21 +04:00
|
|
|
var oneShotCallbackName = callbackName + _const.ONE_SHOT_POSTFIX,
|
|
|
|
i, retVal;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// If there are callback functions registered
|
|
|
|
if (page[oneShotCallbackName] instanceof Array
|
|
|
|
&& page[oneShotCallbackName].length > 0) {
|
|
|
|
_log.debug("_oneShotCallback", callbackName);
|
|
|
|
|
|
|
|
// Invoke all the callback functions (once)
|
|
|
|
for (i = page[oneShotCallbackName].length -1; i >= 0; --i) {
|
|
|
|
retVal = page[oneShotCallbackName][i].apply(page, arguments);
|
|
|
|
}
|
2013-03-18 05:30:24 +04:00
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
// Remove all the callback functions now
|
|
|
|
page[oneShotCallbackName] = [];
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// In case the "page" object has been closed,
|
|
|
|
// the code above will fail: that's OK.
|
2012-11-16 04:48:15 +04:00
|
|
|
}
|
2014-01-04 21:20:21 +04:00
|
|
|
|
|
|
|
// Return (latest) value
|
2012-11-16 04:48:15 +04:00
|
|
|
return retVal;
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
_setOneShotCallbackDecorator = function(callbackName, handlerFunc) {
|
2014-01-04 21:20:21 +04:00
|
|
|
var oneShotCallbackName = callbackName + _const.ONE_SHOT_POSTFIX;
|
2012-11-16 04:48:15 +04:00
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
// Initialize array of One Shot Callbacks
|
|
|
|
if (!(this[oneShotCallbackName] instanceof Array)) {
|
|
|
|
this[oneShotCallbackName] = [];
|
|
|
|
}
|
|
|
|
this[oneShotCallbackName].push(handlerFunc);
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
// Add any new page to the "_windows" container of this session
|
|
|
|
_addNewPage = function(newPage) {
|
2013-03-18 05:30:24 +04:00
|
|
|
_log.debug("_addNewPage");
|
|
|
|
|
2014-01-13 01:08:21 +04:00
|
|
|
// decorate the new Window/Page
|
|
|
|
newPage = _decorateNewWindow(newPage);
|
|
|
|
// set session-specific CookieJar
|
|
|
|
newPage.cookieJar = _cookieJar;
|
|
|
|
// store the Window/Page by WindowHandle
|
|
|
|
_windows[newPage.windowHandle] = newPage;
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
// Delete any closing page from the "_windows" container of this session
|
|
|
|
_deleteClosingPage = function(closingPage) {
|
2013-03-18 05:30:24 +04:00
|
|
|
_log.debug("_deleteClosingPage");
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// Need to be defensive, as the "closing" can be cause by Client Commands
|
|
|
|
if (_windows.hasOwnProperty(closingPage.windowHandle)) {
|
|
|
|
delete _windows[closingPage.windowHandle];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_decorateNewWindow = function(page) {
|
2012-12-15 23:00:23 +04:00
|
|
|
var k;
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// Decorating:
|
|
|
|
// 0. Pages lifetime will be managed by Driver, not the pages
|
|
|
|
page.ownsPages = false;
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// 1. Random Window Handle
|
|
|
|
page.windowHandle = require("./third_party/uuid.js").v1();
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// 2. Initialize the One-Shot Callbacks
|
|
|
|
page["onLoadStarted"] = _oneShotCallbackFactory(page, "onLoadStarted");
|
|
|
|
page["onLoadFinished"] = _oneShotCallbackFactory(page, "onLoadFinished");
|
|
|
|
page["onUrlChanged"] = _oneShotCallbackFactory(page, "onUrlChanged");
|
|
|
|
page["onFilePicker"] = _oneShotCallbackFactory(page, "onFilePicker");
|
|
|
|
page["onCallback"] = _oneShotCallbackFactory(page, "onCallback");
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// 3. Utility methods
|
|
|
|
page.execFuncAndWaitForLoad = _execFuncAndWaitForLoadDecorator;
|
|
|
|
page.setOneShotCallback = _setOneShotCallbackDecorator;
|
2014-01-04 21:20:21 +04:00
|
|
|
page.waitIfLoading = _waitIfLoadingDecorator;
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// 4. Store every newly created page
|
|
|
|
page.onPageCreated = _addNewPage;
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
// 5. Remove every closing page
|
|
|
|
page.onClosing = _deleteClosingPage;
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2012-12-15 23:00:23 +04:00
|
|
|
// 6. Applying Page settings received via capabilities
|
|
|
|
for (k in _pageSettings) {
|
|
|
|
// Apply setting only if really supported by PhantomJS
|
2014-07-24 21:25:46 +04:00
|
|
|
if (page.settings.hasOwnProperty(k) || _additionalPageSettings.hasOwnProperty(k)) {
|
2012-12-15 23:00:23 +04:00
|
|
|
page.settings[k] = _pageSettings[k];
|
|
|
|
}
|
|
|
|
}
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2013-07-26 02:24:53 +04:00
|
|
|
// 7. Applying Page custom headers received via capabilities
|
|
|
|
page.customHeaders = _pageCustomHeaders;
|
2014-01-04 21:20:21 +04:00
|
|
|
|
2013-07-26 02:24:53 +04:00
|
|
|
// 8. Log Page internal errors
|
2014-01-04 21:20:21 +04:00
|
|
|
page.onError = function(errorMsg, errorStack) {
|
|
|
|
var stack = '';
|
|
|
|
|
|
|
|
// Prep the "stack" part of the message
|
|
|
|
errorStack.forEach(function (stackEntry, idx, arr) {
|
|
|
|
stack += " " //< a bit of indentation
|
|
|
|
+ (stackEntry.function || "(anonymous function)")
|
|
|
|
+ " (" + stackEntry.file + ":" + stackEntry.line + ")";
|
|
|
|
stack += idx < arr.length - 1 ? "\n" : "";
|
|
|
|
});
|
|
|
|
|
|
|
|
// Log as error
|
|
|
|
_log.error("page.onError", "msg: " + errorMsg);
|
|
|
|
_log.error("page.onError", "stack:\n" + stack);
|
|
|
|
|
|
|
|
// Register as part of the "browser" log
|
|
|
|
page.browserLog.push(_createLogEntry("WARNING", errorMsg + "\n" + stack));
|
|
|
|
};
|
|
|
|
|
|
|
|
// 9. Log Page console messages
|
|
|
|
page.browserLog = [];
|
|
|
|
page.onConsoleMessage = function(msg, lineNum, sourceId) {
|
|
|
|
// Log as debug
|
|
|
|
_log.debug("page.onConsoleMessage", msg);
|
|
|
|
|
|
|
|
// Register as part of the "browser" log
|
|
|
|
page.browserLog.push(_createLogEntry("INFO", msg + " (" + sourceId + ":" + lineNum + ")"));
|
|
|
|
};
|
|
|
|
|
|
|
|
// 10. Log Page network activity
|
|
|
|
page.resources = [];
|
|
|
|
page.startTime = null;
|
|
|
|
page.endTime = null;
|
|
|
|
page.setOneShotCallback("onLoadStarted", function() {
|
|
|
|
page.startTime = new Date();
|
|
|
|
});
|
|
|
|
page.setOneShotCallback("onLoadFinished", function() {
|
|
|
|
page.endTime = new Date();
|
|
|
|
});
|
|
|
|
page.onResourceRequested = function (req) {
|
|
|
|
_log.debug("page.onResourceRequested", JSON.stringify(req));
|
|
|
|
|
|
|
|
// Register HTTP Request
|
|
|
|
page.resources[req.id] = {
|
|
|
|
request: req,
|
|
|
|
startReply: null,
|
|
|
|
endReply: null,
|
|
|
|
error: null
|
|
|
|
};
|
|
|
|
};
|
|
|
|
page.onResourceReceived = function (res) {
|
|
|
|
_log.debug("page.onResourceReceived", JSON.stringify(res));
|
|
|
|
|
|
|
|
// Register HTTP Response
|
|
|
|
page.resources[res.id] || (page.resources[res.id] = {});
|
|
|
|
if (res.stage === 'start') {
|
|
|
|
page.resources[res.id].startReply = res;
|
|
|
|
} else if (res.stage === 'end') {
|
|
|
|
page.resources[res.id].endReply = res;
|
|
|
|
}
|
2013-07-26 02:24:53 +04:00
|
|
|
};
|
2014-01-04 21:20:21 +04:00
|
|
|
page.onResourceError = function(resError) {
|
|
|
|
_log.debug("page.onResourceError", JSON.stringify(resError));
|
2012-11-16 04:48:15 +04:00
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
// Register HTTP Error
|
|
|
|
page.resources[resError.id] || (page.resources[resError.id] = {});
|
|
|
|
page.resources[resError.id].error = resError;
|
|
|
|
};
|
|
|
|
page.onResourceTimeout = function(req) {
|
|
|
|
_log.debug("page.onResourceTimeout", JSON.stringify(req));
|
|
|
|
|
|
|
|
// Register HTTP Timeout
|
|
|
|
page.resources[req.id] || (page.resources[req.id] = {});
|
|
|
|
page.resources[req.id].error = req;
|
|
|
|
};
|
|
|
|
page.onNavigationRequested = function(url, type, willNavigate, main) {
|
|
|
|
// Clear page log before page loading
|
|
|
|
if (main && willNavigate) {
|
|
|
|
_clearPageLog(page);
|
|
|
|
}
|
|
|
|
};
|
2013-03-18 05:30:24 +04:00
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
_log.info("page.settings", JSON.stringify(page.settings));
|
2013-07-26 02:24:53 +04:00
|
|
|
_log.info("page.customHeaders: ", JSON.stringify(page.customHeaders));
|
2012-11-16 04:48:15 +04:00
|
|
|
|
|
|
|
return page;
|
|
|
|
},
|
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
_createLogEntry = function(level, message) {
|
|
|
|
return {
|
|
|
|
"level" : level,
|
|
|
|
"message" : message,
|
|
|
|
"timestamp" : (new Date()).getTime()
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
/**
|
|
|
|
* Is any window in this Session Loading?
|
|
|
|
* @returns "true" if at least 1 window is loading.
|
|
|
|
*/
|
|
|
|
_isLoading = function() {
|
|
|
|
var wHandle;
|
|
|
|
|
|
|
|
for (wHandle in _windows) {
|
|
|
|
if (_windows[wHandle].loading) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we arrived here, means that no window is loading
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
/**
|
|
|
|
* According to log method specification we have to clear log after each page refresh.
|
|
|
|
* https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/log
|
|
|
|
* @param {Object} page
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_clearPageLog = function (page) {
|
|
|
|
page.resources = [];
|
|
|
|
page.browserLog = [];
|
|
|
|
},
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
_getWindow = function(handleOrName) {
|
|
|
|
var page = null,
|
|
|
|
k;
|
|
|
|
|
|
|
|
if (_isValidWindowHandle(handleOrName)) {
|
|
|
|
// Search by "handle"
|
|
|
|
page = _windows[handleOrName];
|
|
|
|
} else {
|
|
|
|
// Search by "name"
|
|
|
|
for (k in _windows) {
|
|
|
|
if (_windows[k].windowName === handleOrName) {
|
|
|
|
page = _windows[k];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return page;
|
|
|
|
},
|
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
_init = function() {
|
|
|
|
var page;
|
|
|
|
|
2012-12-15 23:00:23 +04:00
|
|
|
// Ensure a Current Window is available, if it's found to be `null`
|
2012-11-16 04:48:15 +04:00
|
|
|
if (_currentWindowHandle === null) {
|
2014-01-13 01:08:21 +04:00
|
|
|
// Create the first Window/Page
|
|
|
|
page = require("webpage").create();
|
|
|
|
// Decorate it with listeners and helpers
|
|
|
|
page = _decorateNewWindow(page);
|
|
|
|
// set session-specific CookieJar
|
|
|
|
page.cookieJar = _cookieJar;
|
|
|
|
// Make the new Window, the Current Window
|
2012-11-16 04:48:15 +04:00
|
|
|
_currentWindowHandle = page.windowHandle;
|
2014-01-13 01:08:21 +04:00
|
|
|
// Store by WindowHandle
|
2012-11-16 04:48:15 +04:00
|
|
|
_windows[_currentWindowHandle] = page;
|
2012-12-15 23:00:23 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_getCurrentWindow = function() {
|
|
|
|
var page = null;
|
|
|
|
|
|
|
|
if (_windows.hasOwnProperty(_currentWindowHandle)) {
|
2012-11-16 04:48:15 +04:00
|
|
|
page = _windows[_currentWindowHandle];
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Handle "null" cases throwing a "no such window" error
|
|
|
|
|
|
|
|
return page;
|
|
|
|
},
|
|
|
|
|
|
|
|
_switchToWindow = function(handleOrName) {
|
|
|
|
var page = _getWindow(handleOrName);
|
|
|
|
|
|
|
|
if (page !== null) {
|
|
|
|
// Switch current window and return "true"
|
|
|
|
_currentWindowHandle = page.windowHandle;
|
2012-12-15 23:00:23 +04:00
|
|
|
// Switch to the Main Frame of that window
|
|
|
|
page.switchToMainFrame();
|
2012-11-16 04:48:15 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Couldn't find the window, so return "false"
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_closeCurrentWindow = function() {
|
|
|
|
if (_currentWindowHandle !== null) {
|
|
|
|
return _closeWindow(_currentWindowHandle);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_closeWindow = function(handleOrName) {
|
|
|
|
var page = _getWindow(handleOrName),
|
|
|
|
handle;
|
|
|
|
|
|
|
|
if (page !== null) {
|
|
|
|
handle = page.windowHandle;
|
|
|
|
_windows[handle].close();
|
|
|
|
delete _windows[handle];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getWindowsCount = function() {
|
|
|
|
return Object.keys(_windows).length;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getCurrentWindowHandle = function() {
|
|
|
|
if (!_isValidWindowHandle(_currentWindowHandle)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return _currentWindowHandle;
|
|
|
|
},
|
|
|
|
|
|
|
|
_isValidWindowHandle = function(handle) {
|
|
|
|
return _windows.hasOwnProperty(handle);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getWindowHandles = function() {
|
|
|
|
return Object.keys(_windows);
|
|
|
|
},
|
|
|
|
|
|
|
|
_setTimeout = function(type, ms) {
|
2012-12-15 23:00:23 +04:00
|
|
|
// In case the chosen timeout is less than 0, we reset it to `_max32bitInt`
|
|
|
|
if (ms < 0) {
|
|
|
|
_timeouts[type] = _max32bitInt;
|
|
|
|
} else {
|
|
|
|
_timeouts[type] = ms;
|
|
|
|
}
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
_getTimeout = function(type) {
|
|
|
|
return _timeouts[type];
|
|
|
|
},
|
|
|
|
|
2012-12-15 23:00:23 +04:00
|
|
|
_getScriptTimeout = function() {
|
|
|
|
return _getTimeout(_const.TIMEOUT_NAMES.SCRIPT);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getImplicitTimeout = function() {
|
|
|
|
return _getTimeout(_const.TIMEOUT_NAMES.IMPLICIT);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPageLoadTimeout = function() {
|
|
|
|
return _getTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD);
|
|
|
|
},
|
|
|
|
|
|
|
|
_setScriptTimeout = function(ms) {
|
|
|
|
_setTimeout(_const.TIMEOUT_NAMES.SCRIPT, ms);
|
|
|
|
},
|
|
|
|
|
|
|
|
_setImplicitTimeout = function(ms) {
|
|
|
|
_setTimeout(_const.TIMEOUT_NAMES.IMPLICIT, ms);
|
|
|
|
},
|
|
|
|
|
|
|
|
_setPageLoadTimeout = function(ms) {
|
|
|
|
_setTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD, ms);
|
2012-11-16 04:48:15 +04:00
|
|
|
},
|
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
_executePhantomJS = function(page, script, args) {
|
|
|
|
try {
|
|
|
|
var code = new Function(script);
|
|
|
|
return code.apply(page, args);
|
|
|
|
} catch (e) {
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-16 04:48:15 +04:00
|
|
|
_aboutToDelete = function() {
|
|
|
|
var k;
|
|
|
|
|
|
|
|
// Close current window first
|
|
|
|
_closeCurrentWindow();
|
|
|
|
|
|
|
|
// Releasing page resources and deleting the objects
|
|
|
|
for (k in _windows) {
|
|
|
|
_closeWindow(k);
|
|
|
|
}
|
2014-01-13 01:08:21 +04:00
|
|
|
|
|
|
|
// Release CookieJar resources
|
|
|
|
_cookieJar.close();
|
2014-01-04 21:20:21 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
_getLog = function (type) {
|
|
|
|
var har = require('./third_party/har.js'),
|
|
|
|
page, tmp;
|
|
|
|
|
|
|
|
// Return "HAR" as Log Type "har"
|
|
|
|
if (type === _const.LOG_TYPES.HAR) {
|
|
|
|
page = _getCurrentWindow();
|
|
|
|
tmp = [];
|
|
|
|
tmp.push(_createLogEntry(
|
|
|
|
"INFO",
|
|
|
|
JSON.stringify(har.createHar(page, page.resources))));
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return Browser Console Log
|
|
|
|
if (type === _const.LOG_TYPES.BROWSER) {
|
|
|
|
return _getCurrentWindow().browserLog;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return empty Log
|
|
|
|
return [];
|
|
|
|
},
|
|
|
|
|
|
|
|
_getLogTypes = function () {
|
|
|
|
var logTypes = [], k;
|
|
|
|
|
|
|
|
for (k in _const.LOG_TYPES) {
|
|
|
|
logTypes.push(_const.LOG_TYPES[k]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return logTypes;
|
2012-11-16 04:48:15 +04:00
|
|
|
};
|
|
|
|
|
2013-03-18 05:30:24 +04:00
|
|
|
// Initialize the Session.
|
|
|
|
// Particularly, create the first empty page/window.
|
|
|
|
_init();
|
|
|
|
|
2014-01-04 21:20:21 +04:00
|
|
|
_log.debug("Session.desiredCapabilities", JSON.stringify(desiredCapabilities));
|
|
|
|
_log.info("Session.negotiatedCapabilities", JSON.stringify(_negotiatedCapabilities));
|
2012-11-16 04:48:15 +04:00
|
|
|
|
|
|
|
// public:
|
|
|
|
return {
|
|
|
|
getCapabilities : function() { return _negotiatedCapabilities; },
|
|
|
|
getId : function() { return _id; },
|
|
|
|
switchToWindow : _switchToWindow,
|
|
|
|
getCurrentWindow : _getCurrentWindow,
|
|
|
|
closeCurrentWindow : _closeCurrentWindow,
|
|
|
|
getWindow : _getWindow,
|
|
|
|
closeWindow : _closeWindow,
|
|
|
|
getWindowsCount : _getWindowsCount,
|
|
|
|
getCurrentWindowHandle : _getCurrentWindowHandle,
|
2012-12-15 23:00:23 +04:00
|
|
|
getWindowHandles : _getWindowHandles,
|
|
|
|
isValidWindowHandle : _isValidWindowHandle,
|
2012-11-16 04:48:15 +04:00
|
|
|
aboutToDelete : _aboutToDelete,
|
2012-12-15 23:00:23 +04:00
|
|
|
inputs : _inputs,
|
|
|
|
setScriptTimeout : _setScriptTimeout,
|
|
|
|
setImplicitTimeout : _setImplicitTimeout,
|
|
|
|
setPageLoadTimeout : _setPageLoadTimeout,
|
|
|
|
getScriptTimeout : _getScriptTimeout,
|
|
|
|
getImplicitTimeout : _getImplicitTimeout,
|
|
|
|
getPageLoadTimeout : _getPageLoadTimeout,
|
2014-01-04 21:20:21 +04:00
|
|
|
executePhantomJS : _executePhantomJS,
|
2013-03-18 05:30:24 +04:00
|
|
|
timeoutNames : _const.TIMEOUT_NAMES,
|
2014-01-04 21:20:21 +04:00
|
|
|
isLoading : _isLoading,
|
|
|
|
getLog: _getLog,
|
|
|
|
getLogTypes: _getLogTypes
|
2012-11-16 04:48:15 +04:00
|
|
|
};
|
|
|
|
};
|