Ivan De Marino 2012-12-15 19:00:23 +00:00 committed by Ariya Hidayat
parent f5652e5110
commit 027aa93b18
7 changed files with 219 additions and 46 deletions

View File

@ -1,10 +1,7 @@
2012-11-25 20:54:35 2012-12-15 18:59:39
commit 54876580d0a3886e42850804b03184adbd899d65 (HEAD, tag: refs/tags/1.0.0, refs/remotes/origin/master, refs/remotes/origin/HEAD, refs/heads/master) commit 2676fd4e6410202ec439a235316451ea2c633681 (HEAD, tag: refs/tags/1.0.1, refs/heads/master)
Author: Ivan De Marino <ivan.de.marino@gmail.com> Author: Ivan De Marino <ivan.de.marino@gmail.com>
Date: Sun Nov 25 19:05:15 2012 +0000 Date: Sat Dec 15 18:58:34 2012 +0000
Updated export script. Preparing GhostDriver version `1.0.1`.
1. Creates the `README.md` to inform PhantomJS developers that the code is automatically exported from GhostDriver
2. Adds `--decorate=full` to the line where we populate the `lastupdate` file

View File

@ -30,7 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var ghostdriver = { var ghostdriver = {
system : require('system'), system : require('system'),
hub : require('./hub_register'), hub : require('./hub_register'),
version : "1.0.0" version : "1.0.1"
}, },
server = require('webserver').create(), server = require('webserver').create(),
router, router,

View File

@ -345,7 +345,7 @@ ghostdriver.SessionReqHand = function(session) {
var postObj = JSON.parse(req.post), var postObj = JSON.parse(req.post),
result, result,
timer, timer,
scriptTimeout = _session.getTimeout(_session.timeoutNames().SCRIPT), scriptTimeout = _session.getScriptTimeout(),
timedOut = false; timedOut = false;
if (typeof(postObj) === "object" && postObj.script && postObj.args) { if (typeof(postObj) === "object" && postObj.script && postObj.args) {
@ -355,7 +355,7 @@ ghostdriver.SessionReqHand = function(session) {
timedOut = true; timedOut = true;
_errors.handleFailedCommandEH( _errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.TIMEOUT, _errors.FAILED_CMD_STATUS.TIMEOUT,
"Script didn't return within "+scriptTimeout+"ms", "Script didn't return within " + scriptTimeout + "ms",
req, req,
res, res,
_session, _session,
@ -399,14 +399,21 @@ ghostdriver.SessionReqHand = function(session) {
"}", "}",
postObj.script, postObj.script,
postObj.args, postObj.args,
_session.getTimeout(_session.timeoutNames().ASYNC_SCRIPT)); _session.getAsyncScriptTimeout());
} else { } else {
throw _errors.createInvalidReqMissingCommandParameterEH(req); throw _errors.createInvalidReqMissingCommandParameterEH(req);
} }
}, },
_getWindowHandle = function (req, res) { _getWindowHandle = function (req, res) {
var handle = _session.getCurrentWindowHandle(); var handle;
// Initialize the Current Window (we need at least that)
_session.initCurrentWindowIfNull();
// Get current window handle
handle = _session.getCurrentWindowHandle();
if (handle !== null) { if (handle !== null) {
res.success(_session.getId(), handle); res.success(_session.getId(), handle);
} else { } else {
@ -420,6 +427,8 @@ ghostdriver.SessionReqHand = function(session) {
}, },
_getWindowHandles = function(req, res) { _getWindowHandles = function(req, res) {
// Initialize the Current Window (we need at least that)
_session.initCurrentWindowIfNull();
res.success(_session.getId(), _session.getWindowHandles()); res.success(_session.getId(), _session.getWindowHandles());
}, },
@ -460,9 +469,7 @@ ghostdriver.SessionReqHand = function(session) {
// Request timed out // Request timed out
_errors.handleFailedCommandEH( _errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.TIMEOUT, _errors.FAILED_CMD_STATUS.TIMEOUT,
"URL '" + postObj.url + "' didn't load within " + "URL '" + postObj.url + "' didn't load within " + _session.getPageLoadTimeout() + "ms",
_session.getTimeout(_session.timeoutNames().PAGE_LOAD) +
"ms",
req, req,
res, res,
_session, _session,
@ -478,13 +485,28 @@ ghostdriver.SessionReqHand = function(session) {
// Normalize the call: the "type" is read from the URL, not a POST parameter // Normalize the call: the "type" is read from the URL, not a POST parameter
if (req.urlParsed.file === _const.IMPLICIT_WAIT) { if (req.urlParsed.file === _const.IMPLICIT_WAIT) {
postObj["type"] = _session.timeoutNames().IMPLICIT; postObj["type"] = _session.timeoutNames.IMPLICIT;
} else if (req.urlParsed.file === _const.ASYNC_SCRIPT) { } else if (req.urlParsed.file === _const.ASYNC_SCRIPT) {
postObj["type"] = _session.timeoutNames().ASYNC_SCRIPT; postObj["type"] = _session.timeoutNames.ASYNC_SCRIPT;
} }
if (typeof(postObj["type"]) !== "undefined" && typeof(postObj["ms"]) !== "undefined") { if (typeof(postObj["type"]) !== "undefined" && typeof(postObj["ms"]) !== "undefined") {
_session.setTimeout(postObj["type"], postObj["ms"]); // Set the right timeout on the Session
switch(postObj["type"]) {
case _session.timeoutNames.SCRIPT:
_session.setScriptTimeout(postObj["ms"]);
break;
case _session.timeoutNames.ASYNC_SCRIPT:
_session.setAsyncScriptTimeout(postObj["ms"]);
break;
case _session.timeoutNames.IMPLICIT:
_session.setImplicitTimeout(postObj["ms"]);
break;
case _session.timeoutNames.PAGE_LOAD:
_session.setPageLoadTimeout(postObj["ms"]);
break;
}
res.success(_session.getId()); res.success(_session.getId());
} else { } else {
throw _errors.createInvalidReqMissingCommandParameterEH(req); throw _errors.createInvalidReqMissingCommandParameterEH(req);

View File

@ -34,10 +34,10 @@ ghostdriver.ShutdownReqHand = function() {
_protoParent = ghostdriver.ShutdownReqHand.prototype, _protoParent = ghostdriver.ShutdownReqHand.prototype,
_handle = function(req, res) { _handle = function(req, res) {
_protoParent.handle.call(this, req, res); _protoParent.handle.call(this, req, res);
if (req.method === "GET" && req.urlParsed.file === "shutdown") { // Any HTTP Request Method will be accepted for this command. Some drivers like HEAD for example...
//res.success(null, null); if (req.urlParsed.file === "shutdown") {
res.statusCode = 200; res.statusCode = 200;
res.setHeader("Content-Type", "text/html;charset=UTF-8"); res.setHeader("Content-Type", "text/html;charset=UTF-8");
res.setHeader("Content-Length", 36); res.setHeader("Content-Length", 36);

View File

@ -348,7 +348,8 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
require("./webdriver_atoms.js").get("execute_script"), require("./webdriver_atoms.js").get("execute_script"),
"return arguments[0].isSameNode(arguments[1]);", "return arguments[0].isSameNode(arguments[1]);",
[_getJSON(), _getJSON(req.urlParsed.file)]); [_getJSON(), _getJSON(req.urlParsed.file)]);
res.success(_session.getId(), result);
res.respondBasedOnResult(_session, req, result);
return; return;
} }
@ -378,7 +379,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
} else { } else {
_errors.handleFailedCommandEH( _errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.UNKNOWN_ERROR, _errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
"Submit succeded but Load Failed", "Submit succeeded but Load Failed",
req, req,
res, res,
_session, _session,
@ -401,12 +402,72 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
}); });
}, },
_postClickCommand = function(req, res) { _canCausePageLoadOnClick = function(currWindow) {
var result = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate( var tagName = _getTagName(currWindow).toLowerCase(),
require("./webdriver_atoms.js").get("click"), href = _getAttribute(currWindow, "href"),
_getJSON()); type = _getAttribute(currWindow, "type").toLowerCase();
res.respondBasedOnResult(_session, req, result); // Return "true" if it's an element that "can cause a page load if clicked"
if (tagName === "a" && !!href && href.length > 0) {
return true;
}
if (tagName === "input" && type === "submit") {
return true;
}
return false;
},
_postClickCommand = function(req, res) {
var currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req),
clickRes,
abortCallback = false;
if (_canCausePageLoadOnClick(currWindow)) {
// Clicking on Current Element can cause a page load, hence we need to wait for it to happen
currWindow.execFuncAndWaitForLoad(function() {
// do the click
clickRes = currWindow.evaluate(require("./webdriver_atoms.js").get("click"), _getJSON());
// If Click was NOT positive, status will be set to something else than '0'
clickRes = JSON.parse(clickRes);
if (clickRes && clickRes.status !== 0) {
abortCallback = true; //< handling the error here
res.respondBasedOnResult(_session, req, clickRes);
}
}, function(status) { //< onLoadFinished
// Report about the Load, only if it was not already handled
if (!abortCallback) {
if (status === "success") {
res.success(_session.getId());
} else {
_errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
"Click succeeded but Load Failed",
req,
res,
_session,
"WebElementReqHand");
}
}
}, function() {
if (arguments.length === 0) { //< onTimeout
// onclick didn't bubble up, but we should still return success
res.success(_session.getId());
} else { //< onError
_errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
"Click failed: " + arguments[0],
req,
res,
_session,
"WebElementReqHand");
}
});
} else {
// By default, clicking on this element can't cause a Page Load: we are done after having clicked
clickRes = currWindow.evaluate(require("./webdriver_atoms.js").get("click"), _getJSON());
res.respondBasedOnResult(_session, req, clickRes);
}
}, },
_getSelectedCommand = function(req, res) { _getSelectedCommand = function(req, res) {

View File

@ -76,11 +76,15 @@ ghostdriver.Session = function(desiredCapabilities) {
_defaultCapabilities.proxy : _defaultCapabilities.proxy :
desiredCapabilities.proxy desiredCapabilities.proxy
}, },
// 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
_timeouts = { _timeouts = {
"script" : 500, //< 0.5s "script" : _max32bitInt,
"async script" : 5000, //< 5s "async script" : _max32bitInt,
"implicit" : 0, //< 0s "implicit" : 0, //< 0s
"page load" : 10000 //< 10s "page load" : _max32bitInt,
}, },
_const = { _const = {
TIMEOUT_NAMES : { TIMEOUT_NAMES : {
@ -95,7 +99,23 @@ ghostdriver.Session = function(desiredCapabilities) {
_currentWindowHandle = null, _currentWindowHandle = null,
_id = require("./third_party/uuid.js").v1(), _id = require("./third_party/uuid.js").v1(),
_inputs = ghostdriver.Inputs(), _inputs = ghostdriver.Inputs(),
_capsPageSettingsPref = "phantomjs.page.settings.",
_pageSettings = {},
k, settingKey;
// Searching for `phantomjs.settings.*` in the Desired Capabilities and merging with the Negotiated Capabilities
// Possible values: @see https://github.com/ariya/phantomjs/wiki/API-Reference#wiki-webpage-settings.
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];
}
}
}
var
/** /**
* Executes a function and waits for Load to happen. * Executes a function and waits for Load to happen.
* *
@ -110,6 +130,7 @@ ghostdriver.Session = function(desiredCapabilities) {
var args = Array.prototype.splice.call(arguments, 0), var args = Array.prototype.splice.call(arguments, 0),
timer, timer,
loadingNewPage = false, loadingNewPage = false,
pageLoadNotTriggered = true,
thisPage = this; thisPage = this;
// Normalize "execTypeOpt" value // Normalize "execTypeOpt" value
@ -139,10 +160,12 @@ ghostdriver.Session = function(desiredCapabilities) {
// callback. // callback.
this.setOneShotCallback("onLoadStarted", function () { this.setOneShotCallback("onLoadStarted", function () {
// console.log("onLoadStarted"); // console.log("onLoadStarted");
pageLoadNotTriggered = false;
loadingNewPage = true; loadingNewPage = true;
}); });
this.setOneShotCallback("onUrlChanged", function () { this.setOneShotCallback("onUrlChanged", function () {
// console.log("onUrlChanged"); // console.log("onUrlChanged");
pageLoadNotTriggered = false;
// If "not loading a new page" it's just a fragment change // If "not loading a new page" it's just a fragment change
// and we should call "onLoadFunc()" // and we should call "onLoadFunc()"
@ -168,6 +191,7 @@ ghostdriver.Session = function(desiredCapabilities) {
// message += " in " + item["function"]; // message += " in " + item["function"];
// console.log(" " + message); // console.log(" " + message);
// }); // });
pageLoadNotTriggered = false;
thisPage.stop(); //< stop the page from loading thisPage.stop(); //< stop the page from loading
clearTimeout(timer); clearTimeout(timer);
@ -177,12 +201,13 @@ ghostdriver.Session = function(desiredCapabilities) {
}); });
// Starting timer // Starting timer
// console.log("Setting timer to: " + _getPageLoadTimeout());
timer = setTimeout(function() { timer = setTimeout(function() {
thisPage.stop(); //< stop the page from loading thisPage.stop(); //< stop the page from loading
thisPage.resetOneShotCallbacks(); thisPage.resetOneShotCallbacks();
onErrorFunc.apply(thisPage, arguments); onErrorFunc.apply(thisPage, arguments);
}, _getTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD)); }, _getPageLoadTimeout());
// We are ready to execute // We are ready to execute
if (execTypeOpt === "eval") { if (execTypeOpt === "eval") {
@ -192,6 +217,15 @@ ghostdriver.Session = function(desiredCapabilities) {
// "Apply" the provided function // "Apply" the provided function
func.apply(this, args); func.apply(this, args);
} }
// In case a Page Load is not triggered at all (within 0.5s), we assume it's done and move on
setTimeout(function() {
if (pageLoadNotTriggered) {
clearTimeout(timer);
thisPage.resetOneShotCallbacks();
onLoadFunc.call(thisPage, "success");
}
}, 500);
}, },
_oneShotCallbackFactory = function(page, callbackName) { _oneShotCallbackFactory = function(page, callbackName) {
@ -239,6 +273,8 @@ ghostdriver.Session = function(desiredCapabilities) {
}, },
_decorateNewWindow = function(page) { _decorateNewWindow = function(page) {
var k;
// Decorating: // Decorating:
// 0. Pages lifetime will be managed by Driver, not the pages // 0. Pages lifetime will be managed by Driver, not the pages
page.ownsPages = false; page.ownsPages = false;
@ -258,8 +294,16 @@ ghostdriver.Session = function(desiredCapabilities) {
page.onPageCreated = _addNewPage; page.onPageCreated = _addNewPage;
// 5. Remove every closing page // 5. Remove every closing page
page.onClosing = _deleteClosingPage; page.onClosing = _deleteClosingPage;
// 6. Applying Page settings received via capabilities
for (k in _pageSettings) {
// Apply setting only if really supported by PhantomJS
if (p.settings.hasOwnProperty(k)) {
page.settings[k] = _pageSettings[k];
}
}
// page.onConsoleMessage = function(msg) { console.log(msg); }; // page.onConsoleMessage = function(msg) { console.log(msg); };
// console.log("New Window/Page settings: " + JSON.stringify(page.settings, null, " "));
return page; return page;
}, },
@ -284,14 +328,21 @@ ghostdriver.Session = function(desiredCapabilities) {
return page; return page;
}, },
_getCurrentWindow = function() { _initCurrentWindowIfNull = function() {
var page = null; // Ensure a Current Window is available, if it's found to be `null`
if (_currentWindowHandle === null) { if (_currentWindowHandle === null) {
// First call to get the current window: need to create one // First call to get the current window: need to create one
page = _decorateNewWindow(require("webpage").create()); page = _decorateNewWindow(require("webpage").create());
_currentWindowHandle = page.windowHandle; _currentWindowHandle = page.windowHandle;
_windows[_currentWindowHandle] = page; _windows[_currentWindowHandle] = page;
} else if (_windows.hasOwnProperty(_currentWindowHandle)) { }
},
_getCurrentWindow = function() {
var page = null;
_initCurrentWindowIfNull();
if (_windows.hasOwnProperty(_currentWindowHandle)) {
page = _windows[_currentWindowHandle]; page = _windows[_currentWindowHandle];
} }
@ -306,6 +357,8 @@ ghostdriver.Session = function(desiredCapabilities) {
if (page !== null) { if (page !== null) {
// Switch current window and return "true" // Switch current window and return "true"
_currentWindowHandle = page.windowHandle; _currentWindowHandle = page.windowHandle;
// Switch to the Main Frame of that window
page.switchToMainFrame();
return true; return true;
} }
@ -353,15 +406,48 @@ ghostdriver.Session = function(desiredCapabilities) {
}, },
_setTimeout = function(type, ms) { _setTimeout = function(type, ms) {
_timeouts[type] = ms; // In case the chosen timeout is less than 0, we reset it to `_max32bitInt`
if (ms < 0) {
_timeouts[type] = _max32bitInt;
} else {
_timeouts[type] = ms;
}
}, },
_getTimeout = function(type) { _getTimeout = function(type) {
return _timeouts[type]; return _timeouts[type];
}, },
_timeoutNames = function() { _getScriptTimeout = function() {
return _const.TIMEOUT_NAMES; return _getTimeout(_const.TIMEOUT_NAMES.SCRIPT);
},
_getAsyncScriptTimeout = function() {
return _getTimeout(_const.TIMEOUT_NAMES.ASYNC_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);
},
_setAsyncScriptTimeout = function(ms) {
_setTimeout(_const.TIMEOUT_NAMES.ASYNC_SCRIPT, ms);
},
_setImplicitTimeout = function(ms) {
_setTimeout(_const.TIMEOUT_NAMES.IMPLICIT, ms);
},
_setPageLoadTimeout = function(ms) {
_setTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD, ms);
}, },
_aboutToDelete = function() { _aboutToDelete = function() {
@ -389,14 +475,21 @@ ghostdriver.Session = function(desiredCapabilities) {
getWindow : _getWindow, getWindow : _getWindow,
closeWindow : _closeWindow, closeWindow : _closeWindow,
getWindowsCount : _getWindowsCount, getWindowsCount : _getWindowsCount,
initCurrentWindowIfNull : _initCurrentWindowIfNull,
getCurrentWindowHandle : _getCurrentWindowHandle, getCurrentWindowHandle : _getCurrentWindowHandle,
getWindowHandles: _getWindowHandles, getWindowHandles : _getWindowHandles,
isValidWindowHandle: _isValidWindowHandle, isValidWindowHandle : _isValidWindowHandle,
aboutToDelete : _aboutToDelete, aboutToDelete : _aboutToDelete,
setTimeout : _setTimeout, inputs : _inputs,
getTimeout : _getTimeout, setScriptTimeout : _setScriptTimeout,
timeoutNames : _timeoutNames, setAsyncScriptTimeout : _setAsyncScriptTimeout,
inputs: _inputs setImplicitTimeout : _setImplicitTimeout,
setPageLoadTimeout : _setPageLoadTimeout,
getScriptTimeout : _getScriptTimeout,
getAsyncScriptTimeout : _getAsyncScriptTimeout,
getImplicitTimeout : _getImplicitTimeout,
getPageLoadTimeout : _getPageLoadTimeout,
timeoutNames : _const.TIMEOUT_NAMES
}; };
}; };

View File

@ -207,7 +207,7 @@ ghostdriver.WebElementLocator = function(session) {
elementOrElements.hasOwnProperty("value")) { elementOrElements.hasOwnProperty("value")) {
// return if elements found OR we passed the "stopSearchByTime" // return if elements found OR we passed the "stopSearchByTime"
stopSearchByTime = searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT); stopSearchByTime = searchStartTime + _session.getImplicitTimeout();
if (elementOrElements.value.length !== 0 || new Date().getTime() > stopSearchByTime) { if (elementOrElements.value.length !== 0 || new Date().getTime() > stopSearchByTime) {
res.success(_session.getId(), elementOrElements.value); res.success(_session.getId(), elementOrElements.value);
return; return;
@ -215,7 +215,7 @@ ghostdriver.WebElementLocator = function(session) {
} }
// retry if we haven't passed "stopSearchByTime" // retry if we haven't passed "stopSearchByTime"
stopSearchByTime = searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT); stopSearchByTime = searchStartTime + _session.getImplicitTimeout();
if (stopSearchByTime >= new Date().getTime()) { if (stopSearchByTime >= new Date().getTime()) {
// Recursive call in 50ms // Recursive call in 50ms
setTimeout(function(){ setTimeout(function(){