Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Importing new GhostDriver 1.0.3.

See the https://github.com/detro/ghostdriver/ project for details.
  • Loading branch information...
commit 78d90641df12d10b1f30b2bb4c08b92d6aff5f9b 1 parent 005db03
@detro authored
Showing with 5,205 additions and 2,508 deletions.
  1. +59 −18 src/config.cpp
  2. +12 −1 src/config.h
  3. +2 −0  src/consts.h
  4. +100 −0 src/ghostdriver/config.js
  5. +3 −0  src/ghostdriver/ghostdriver.qrc
  6. +34 −31 src/ghostdriver/hub_register.js
  7. +11 −9 src/ghostdriver/inputs.js
  8. +11 −5 src/ghostdriver/lastupdate
  9. +109 −0 src/ghostdriver/logger.js
  10. +25 −27 src/ghostdriver/main.js
  11. +11 −8 src/ghostdriver/request_handlers/router_request_handler.js
  12. +14 −7 src/ghostdriver/request_handlers/session_manager_request_handler.js
  13. +115 −47 src/ghostdriver/request_handlers/session_request_handler.js
  14. +3 −0  src/ghostdriver/request_handlers/shutdown_request_handler.js
  15. +5 −3 src/ghostdriver/request_handlers/status_request_handler.js
  16. +78 −95 src/ghostdriver/request_handlers/webelement_request_handler.js
  17. +134 −108 src/ghostdriver/session.js
  18. +287 −0 src/ghostdriver/third_party/console++.js
  19. +48 −47 src/ghostdriver/third_party/webdriver-atoms/active_element.js
  20. +126 −124 src/ghostdriver/third_party/webdriver-atoms/clear.js
  21. +15 −15 src/ghostdriver/third_party/webdriver-atoms/clear_local_storage.js
  22. +15 −15 src/ghostdriver/third_party/webdriver-atoms/clear_session_storage.js
  23. +137 −133 src/ghostdriver/third_party/webdriver-atoms/click.js
  24. +46 −45 src/ghostdriver/third_party/webdriver-atoms/default_content.js
  25. +2,776 −753 src/ghostdriver/third_party/webdriver-atoms/deps.js
  26. +118 −115 src/ghostdriver/third_party/webdriver-atoms/double_click.js
  27. +117 −114 src/ghostdriver/third_party/webdriver-atoms/drag.js
  28. +16 −15 src/ghostdriver/third_party/webdriver-atoms/execute_async_script.js
  29. +13 −11 src/ghostdriver/third_party/webdriver-atoms/execute_script.js
  30. +15 −15 src/ghostdriver/third_party/webdriver-atoms/execute_sql.js
  31. +84 −84 src/ghostdriver/third_party/webdriver-atoms/find_element.js
  32. +84 −84 src/ghostdriver/third_party/webdriver-atoms/find_elements.js
  33. +99 −97 src/ghostdriver/third_party/webdriver-atoms/focus_on_element.js
  34. +85 −85 src/ghostdriver/third_party/webdriver-atoms/frame_by_id_or_name.js
  35. +47 −46 src/ghostdriver/third_party/webdriver-atoms/frame_by_index.js
  36. +14 −14 src/ghostdriver/third_party/webdriver-atoms/get_appcache_status.js
  37. +50 −53 src/ghostdriver/third_party/webdriver-atoms/get_attribute.js
  38. +54 −55 src/ghostdriver/third_party/webdriver-atoms/get_attribute_value.js
  39. +7 −7 src/ghostdriver/third_party/webdriver-atoms/get_current_position.js
  40. +8 −7 src/ghostdriver/third_party/webdriver-atoms/get_element_from_cache.js
  41. +48 −47 src/ghostdriver/third_party/webdriver-atoms/get_frame_window.js
  42. +41 −40 src/ghostdriver/third_party/webdriver-atoms/get_in_view_location.js
  43. +15 −15 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_item.js
  44. +15 −15 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_keys.js
  45. +15 −15 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_size.js
  46. +8 −8 src/ghostdriver/third_party/webdriver-atoms/get_location.js
  47. +46 −45 src/ghostdriver/third_party/webdriver-atoms/get_location_in_view.js
  48. +15 −15 src/ghostdriver/third_party/webdriver-atoms/get_session_storage_item.js
  49. +15 −15 src/ghostdriver/third_party/webdriver-atoms/get_session_storage_keys.js
Sorry, we could not display the entire diff because it was too big.
View
77 src/config.cpp
@@ -40,9 +40,11 @@
#include "terminal.h"
#include "qcommandline.h"
#include "utils.h"
+#include "consts.h"
#include <iostream>
+
static const struct QCommandLineConfigEntry flags[] =
{
{ QCommandLine::Option, '\0', "cookies-file", "Sets the file name to store the persistent cookies", QCommandLine::Optional },
@@ -65,9 +67,12 @@ static const struct QCommandLineConfigEntry flags[] =
{ QCommandLine::Option, '\0', "web-security", "Enables web security, 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-protocol", "Sets the SSL protocol (supported protocols: 'SSLv3' (default), 'SSLv2', 'TLSv1', 'any')", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver", "Starts in 'Remote WebDriver mode' (embedded GhostDriver): '[[<IP>:]<PORT>]' (default '127.0.0.1:8910') ", QCommandLine::Optional },
- { QCommandLine::Option, '\0', "webdriver-selenium-grid-hub", "URL to the Selenium Grid HUB: 'URL_TO_HUB' (default 'none') (NOTE: works only together with '--webdriver') ", QCommandLine::Optional },
+ { QCommandLine::Option, '\0', "webdriver-logfile", "File where to write the WebDriver's Log (default 'none') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
+ { QCommandLine::Option, '\0', "webdriver-loglevel", "WebDriver Logging Level: (supported: 'ERROR', 'WARN', 'INFO', 'DEBUG') (default 'INFO') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
+ { QCommandLine::Option, '\0', "webdriver-selenium-grid-hub", "URL to the Selenium Grid HUB: 'URL_TO_HUB' (default 'none') (NOTE: needs '--webdriver') ", QCommandLine::Optional },
{ QCommandLine::Param, '\0', "script", "Script", QCommandLine::Flags(QCommandLine::Optional|QCommandLine::ParameterFence)},
{ QCommandLine::Param, '\0', "argument", "Script argument", QCommandLine::OptionalMultiple },
+ { QCommandLine::Switch, 'w', "wd", "Equivalent to '--webdriver' option above", QCommandLine::Optional },
{ QCommandLine::Switch, 'h', "help", "Shows this message and quits", QCommandLine::Optional },
{ QCommandLine::Switch, 'v', "version", "Prints out PhantomJS version", QCommandLine::Optional },
QCOMMANDLINE_CONFIG_ENTRY_END
@@ -107,12 +112,22 @@ void Config::processArgs(const QStringList &args)
if (isWebdriverMode()) {
QStringList argsForGhostDriver;
- m_scriptFile = "main.js"; //< launch script
- argsForGhostDriver << m_webdriver; //< ip:port
+ m_scriptFile = "main.js"; //< launch script
+
+ argsForGhostDriver << QString("--ip=%1").arg(m_webdriverIp); //< "--ip=IP"
+ argsForGhostDriver << QString("--port=%1").arg(m_webdriverPort); //< "--port=PORT"
+
if (!m_webdriverSeleniumGridHub.isEmpty()) {
- argsForGhostDriver << m_webdriverSeleniumGridHub; //< selenium grid url
+ argsForGhostDriver << QString("--hub=%1").arg(m_webdriverSeleniumGridHub); //< "--hub=SELENIUM_GRID_HUB_URL"
}
+ if (!m_webdriverLogFile.isEmpty()) {
+ argsForGhostDriver << QString("--logFile=%1").arg(m_webdriverLogFile); //< "--logFile=LOG_FILE"
+ argsForGhostDriver << "--logColor=false"; //< Force no-color-output in Log File
+ }
+
+ argsForGhostDriver << QString("--logLevel=%1").arg(m_webdriverLogLevel); //< "--logLevel=LOG_LEVEL"
+
// Clear current args and override with those
setScriptArgs(argsForGhostDriver);
}
@@ -443,34 +458,47 @@ bool Config::javascriptCanCloseWindows() const
void Config::setWebdriver(const QString &webdriverConfig)
{
- // This option can be provided empty: in that case we should use the default IP:PORT configuration
- QString ip = "127.0.0.1";
- QString port = "8910";
-
// Parse and validate the configuration
bool isValidPort;
QStringList wdCfg = webdriverConfig.split(':');
if (wdCfg.length() == 1 && wdCfg[0].toInt(&isValidPort) && isValidPort) {
// Only a PORT was provided
- port = wdCfg[0];
+ m_webdriverPort = wdCfg[0];
} else if(wdCfg.length() == 2 && !wdCfg[0].isEmpty() && wdCfg[1].toInt(&isValidPort) && isValidPort) {
// Both IP and PORT provided
- ip = wdCfg[0];
- port = wdCfg[1];
+ m_webdriverIp = wdCfg[0];
+ m_webdriverPort = wdCfg[1];
}
-
- // Setting the "webdriver" configuration
- m_webdriver = QString("%1:%2").arg(ip).arg(port);
}
QString Config::webdriver() const
{
- return m_webdriver;
+ return QString("%1:%2").arg(m_webdriverIp).arg(m_webdriverPort);
}
bool Config::isWebdriverMode() const
{
- return !m_webdriver.isEmpty();
+ return !m_webdriverPort.isEmpty();
+}
+
+void Config::setWebdriverLogFile(const QString& webdriverLogFile)
+{
+ m_webdriverLogFile = webdriverLogFile;
+}
+
+QString Config::webdriverLogFile() const
+{
+ return m_webdriverLogFile;
+}
+
+void Config::setWebdriverLogLevel(const QString& webdriverLogLevel)
+{
+ m_webdriverLogLevel = webdriverLogLevel;
+}
+
+QString Config::webdriverLogLevel() const
+{
+ return m_webdriverLogLevel;
}
void Config::setWebdriverSeleniumGridHub(const QString &hubUrl)
@@ -514,7 +542,10 @@ void Config::resetToDefaults()
m_helpFlag = false;
m_printDebugMessages = false;
m_sslProtocol = "sslv3";
- m_webdriver = QString();
+ m_webdriverIp = QString();
+ m_webdriverPort = QString();
+ m_webdriverLogFile = QString();
+ m_webdriverLogLevel = "INFO";
m_webdriverSeleniumGridHub = QString();
}
@@ -562,6 +593,10 @@ void Config::handleSwitch(const QString &sw)
{
setHelpFlag(sw == "help");
setVersionFlag(sw == "version");
+
+ if (sw == "wd") {
+ setWebdriver(DEFAULT_WEBDRIVER_CONFIG);
+ }
}
void Config::handleOption(const QString &option, const QVariant &value)
@@ -660,7 +695,13 @@ void Config::handleOption(const QString &option, const QVariant &value)
setSslProtocol(value.toString());
}
if (option == "webdriver") {
- setWebdriver(value.toString());
+ setWebdriver(value.toString().length() > 0 ? value.toString() : DEFAULT_WEBDRIVER_CONFIG);
+ }
+ if (option == "webdriver-logfile") {
+ setWebdriverLogFile(value.toString());
+ }
+ if (option == "webdriver-loglevel") {
+ setWebdriverLogLevel(value.toString());
}
if (option == "webdriver-selenium-grid-hub") {
setWebdriverSeleniumGridHub(value.toString());
View
13 src/config.h
@@ -59,6 +59,8 @@ class Config: public QObject
Q_PROPERTY(bool javascriptCanCloseWindows READ javascriptCanCloseWindows WRITE setJavascriptCanCloseWindows)
Q_PROPERTY(QString sslProtocol READ sslProtocol WRITE setSslProtocol)
Q_PROPERTY(QString webdriver READ webdriver WRITE setWebdriver)
+ Q_PROPERTY(QString webdriverLogFile READ webdriverLogFile WRITE setWebdriverLogFile)
+ Q_PROPERTY(QString webdriverLogLevel READ webdriverLogLevel WRITE setWebdriverLogLevel)
Q_PROPERTY(QString webdriverSeleniumGridHub READ webdriverSeleniumGridHub WRITE setWebdriverSeleniumGridHub)
public:
@@ -158,6 +160,12 @@ class Config: public QObject
QString webdriver() const;
bool isWebdriverMode() const;
+ void setWebdriverLogFile(const QString& webdriverLogFile);
+ QString webdriverLogFile() const;
+
+ void setWebdriverLogLevel(const QString& webdriverLogLevel);
+ QString webdriverLogLevel() const;
+
void setWebdriverSeleniumGridHub(const QString& hubUrl);
QString webdriverSeleniumGridHub() const;
@@ -205,7 +213,10 @@ public slots:
bool m_javascriptCanOpenWindows;
bool m_javascriptCanCloseWindows;
QString m_sslProtocol;
- QString m_webdriver;
+ QString m_webdriverIp;
+ QString m_webdriverPort;
+ QString m_webdriverLogFile;
+ QString m_webdriverLogLevel;
QString m_webdriverSeleniumGridHub;
};
View
2  src/consts.h
@@ -62,4 +62,6 @@
#define PAGE_SETTINGS_JS_CAN_OPEN_WINDOWS "javascriptCanOpenWindows"
#define PAGE_SETTINGS_JS_CAN_CLOSE_WINDOWS "javascriptCanCloseWindows"
+#define DEFAULT_WEBDRIVER_CONFIG "127.0.0.1:8910"
+
#endif // CONSTS_H
View
100 src/ghostdriver/config.js
@@ -0,0 +1,100 @@
+/*
+This file is part of the GhostDriver by Ivan De Marino <http://ivandemarino.me>.
+
+Copyright (c) 2012, Ivan De Marino <http://ivandemarino.me>
+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.
+*/
+
+// Default configuration
+var defaultConfig = {
+ "ip" : "127.0.0.1",
+ "port" : "8910",
+ "hub" : null,
+ "logFile" : null,
+ "logLevel" : "INFO",
+ "logColor" : false
+ },
+ config = {
+ "ip" : defaultConfig.ip,
+ "port" : defaultConfig.port,
+ "hub" : defaultConfig.hub,
+ "logFile" : defaultConfig.logFile,
+ "logLevel" : defaultConfig.logLevel,
+ "logColor" : defaultConfig.logColor
+ },
+ logOutputFile = null,
+ logger = require("./logger.js"),
+ _log = logger.create("Config");
+
+function apply () {
+ // Normalise and Set Console Logging Level
+ config.logLevel = config.logLevel.toUpperCase();
+ if (!logger.console.LEVELS.hasOwnProperty(config.logLevel)) {
+ config.logLevel = defaultConfig.logLevel;
+ }
+ logger.console.setLevel(logger.console.LEVELS[config.logLevel]);
+
+ // Normalise and Set Console Color
+ try {
+ config.logColor = JSON.parse(config.logColor);
+ } catch (e) {
+ config.logColor = defaultConfig.logColor;
+ }
+ if (config.logColor) {
+ logger.console.enableColor();
+ } else {
+ logger.console.disableColor();
+ }
+
+ // Add a Log File (if any)
+ if (config.logFile !== null) {
+ logger.addLogFile(config.logFile);
+ }
+}
+
+exports.init = function(cliArgs) {
+ var i, k,
+ regexp = new RegExp("^--([a-z]+)=([a-z0-9_/\\\\:.]+)$", "i"),
+ regexpRes;
+
+ // Loop over all the Command Line Arguments
+ // If any of the form '--param=value' is found, it's compared against
+ // the 'config' object to see if 'config.param' exists.
+ for (i = cliArgs.length -1; i >= 1; --i) {
+ // Apply Regular Expression
+ regexpRes = regexp.exec(cliArgs[i]);
+ if (regexpRes !== null && regexpRes.length === 3 &&
+ config.hasOwnProperty(regexpRes[1])) {
+ config[regexpRes[1]] = regexpRes[2];
+ }
+ }
+
+ // Apply/Normalize the Configuration before returning
+ apply();
+
+ _log.debug("init", "Configuration => " + JSON.stringify(config));
+};
+
+exports.get = function() {
+ return config;
+};
View
3  src/ghostdriver/ghostdriver.qrc
@@ -1,8 +1,10 @@
<RCC>
<qresource prefix="ghostdriver/">
+ <file>config.js</file>
<file>errors.js</file>
<file>hub_register.js</file>
<file>inputs.js</file>
+ <file>logger.js</file>
<file>main.js</file>
<file>request_handlers/request_handler.js</file>
<file>request_handlers/router_request_handler.js</file>
@@ -12,6 +14,7 @@
<file>request_handlers/status_request_handler.js</file>
<file>request_handlers/webelement_request_handler.js</file>
<file>session.js</file>
+ <file>third_party/console++.js</file>
<file>third_party/parseuri.js</file>
<file>third_party/uuid.js</file>
<file>third_party/webdriver-atoms/active_element.js</file>
View
65 src/ghostdriver/hub_register.js
@@ -27,40 +27,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/* generate node configuration for this node */
var nodeconf = function(ip, port, hub) {
- var ref$, hubHost, hubPort;
+ var ref$, hubHost, hubPort;
- ref$ = hub.match(/([\w\d\.]+):(\d+)/);
- hubHost = ref$[1];
- hubPort = +ref$[2]; //< ensure it's of type "number"
+ ref$ = hub.match(/([\w\d\.]+):(\d+)/);
+ hubHost = ref$[1];
+ hubPort = +ref$[2]; //< ensure it's of type "number"
- return {
- capabilities: [{
- browserName: "phantomjs",
- maxInstances: 1,
- seleniumProtocol: "WebDriver"
- }],
- configuration: {
- hub: hub,
- hubHost: hubHost,
- hubPort: hubPort,
- port: port,
- proxy: "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
- // Note that multiple webdriver sessions or instances within a single
- // Ghostdriver process will interact in unexpected and undesirable ways.
- maxSession: 1,
- register: true,
- registerCycle: 5000,
- role: "wd",
- url: "http://" + ip + ":" + port,
- remoteHost: "http://" + ip + ":" + port
- }
- };
-};
+ return {
+ capabilities: [{
+ browserName: "phantomjs",
+ maxInstances: 1,
+ seleniumProtocol: "WebDriver"
+ }],
+ configuration: {
+ hub: hub,
+ hubHost: hubHost,
+ hubPort: hubPort,
+ port: port,
+ proxy: "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
+ // Note that multiple webdriver sessions or instances within a single
+ // Ghostdriver process will interact in unexpected and undesirable ways.
+ maxSession: 1,
+ register: true,
+ registerCycle: 5000,
+ role: "wd",
+ url: "http://" + ip + ":" + port,
+ remoteHost: "http://" + ip + ":" + port
+ }
+ };
+ },
+ _log = require("./logger.js").create("HUB Register");
module.exports = {
register: function(ip, port, hub) {
+ var page;
+
try {
- var page = require('webpage').create();
+ page = require('webpage').create();
port = +port; //< ensure it's of type "number"
if(!hub.match(/\/$/)) {
hub += '/';
@@ -75,14 +78,14 @@ module.exports = {
}
}, function(status) {
if(status !== 'success') {
- console.error("Unable to contact grid " + hub + ": " + status);
+ _log.error("register", "Unable to contact grid " + hub + ": " + status);
phantom.exit(1);
}
if(page.framePlainText !== "ok") {
- console.error("Problem registering with grid " + hub + ": " + page.content);
+ _log.error("register", "Problem registering with grid " + hub + ": " + page.content);
phantom.exit(1);
}
- console.log("Registered with grid hub: " + hub + " (" + page.framePlainText + ")");
+ _log.info("register", "Registered with grid hub: " + hub + " (" + page.framePlainText + ")");
});
} catch (e) {
throw new Error("Could not register to Selenium Grid Hub: " + hub);
View
20 src/ghostdriver/inputs.js
@@ -29,9 +29,7 @@ var ghostdriver = ghostdriver || {};
ghostdriver.Inputs = function () {
// private:
- var
- _mousePos = { x: 0, y: 0 },
- _keyboardState = {},
+ const
_specialKeys = {
'\uE000': "Escape", // NULL
'\uE001': "Cancel", // Cancel
@@ -40,7 +38,7 @@ ghostdriver.Inputs = function () {
'\uE004': "Tab", // Tab
'\uE005': "Clear", // Clear
'\uE006': "\n",
- '\uE007': "\n",
+ '\uE007': "Enter",
'\uE008': "Shift", // Shift
'\uE009': "Control", // Control
'\uE00A': "Alt", // Alt
@@ -196,8 +194,11 @@ ghostdriver.Inputs = function () {
"ALT": 0x08000000, // An Alt key on the keyboard is pressed.
"META": 0x10000000, // A Meta key on the keyboard is pressed.
"NUMPAD": 0x20000000 // Keypad key.
- },
+ };
+ var
+ _mousePos = { x: 0, y: 0 },
+ _keyboardState = {},
_currentModifierKeys = 0,
_isModifierKey = function (key) {
@@ -261,12 +262,13 @@ ghostdriver.Inputs = function () {
},
_translateKey = function (session, key) {
- var actualKey = key;
- var phantomjskeys = session.getCurrentWindow().event.key;
+ var
+ actualKey = key,
+ phantomjskeys = session.getCurrentWindow().event.key;
if (_specialKeys.hasOwnProperty(key)) {
actualKey = _specialKeys[key];
- if (session.getCurrentWindow().event.key.hasOwnProperty(actualKey)) {
- actualKey = session.getCurrentWindow().event.key[actualKey];
+ if (phantomjskeys.hasOwnProperty(actualKey)) {
+ actualKey = phantomjskeys[actualKey];
}
}
return actualKey;
View
16 src/ghostdriver/lastupdate
@@ -1,9 +1,15 @@
-2012-12-19 18:36:40
+2013-03-18 01:28:52
-commit b13d9c164d11e83eab97bb8e49cb50b31262f435 (HEAD, tag: refs/tags/1.0.2, refs/remotes/origin/master, refs/remotes/origin/HEAD, refs/heads/master)
+commit 2792939c149829d03e7ed07cd6f03f5fd7e73ef4 (HEAD, tag: refs/tags/1.0.3, refs/remotes/origin/master, refs/remotes/origin/HEAD, refs/heads/master)
Author: Ivan De Marino <ivan.de.marino@gmail.com>
-Date: Wed Dec 19 18:33:31 2012 +0000
+Date: Mon Mar 18 01:27:12 2013 +0000
- Bumping version to `1.0.2`.
+ Java Binding JAR 1.0.3 now ready.
- Also, importing Selenium `2.28.0` in tests.
+ Can be added to a Maven project with:
+
+ <dependency>
+ <groupId>com.github.detro.ghostdriver</groupId>
+ <artifactId>phantomjsdriver</artifactId>
+ <version>1.0.3</version>
+ </dependency>
View
109 src/ghostdriver/logger.js
@@ -0,0 +1,109 @@
+/*
+This file is part of the GhostDriver by Ivan De Marino <http://ivandemarino.me>.
+
+Copyright (c) 2012, Ivan De Marino <http://ivandemarino.me>
+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.
+*/
+
+// Init Console++
+require("./third_party/console++.js");
+
+// Constants
+const
+separator = " - ";
+
+/**
+ * (Super-simple) Logger
+ *
+ * @param context {String} Logger context
+ */
+function Logger (context) {
+ var loggerObj, i;
+
+ if (!context || context.length === 0) {
+ throw new Error("Invalid 'context' for Logger: " + context);
+ }
+
+ loggerObj = {
+ debug : function(scope, message) {
+ console.debug(context + separator +
+ scope +
+ (message && message.length > 0 ? separator + message : "")
+ );
+ },
+ info : function(scope, message) {
+ console.info(context + separator +
+ scope +
+ (message && message.length > 0 ? separator + message : "")
+ );
+ },
+ warn : function(scope, message) {
+ console.warn(context + separator +
+ scope +
+ (message && message.length > 0 ? separator + message : "")
+ );
+ },
+ error : function(scope, message) {
+ console.error(context + separator +
+ scope +
+ (message && message.length > 0 ? separator + message : "")
+ );
+ }
+ };
+
+
+ return loggerObj;
+}
+
+/**
+ * Export: Create Logger with Context
+ *
+ * @param context {String} Context of the new Logger
+ */
+exports.create = function (context) {
+ return new Logger(context);
+};
+
+/**
+ * Export: Add Log File.
+ *
+ * @param logFileName {String Name of the file were to output (append) the Logs.
+ */
+exports.addLogFile = function(logFileName) {
+ var fs = require("fs"),
+ f = fs.open(fs.absolute(logFileName), 'a');
+
+ // Append line to Log File
+ console.onOutput(function(msg, levelName) {
+ f.writeLine(msg);
+ f.flush();
+ });
+
+ // Flush the Log File when process exits
+ phantom.aboutToExit.connect(f.flush);
+};
+
+/**
+ * Export: Console object
+ */
+exports.console = console;
View
52 src/ghostdriver/main.js
@@ -28,21 +28,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Load dependencies
// NOTE: We need to provide PhantomJS with the "require" module ASAP. This is a pretty s**t way to load dependencies
var ghostdriver = {
- system : require('system'),
- hub : require('./hub_register'),
- version : "1.0.2"
+ system : require("system"),
+ hub : require("./hub_register.js"),
+ logger : require("./logger.js"),
+ config : null, //< will be set in a short while
+ version : "1.0.3"
},
- server = require('webserver').create(),
+ server = require("webserver").create(),
router,
- parseURI,
- listenOn,
- listenOnIp = "127.0.0.1",
- listenOnPort = "8910";
+ parseURI = require("./third_party/parseuri.js"),
+ _log = ghostdriver.logger.create("GhostDriver");
+
+// Initialize the configuration
+require("./config.js").init(ghostdriver.system.args);
+ghostdriver.config = require("./config.js").get();
// Enable "strict mode" for the 'parseURI' library
-parseURI = require("./third_party/parseuri.js");
parseURI.options.strictMode = true;
+// Load all the core dependencies
phantom.injectJs("session.js");
phantom.injectJs("inputs.js");
phantom.injectJs("request_handlers/request_handler.js");
@@ -58,30 +62,24 @@ try {
// HTTP Request Router
router = new ghostdriver.RouterReqHand();
- // Check if parameters were given, regarding the "ip:port" to listen to
- if (ghostdriver.system.args[1]) {
- if (ghostdriver.system.args[1].indexOf(':') >= 0) {
- listenOn = ghostdriver.system.args[1].split(':');
- listenOnIp = listenOn[0];
- listenOnPort = listenOn[1];
- } else {
- listenOnPort = ghostdriver.system.args[1];
- }
- }
-
// Start the server
- if (server.listen(listenOnPort, router.handle)) {
- console.log('Ghost Driver running on port ' + server.port);
+ if (server.listen(ghostdriver.config.port, router.handle)) {
+ _log.info("Main", "running on port " + server.port);
- // If parameters regarding a Selenium Grid HUB were given, register to it!
- if (ghostdriver.system.args[2]) {
- ghostdriver.hub.register(listenOnIp, listenOnPort, ghostdriver.system.args[2]);
+ // If a Selenium Grid HUB was provided, register to it!
+ if (ghostdriver.config.hub !== null) {
+ _log.info("Main", "registering to Selenium HUB"+
+ " '" + ghostdriver.config.hub + "'" +
+ " using '" + ghostdriver.config.ip + ":" + ghostdriver.config.port + "'");
+ ghostdriver.hub.register(ghostdriver.config.ip,
+ ghostdriver.config.port,
+ ghostdriver.config.hub);
}
} else {
- throw new Error("ERROR: Could not start Ghost Driver");
+ throw new Error("Could not start Ghost Driver");
phantom.exit(1);
}
} catch (e) {
- console.error(e);
+ _log.error("Main", e.message + " => "+ JSON.stringify(e, null, " "));
phantom.exit(1);
}
View
19 src/ghostdriver/request_handlers/router_request_handler.js
@@ -32,19 +32,22 @@ var ghostdriver = ghostdriver || {};
*/
ghostdriver.RouterReqHand = function() {
// private:
- var
- _protoParent = ghostdriver.RouterReqHand.prototype,
- _statusRH = new ghostdriver.StatusReqHand(),
- _shutdownRH = new ghostdriver.ShutdownReqHand(),
- _sessionManRH = new ghostdriver.SessionManagerReqHand(),
+ const
_const = {
STATUS : "status",
SESSION : "session",
SESSIONS : "sessions",
SESSION_DIR : "/session/",
SHUTDOWN : "shutdown"
- },
+ };
+
+ var
+ _protoParent = ghostdriver.RouterReqHand.prototype,
+ _statusRH = new ghostdriver.StatusReqHand(),
+ _shutdownRH = new ghostdriver.ShutdownReqHand(),
+ _sessionManRH = new ghostdriver.SessionManagerReqHand(),
_errors = _protoParent.errors,
+ _log = ghostdriver.logger.create("RouterReqHand"),
_handle = function(req, res) {
var session,
@@ -53,7 +56,7 @@ ghostdriver.RouterReqHand = function() {
// Invoke parent implementation
_protoParent.handle.call(this, req, res);
- // console.log("Request => " + JSON.stringify(req, null, ' '));
+ _log.debug("_handle", "Request => " + JSON.stringify(req, null, " "));
try {
if (req.urlParsed.directory.match(/^\/wd\/hub/)) {
@@ -84,7 +87,7 @@ ghostdriver.RouterReqHand = function() {
throw _errors.createInvalidReqUnknownCommandEH(req);
}
} catch (e) {
- console.error("Error => " + JSON.stringify(e, null, ' '));
+ _log.error("_handle", "Thrown => " + JSON.stringify(e, null, " "));
if (typeof(e.handle) === "function") {
e.handle(res);
View
21 src/ghostdriver/request_handlers/session_manager_request_handler.js
@@ -34,7 +34,8 @@ ghostdriver.SessionManagerReqHand = function() {
_sessions = {}, //< will store key/value pairs like 'SESSION_ID : SESSION_OBJECT'
_sessionRHs = {},
_errors = _protoParent.errors,
- _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT = 60000,
+ _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT = 300000, // 5 minutes
+ _log = ghostdriver.logger.create("SessionManagerReqHand"),
_handle = function(req, res) {
_protoParent.handle.call(this, req, res);
@@ -59,14 +60,21 @@ ghostdriver.SessionManagerReqHand = function() {
_postNewSessionCommand = function(req, res) {
var newSession,
+ postObj;
+
+ try {
postObj = JSON.parse(req.post);
+ } catch (e) {
+ // If the parsing has failed, the error is reported at the end
+ }
- if (typeof(postObj) === "object") {
+ if (typeof(postObj) === "object" &&
+ typeof(postObj.desiredCapabilities) === "object") {
// Create and store a new Session
newSession = new ghostdriver.Session(postObj.desiredCapabilities);
_sessions[newSession.getId()] = newSession;
- // console.log("New Session Created: " + newSession.getId());
+ _log.info("_postNewSessionCommand", "New Session Created: " + newSession.getId());
// Redirect to the newly created Session
res.statusCode = 303; //< "303 See Other"
@@ -155,19 +163,18 @@ ghostdriver.SessionManagerReqHand = function() {
// Do this cleanup only if there are sessions
if (Object.keys(_sessions).length > 0) {
- console.log("Asynchronous Sessions cleanup phase starting NOW");
+ _log.info("_cleanupWindowlessSessions", "Asynchronous Sessions clean-up phase starting NOW");
for (sId in _sessions) {
if (_sessions[sId].getWindowsCount() === 0) {
- console.log("About to delete Session '"+sId+"', because windowless...");
_deleteSession(sId);
- console.log("... deleted!");
+ _log.info("_cleanupWindowlessSessions", "Deleted Session '"+sId+"', because windowless");
}
}
}
};
// Regularly cleanup un-used sessions
- setInterval(_cleanupWindowlessSessions, _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT); //< every 60s
+ setInterval(_cleanupWindowlessSessions, _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT);
// public:
return {
View
162 src/ghostdriver/request_handlers/session_request_handler.js
@@ -30,10 +30,7 @@ var ghostdriver = ghostdriver || {};
ghostdriver.SessionReqHand = function(session) {
// private:
- var
- _session = session,
- _protoParent = ghostdriver.SessionReqHand.prototype,
- _locator = new ghostdriver.WebElementLocator(session),
+ const
_const = {
URL : "url",
ELEMENT : "element",
@@ -67,16 +64,20 @@ ghostdriver.SessionReqHand = function(session) {
BUTTON_DOWN : "buttondown",
BUTTON_UP : "buttonup",
DOUBLE_CLICK : "doubleclick"
- },
+ };
+
+ var
+ _session = session,
+ _protoParent = ghostdriver.SessionReqHand.prototype,
+ _locator = new ghostdriver.WebElementLocator(session),
_errors = _protoParent.errors,
+ _log = ghostdriver.logger.create("SessionReqHand"),
_handle = function(req, res) {
var element;
_protoParent.handle.call(this, req, res);
- // console.log("Request => " + JSON.stringify(req, null, ' '));
-
// Handle "/url" GET and POST
if (req.urlParsed.file === _const.URL) { //< ".../url"
if (req.method === "GET") {
@@ -184,6 +185,7 @@ ghostdriver.SessionReqHand = function(session) {
_createOnSuccessHandler = function(res) {
return function (status) {
+ _log.debug("_SuccessHandler", "status: " + status);
res.success(_session.getId());
};
},
@@ -193,8 +195,7 @@ ghostdriver.SessionReqHand = function(session) {
command,
targetWindow;
- // console.log("_doWindowHandleCommands");
- // console.log(JSON.stringify(req, null, " "));
+ _log.debug("_doWindowHandleCommands", "req => " + JSON.stringify(req));
// Ensure all the parameters are provided
if (req.urlParsed.chunks.length === 3) {
@@ -334,9 +335,9 @@ ghostdriver.SessionReqHand = function(session) {
currWindow.execFuncAndWaitForLoad(
function() { currWindow.goForward(); },
successHand,
- successHand); //< We don't care if 'back' fails
+ successHand); //< We don't care if 'forward' fails
} else {
- // We can't go back, and that's ok
+ // We can't go forward, and that's ok
successHand();
}
},
@@ -384,11 +385,12 @@ ghostdriver.SessionReqHand = function(session) {
_executeAsyncCommand = function(req, res) {
var postObj = JSON.parse(req.post);
- // console.log("executeAsync - " + JSON.stringify(postObj));
+ _log.debug("_executeCommand", "postObj => " + JSON.stringify(postObj));
if (typeof(postObj) === "object" && postObj.script && postObj.args) {
_protoParent.getSessionCurrWindow.call(this, _session, req).setOneShotCallback("onCallback", function() {
- // console.log("onCallback - " + JSON.stringify(postObj));
+ _log.debug("_executeCommand", "onCallback - arguments => " + JSON.stringify(arguments));
+
res.respondBasedOnResult(_session, req, arguments[0]);
});
@@ -408,9 +410,6 @@ ghostdriver.SessionReqHand = function(session) {
_getWindowHandle = function (req, res) {
var handle;
- // Initialize the Current Window (we need at least that)
- _session.initCurrentWindowIfNull();
-
// Get current window handle
handle = _session.getCurrentWindowHandle();
@@ -427,8 +426,6 @@ ghostdriver.SessionReqHand = function(session) {
},
_getWindowHandles = function(req, res) {
- // Initialize the Current Window (we need at least that)
- _session.initCurrentWindowIfNull();
res.success(_session.getId(), _session.getWindowHandles());
},
@@ -452,12 +449,11 @@ ghostdriver.SessionReqHand = function(session) {
var postObj = JSON.parse(req.post),
currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
- // console.log("Session '"+ _session.getId() +"' is about to load URL: " + postObj.url);
+ _log.debug("_postUrlCommand", "Session '"+ _session.getId() +"' is about to load URL: " + postObj.url);
if (typeof(postObj) === "object" && postObj.url) {
// Switch to the main frame first
currWindow.switchToMainFrame();
- // console.log("Session '"+ _session.getId() +"' has switched to the MainFrame");
// Load URL and wait for load to finish (or timeout)
currWindow.execFuncAndWaitForLoad(
@@ -465,15 +461,26 @@ ghostdriver.SessionReqHand = function(session) {
currWindow.open(postObj.url);
},
_createOnSuccessHandler(res), //< success
- function() { //< failure/timeout
- // Request timed out
- _errors.handleFailedCommandEH(
+ function(errMsg) { //< failure/timeout
+ if (errMsg === "timeout") {
+ // Request timed out
+ _errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.TIMEOUT,
- "URL '" + postObj.url + "' didn't load within " + _session.getPageLoadTimeout() + "ms",
+ "URL '" + postObj.url + "' didn't load within the 'Page Load Timeout'",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ } else {
+ // Unknown error
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
+ "URL '" + postObj.url + "' didn't load. Error: '" + errMsg + "'",
req,
res,
_session,
"SessionReqHand");
+ }
});
} else {
throw _errors.createInvalidReqMissingCommandParameterEH(req);
@@ -516,49 +523,95 @@ ghostdriver.SessionReqHand = function(session) {
_postFrameCommand = function(req, res) {
var postObj = JSON.parse(req.post),
frameName,
- switched = false;
+ framePos,
+ switched = false,
+ currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+
+ _log.debug("_postFrameCommand", "Current frames count: " + currWindow.framesCount);
if (typeof(postObj) === "object" && typeof(postObj.id) !== "undefined") {
if(postObj.id === null) {
+ _log.debug("_postFrameCommand", "Switching to 'null' (main frame)");
+
// Reset focus on the topmost (main) Frame
- _protoParent.getSessionCurrWindow.call(this, _session, req).switchToMainFrame();
+ currWindow.switchToMainFrame();
switched = true;
} else if (typeof(postObj.id) === "number") {
+ _log.debug("_postFrameCommand", "Switching to frame number: " + postObj.id);
+
// Switch frame by "index"
- switched = _protoParent.getSessionCurrWindow.call(this, _session, req).switchToFrame(postObj.id);
+ switched = currWindow.switchToFrame(postObj.id);
} else if (typeof(postObj.id) === "string") {
- // Switch frame by "name" and, if not found, by "id"
- switched = _protoParent.getSessionCurrWindow.call(this, _session, req).switchToFrame(postObj.id);
+ // Switch frame by "name" or by "id"
+ _log.debug("_postFrameCommand", "Switching to frame #id: " + postObj.id);
+
+ switched = currWindow.switchToFrame(postObj.id);
// If we haven't switched, let's try to find the frame "name" using it's "id"
if (!switched) {
// fetch the frame "name" via "id"
- frameName = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(function(frameId) {
+ frameName = currWindow.evaluate(function(frameId) {
var el = null;
el = document.querySelector('#'+frameId);
if (el !== null) {
return el.name;
}
+
return null;
}, postObj.id);
+ _log.debug("_postFrameCommand", "Failed to switch by #id, trying by name: " + frameName);
+
// Switch frame by "name"
- switched = _protoParent.getSessionCurrWindow.call(this, _session, req).switchToFrame(frameName);
+ if (frameName !== null) {
+ switched = currWindow.switchToFrame(frameName);
+ }
+
+ if (!switched) {
+ // fetch the frame "position" via "id"
+ framePos = currWindow.evaluate(function(frameIdOrName) {
+ var allFrames = document.querySelectorAll("frame,iframe"),
+ theFrame = document.querySelector('#'+frameIdOrName) || document.querySelector('[name='+frameIdOrName+']'),
+ i;
+
+ for (i = allFrames.length -1; i >= 0; --i) {
+ if (allFrames[i].contentWindow === theFrame.contentWindow) {
+ return i;
+ }
+ }
+ }, postObj.id);
+
+ if (framePos >= 0) {
+ _log.debug("_postFrameCommand", "Failed to switch by #id or name, trying by position: "+framePos);
+ switched = currWindow.switchToFrame(framePos);
+ } else {
+ _log.warn("_postFrameCommand", "Unable to locate the Frame!");
+ }
+ }
}
} else if (typeof(postObj.id) === "object" && typeof(postObj.id["ELEMENT"]) === "string") {
+ _log.debug("_postFrameCommand", "Switching to frame ELEMENT: " + JSON.stringify(postObj.id));
+
// Will use the Element JSON to find the frame name
- frameName = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(
+ frameName = currWindow.evaluate(
require("./webdriver_atoms.js").get("execute_script"),
- "return arguments[0].name;",
+ "if (!arguments[0].name && !arguments[0].id) { " +
+ " arguments[0].name = '_random_name_id_' + new Date().getTime(); " +
+ " arguments[0].id = arguments[0].name; " +
+ "} " +
+ "return arguments[0].name || arguments[0].id;",
[postObj.id]);
- // If a name was found
+ _log.debug("_postFrameCommand", "Will try to switch to Frame using: "+frameName.value);
+
+ // If a frame name (or id) is found for the given ELEMENT, we
+ // "re-call" this very function, changing the `post` property
+ // on the `req` object. The `post` will contain this time
+ // the frame name (or id) that was found.
if (frameName && frameName.value) {
- // Switch frame by "name"
- switched = _protoParent.getSessionCurrWindow.call(this, _session, req).switchToFrame(frameName.value);
- } else {
- // No name was found
- switched = false;
+ req.post = "{\"id\" : \"" + frameName.value + "\"}";
+ _postFrameCommand.call(this, req, res);
+ return;
}
} else {
throw _errors.createInvalidReqInvalidCommandMethodEH(req);
@@ -600,7 +653,8 @@ ghostdriver.SessionReqHand = function(session) {
}
// Check that either an Element ID or an X-Y Offset was provided
if (elementSpecified || offsetSpecified) {
- // console.log("element: " + elementSpecified + ", offset: " + offsetSpecified);
+ _log.debug("_postMouseMoveToCommand", "element: " + elementSpecified + ", offset: " + offsetSpecified);
+
// If an Element was provided...
if (elementSpecified) {
// Get Element's Location and add it to the coordinates
@@ -612,23 +666,23 @@ ghostdriver.SessionReqHand = function(session) {
coords.x = elementLocation.x;
coords.y = elementLocation.y;
}
- // console.log("element specified. initial coordinates (" + coords.x + "," + coords.y + ")");
} else {
coords = _session.inputs.getCurrentCoordinates();
- // console.log("no element specified. initial coordinates (" + coords.x + "," + coords.y + ")");
}
+ _log.debug("_postMouseMoveToCommand", "initial coordinates: (" + coords.x + "," + coords.y + ")");
+
if (elementSpecified && !offsetSpecified && elementSize !== null) {
coords.x += Math.floor(elementSize.width / 2);
coords.y += Math.floor(elementSize.height / 2);
- // console.log("element specified and no offset. coordinates adjusted to (" + coords.x + "," + coords.y + ")");
} else {
// Add up the offset (if any)
coords.x += postObj.xoffset || 0;
coords.y += postObj.yoffset || 0;
- // console.log("offset specified. coordinates adjusted to (" + coords.x + "," + coords.y + ")");
}
+ _log.debug("_postMouseMoveToCommand", "coordinates adjusted to: (" + coords.x + "," + coords.y + ")");
+
// Send the Mouse Move as native event
_session.inputs.mouseMove(_session, coords);
res.success(_session.getId());
@@ -666,7 +720,21 @@ ghostdriver.SessionReqHand = function(session) {
},
_postCookieCommand = function(req, res) {
- var postObj = JSON.parse(req.post || "{}");
+ var postObj = JSON.parse(req.post || "{}"),
+ currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+
+ // If the page has not loaded anything yet, setting cookies is forbidden
+ if (currWindow.url.indexOf("about:blank") === 0) {
+ // Something else went wrong
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.UNABLE_TO_SET_COOKIE,
+ "Unable to set Cookie: no URL has been loaded yet",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ return;
+ }
if (postObj.cookie) {
// JavaScript deals with Timestamps in "milliseconds since epoch": normalize!
@@ -676,12 +744,12 @@ ghostdriver.SessionReqHand = function(session) {
// If the cookie is expired OR if it was successfully added
if ((postObj.cookie.expiry && postObj.cookie.expiry <= new Date().getTime()) ||
- _protoParent.getSessionCurrWindow.call(this, _session, req).addCookie(postObj.cookie)) {
+ currWindow.addCookie(postObj.cookie)) {
// Notify success
res.success(_session.getId());
} else {
// Something went wrong while trying to set the cookie
- if (_protoParent.getSessionCurrWindow.call(this, _session, req).url.indexOf(postObj.cookie.domain) < 0) {
+ if (currWindow.url.indexOf(postObj.cookie.domain) < 0) {
// Domain mismatch
_errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.INVALID_COOKIE_DOMAIN,
View
3  src/ghostdriver/request_handlers/shutdown_request_handler.js
@@ -32,8 +32,11 @@ ghostdriver.ShutdownReqHand = function() {
// private:
var
_protoParent = ghostdriver.ShutdownReqHand.prototype,
+ _log = ghostdriver.logger.create("ShutdownReqHand"),
_handle = function(req, res) {
+ _log.info("_handle", "About to shutdown");
+
_protoParent.handle.call(this, req, res);
// Any HTTP Request Method will be accepted for this command. Some drivers like HEAD for example...
View
8 src/ghostdriver/request_handlers/status_request_handler.js
@@ -29,8 +29,7 @@ var ghostdriver = ghostdriver || {};
ghostdriver.StatusReqHand = function() {
// private:
- var
- _protoParent = ghostdriver.StatusReqHand.prototype,
+ const
_statusObj = {
"build" : {
"version" : ghostdriver.version
@@ -40,7 +39,10 @@ ghostdriver.StatusReqHand = function() {
"version" : ghostdriver.system.os.version,
"arch" : ghostdriver.system.os.architecture
}
- },
+ };
+
+ var
+ _protoParent = ghostdriver.StatusReqHand.prototype,
_handle = function(req, res) {
_protoParent.handle.call(this, req, res);
View
173 src/ghostdriver/request_handlers/webelement_request_handler.js
@@ -30,11 +30,7 @@ var ghostdriver = ghostdriver || {};
ghostdriver.WebElementReqHand = function(idOrElement, session) {
// private:
- var
- _id = ((typeof(idOrElement) === "object") ? idOrElement["ELEMENT"] : idOrElement),
- _session = session,
- _locator = new ghostdriver.WebElementLocator(_session),
- _protoParent = ghostdriver.WebElementReqHand.prototype,
+ const
_const = {
ELEMENT : "element",
ELEMENTS : "elements",
@@ -53,14 +49,19 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
LOCATION : "location",
LOCATION_IN_VIEW : "location_in_view",
SIZE : "size"
- },
+ };
+
+ var
+ _id = ((typeof(idOrElement) === "object") ? idOrElement["ELEMENT"] : idOrElement),
+ _session = session,
+ _locator = new ghostdriver.WebElementLocator(_session),
+ _protoParent = ghostdriver.WebElementReqHand.prototype,
_errors = _protoParent.errors,
+ _log = ghostdriver.logger.create("WebElementReqHand"),
_handle = function(req, res) {
_protoParent.handle.call(this, req, res);
- // console.log("Request => " + JSON.stringify(req, null, ' '));
-
if (req.urlParsed.file === _const.ELEMENT && req.method === "POST") {
_locator.handleLocateCommand(req, res, _locator.locateElement, _getJSON());
return;
@@ -146,7 +147,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getLocation = function(req) {
var result = _getLocationResult(req);
- // console.log("Location: "+JSON.stringify(result));
+ _log.debug("_getLocation", JSON.stringify(result));
if (result.status === 0) {
return result.value;
@@ -158,7 +159,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getLocationCommand = function(req, res) {
var locationRes = _getLocationResult(req);
- // console.log("Location (cmd): "+JSON.stringify(locationRes));
+ _log.debug("_getLocationCommand", JSON.stringify(locationRes));
res.respondBasedOnResult(_session, req, locationRes);
},
@@ -173,7 +174,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getLocationInView = function (req) {
var result = _getLocationInViewResult(req);
- // console.log("Location: "+JSON.stringify(result));
+ _log.debug("_getLocationInView", JSON.stringify(result));
if (result.status === 0) {
return result.value;
@@ -185,7 +186,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getLocationInViewCommand = function (req, res) {
var locationInViewRes = _getLocationInViewResult(req);
- // console.log("Scrolling into View result: "+JSON.stringify(locationInViewRes));
+ _log.debug("_getLocationInViewCommand", JSON.stringify(locationInViewRes));
// Something went wrong: report the error
res.respondBasedOnResult(_session, req, locationInViewRes);
@@ -200,7 +201,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getSize = function (req) {
var result = JSON.parse(_getSizeResult(req));
- // console.log("Size: " + JSON.stringify(result));
+ _log.debug("_getSize", JSON.stringify(result));
if (result.status === 0) {
return result.value;
@@ -212,7 +213,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
_getSizeCommand = function (req, res) {
var sizeRes = _getSizeResult(req);
- // console.log("Size (cmd): "+JSON.stringify(sizeRes));
+ _log.debug("_getSizeCommand", JSON.stringify(sizeRes))
res.respondBasedOnResult(_session, req, sizeRes);
},
@@ -236,7 +237,7 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
}
break;
case '\n':
- resultStr += '\uE006'; // Return
+ resultStr += '\uE007'; // Enter
break;
default:
resultStr += str[i];
@@ -251,17 +252,18 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
var postObj = JSON.parse(req.post),
currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req),
typeRes,
- text;
+ text,
+ fsModule = require("fs");
// Ensure all required parameters are available
if (typeof(postObj) === "object" && typeof(postObj.value) === "object") {
// Normalize input: some binding might send an array of single characters
text = postObj.value.join("");
- // Detect if it's an Input File type (that requires special behaviour)
+ // Detect if it's an Input File type (that requires special behaviour), and the File actually exists
if (_getTagName(currWindow).toLowerCase() === "input" &&
- _getAttribute(currWindow, "type").toLowerCase() === "file") {
-
+ _getAttribute(currWindow, "type").toLowerCase() === "file" &&
+ fsModule.exists(text)) {
// Register a one-shot-callback to fill the file picker once invoked by clicking on the element
currWindow.setOneShotCallback("onFilePicker", function(oldFile) {
// Send the response as soon as we are done setting the value in the "input[type=file]" element
@@ -379,18 +381,23 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
} else {
_errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
- "Submit succeeded but Load Failed",
+ "Submit succeeded but Load Failed. Status: '" + status + "'",
req,
res,
_session,
"WebElementReqHand");
}
}
- }, function() {
- if (arguments.length === 0) { //< onTimeout
- // onsubmit didn't bubble up, but we should still return success
- res.success(_session.getId());
- } else { //< onError
+ }, function(errMsg) {
+ if (errMsg === "timeout") { //< onTimeout
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.TIMEOUT,
+ "Submit timed-out",
+ req,
+ res,
+ _session,
+ "WebElementReqHand");
+ } else { //< onError (generic)
_errors.handleFailedCommandEH(
_errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
"Submit failed: " + arguments[0],
@@ -402,83 +409,59 @@ ghostdriver.WebElementReqHand = function(idOrElement, session) {
});
},
- _canCausePageLoadOnClick = function(currWindow) {
- var tagName = _getTagName(currWindow).toLowerCase(),
- href = (_getAttribute(currWindow, "href") || ""),
- type = (_getAttribute(currWindow, "type") || "").toLowerCase();
-
- // Return "true" if it's an element that "could cause a page load when clicked"
- // 1. "A" tag with "HREF" set
- if (tagName === "a" && href.length > 0) {
- return true;
- }
- // 2. "INPUT/BUTTON" tag with "TYPE" set to "SUBMIT/IMAGE"
- if (tagName === "input" || tagName === "button") {
- if (type === "submit" || type === "image") {
- 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
- // console.log("click: onLoadFinished: "+status);
-
- // Report Load Finished, only if callbacks were not "aborted"
- 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");
- }
+ // 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 Load Finished, only if callbacks were not "aborted"
+ if (!abortCallback) {
+ if (status === "success") {
+ res.success(_session.getId());
+ } else {
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
+ "Click succeeded but Load Failed. Status: '" + status + "'",
+ req,
+ res,
+ _session,
+ "WebElementReqHand");
}
- }, function() {
- // console.log("click: onLoadError");
-
- // Report Load Erro, only if callbacks were not "aborted"
- if (!abortCallback) {
- 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");
- }
+ }
+ }, function(errMsg) {
+ // Report Load Error, only if callbacks were not "aborted"
+ if (!abortCallback) {
+ if (errMsg === "timeout") { //< onTimeout
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.TIMEOUT,
+ "Click failed: " + arguments[0],
+ req,
+ res,
+ _session,
+ "WebElementReqHand");
+ } 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) {
View
242 src/ghostdriver/session.js
@@ -29,12 +29,23 @@ var ghostdriver = ghostdriver || {};
ghostdriver.Session = function(desiredCapabilities) {
// private:
+ const
+ _const = {
+ TIMEOUT_NAMES : {
+ SCRIPT : "script",
+ ASYNC_SCRIPT : "async script",
+ IMPLICIT : "implicit",
+ PAGE_LOAD : "page load"
+ },
+ ONE_SHOT_POSTFIX : "OneShot"
+ };
+
var
_defaultCapabilities = { // TODO - Actually try to match the "desiredCapabilities" instead of ignoring them
"browserName" : "phantomjs",
- "version" :
- "phantomjs-" + phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch + '+' +
- "ghostdriver-" + ghostdriver.version,
+ "version" : phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch,
+ "driverName" : "ghostdriver",
+ "driverVersion" : ghostdriver.version,
"platform" : ghostdriver.system.os.name + '-' + ghostdriver.system.os.version + '-' + ghostdriver.system.os.architecture,
"javascriptEnabled" : true,
"takesScreenshot" : true,
@@ -55,6 +66,8 @@ ghostdriver.Session = function(desiredCapabilities) {
_negotiatedCapabilities = {
"browserName" : _defaultCapabilities.browserName,
"version" : _defaultCapabilities.version,
+ "driverName" : _defaultCapabilities.driverName,
+ "driverVersion" : _defaultCapabilities.driverVersion,
"platform" : _defaultCapabilities.platform,
"javascriptEnabled" : typeof(desiredCapabilities.javascriptEnabled) === "undefined" ?
_defaultCapabilities.javascriptEnabled :
@@ -83,24 +96,16 @@ ghostdriver.Session = function(desiredCapabilities) {
_timeouts = {
"script" : _max32bitInt,
"async script" : _max32bitInt,
- "implicit" : 0, //< 0s
+ "implicit" : 5, //< 5ms
"page load" : _max32bitInt,
},
- _const = {
- TIMEOUT_NAMES : {
- SCRIPT : "script",
- ASYNC_SCRIPT : "async script",
- IMPLICIT : "implicit",
- PAGE_LOAD : "page load"
- },
- ONE_SHOT_POSTFIX : "OneShot"
- },
_windows = {}, //< NOTE: windows are "webpage" in Phantom-dialect
_currentWindowHandle = null,
_id = require("./third_party/uuid.js").v1(),
_inputs = ghostdriver.Inputs(),
_capsPageSettingsPref = "phantomjs.page.settings.",
_pageSettings = {},
+ _log = ghostdriver.logger.create("Session [" + _id + "]"),
k, settingKey;
// Searching for `phantomjs.settings.*` in the Desired Capabilities and merging with the Negotiated Capabilities
@@ -119,115 +124,108 @@ ghostdriver.Session = function(desiredCapabilities) {
/**
* Executes a function and waits for Load to happen.
*
- * @param func Function to execute
+ * @param code Code to execute: a Function or just plain code
* @param onLoadFunc Function to execute when page finishes Loading
* @param onErrorFunc Function to execute in case of error
+ * (eg. Javascript error, page load problem or timeout).
* @param execTypeOpt Decides if to "apply" the function directly or page."eval" it.
* Optional. Default value is "apply".
*/
- _execFuncAndWaitForLoadDecorator = function(func, onLoadFunc, onErrorFunc, execTypeOpt) {
+ _execFuncAndWaitForLoadDecorator = function(code, onLoadFunc, onErrorFunc, execTypeOpt) {
// convert 'arguments' to a real Array
var args = Array.prototype.splice.call(arguments, 0),
- loadingTimer,
- loadingNewPage = false,
- pageLoadNotTriggered = true,
- thisPage = this;
+ thisPage = this,
+ onLoadFinishedArgs = null,
+ onErrorArgs = null;
// Normalize "execTypeOpt" value
- if (typeof(execexecTypeOpt) === "undefined" ||
- (execexecTypeOpt !== "apply" && execTypeOpt !== "eval")) {
+ if (typeof(execTypeOpt) === "undefined" ||
+ (execTypeOpt !== "apply" && execTypeOpt !== "eval")) {
execTypeOpt = "apply";
}
- // Separating arguments for the "function call" from the callback handlers.
- if (execTypeOpt === "eval") {
- // NOTE: I'm also passing 'evalFunc' as first parameter
- // for the 'evaluate' call, and '0' as timeout.
- args.splice(0, 3, evalFunc, 0);
- } else {
- args.splice(0, 3);
- }
+ // Our callbacks assume that the only thing affecting the page state
+ // is the function we execute. Therefore we need to kill any
+ // pre-existing activity (such as part of the page being loaded in
+ // the background), otherwise it's events might interleave with the
+ // events from the current function.
+ this.stop();
- // Register event handlers
- // This logic bears some explaining. If we are loading a new page,
- // the loadStarted event will fire, then urlChanged, then loadFinished,
- // assuming no errors. However, when navigating to a fragment on the
- // same page, neither the loadStarted nor the loadFinished events will
- // fire. So if we receive a urlChanged event without a corresponding
- // loadStarted event, we know we are only navigating to a fragment on
- // the same page, and should fire the onLoadFunc callback. Otherwise,
- // we need to wait until the loadFinished event before firing the
- // callback.
- this.setOneShotCallback("onLoadStarted", function () {
- // console.log("onLoadStarted");
-
- pageLoadNotTriggered = false;
- loadingNewPage = true;
- });
- this.setOneShotCallback("onUrlChanged", function () {
- // console.log("onUrlChanged");
-
- pageLoadNotTriggered = false;
- // If "not loading a new page" it's just a fragment change
- // and we should call "onLoadFunc()"
- if (!loadingNewPage) {
- clearTimeout(loadingTimer);
- thisPage.resetOneShotCallbacks();
- onLoadFunc.call(thisPage, "success");
- }
- });
- this.setOneShotCallback("onLoadFinished", function () {
- // console.log("onLoadFinished");
+ // Register Callbacks to grab any async event we are interested in
+ this.setOneShotCallback("onLoadFinished", function (status) {
+ _log.debug("_execFuncAndWaitForLoadDecorator", "onLoadFinished: " + status);
- clearTimeout(loadingTimer);
- thisPage.resetOneShotCallbacks();
- onLoadFunc.apply(thisPage, arguments);
+ onLoadFinishedArgs = Array.prototype.slice.call(arguments);
});
this.setOneShotCallback("onError", function(message, stack) {
- // console.log("onError: "+message+"\n");
- // stack.forEach(function(item) {
- // var message = item.file + ":" + item.line;
- // if (item["function"])
- // message += " in " + item["function"];
- // console.log(" " + message);
- // });
-
- pageLoadNotTriggered = false;
- thisPage.stop(); //< stop the page from loading
- clearTimeout(loadingTimer);
- thisPage.resetOneShotCallbacks();
- onErrorFunc.apply(thisPage, arguments);
+ _log.debug("_execFuncAndWaitForLoadDecorator", "onError: "+message+"\n");
+ stack.forEach(function(item) {
+ var msg = item.file + ":" + item.line;
+ msg += item["function"] ? " in " + item["function"] : "";
+ _log.debug("_execFuncAndWaitForLoadDecorator", " " + msg);
+ });
+
+ onErrorArgs = Array.prototype.slice.call(arguments);
});
- // Starting loadingTimer
- // console.log("Setting 'loadingTimer' to: " + _getPageLoadTimeout());
- loadingTimer = setTimeout(function() {
- // console.log("loadingTimer: pageLoadTimeout");
-
- thisPage.stop(); //< stop the page from loading
- thisPage.resetOneShotCallbacks();
- onErrorFunc.apply(thisPage, arguments);
- }, _getPageLoadTimeout());
-
- // 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 === true) {
- // console.log("pageLoadNotTriggered");
-
- clearTimeout(loadingTimer);
- thisPage.resetOneShotCallbacks();
- onLoadFunc.call(thisPage, "success");
- }
- }, 500);
-
- // We are ready to execute
+ // Execute "code"
if (execTypeOpt === "eval") {
+ // 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);
// Invoke the Page Eval with the provided function
this.evaluateAsync.apply(this, args);
} else {
+ // Remove arguments used by this function before providing them to the target function.
+ args.splice(0, 3);
// "Apply" the provided function
- func.apply(this, args);
+ code.apply(this, args);
}
+
+ // Wait 10ms before proceeding any further: in this window of time
+ // the page can react and start loading (if it has to).
+ setTimeout(function() {
+ var loadingStartedTs,
+ checkLoadingFinished;
+
+ loadingStartedTs = new Date().getTime();
+
+ checkLoadingFinished = function() {
+ if (!_isLoading()) { //< page finished loading
+ _log.debug("_execFuncAndWaitForLoadDecorator", "Page Loading in Session: false");
+
+ thisPage.resetOneShotCallbacks();
+
+ if (onLoadFinishedArgs !== null) {
+ // Report the result of the "Load Finished" event
+ onLoadFunc.apply(thisPage, onLoadFinishedArgs);
+ } else if (onErrorArgs !== null) {
+ // Report the "Error" event
+ onErrorFunc.apply(thisPage, onErrorArgs);
+ } 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()) {
+ thisPage.resetOneShotCallbacks();
+
+ // Report the "Timeout" event
+ onErrorFunc.call(thisPage, "timeout");
+
+ return;
+ }
+
+ // Retry in 100ms
+ setTimeout(checkLoadingFinished, 100);
+ };
+ checkLoadingFinished();
+ }, 10);
},
_oneShotCallbackFactory = function(page, callbackName) {
@@ -235,7 +233,8 @@ ghostdriver.Session = function(desiredCapabilities) {
var retVal;
if (typeof(page[callbackName + _const.ONE_SHOT_POSTFIX]) === "function") {
- // console.log("Invoking one-shot-callback for: " + callbackName);
+ _log.debug("_oneShotCallback", callbackName);
+
retVal = page[callbackName + _const.ONE_SHOT_POSTFIX].apply(page, arguments);
page[callbackName + _const.ONE_SHOT_POSTFIX] = null;
}
@@ -252,7 +251,7 @@ ghostdriver.Session = function(desiredCapabilities) {
},
_resetOneShotCallbacksDecorator = function() {
- // console.log("Clearing One-Shot Callbacks");
+ _log.debug("_resetOneShotCallbacksDecorator");
this["onLoadStarted" + _const.ONE_SHOT_POSTFIX] = null;
this["onLoadFinished" + _const.ONE_SHOT_POSTFIX] = null;
@@ -262,12 +261,16 @@ ghostdriver.Session = function(desiredCapabilities) {
// Add any new page to the "_windows" container of this session
_addNewPage = function(newPage) {
+ _log.debug("_addNewPage");
+
_decorateNewWindow(newPage); //< decorate the new page
_windows[newPage.windowHandle] = newPage; //< store the page/window
},
// Delete any closing page from the "_windows" container of this session
_deleteClosingPage = function(closingPage) {
+ _log.debug("_deleteClosingPage");
+
// Need to be defensive, as the "closing" can be cause by Client Commands
if (_windows.hasOwnProperty(closingPage.windowHandle)) {
delete _windows[closingPage.windowHandle];
@@ -304,12 +307,30 @@ ghostdriver.Session = function(desiredCapabilities) {
}
}
- // page.onConsoleMessage = function(msg) { console.log(msg); };
- // console.log("New Window/Page settings: " + JSON.stringify(page.settings, null, " "));
+ page.onConsoleMessage = function(msg) { _log.debug("page.onConsoleMessage", msg); };
+
+ _log.debug("_decorateNewWindow", "page.settings: " + JSON.stringify(page.settings));
return page;
},
+ /**
+ * 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;
+ },
+
_getWindow = function(handleOrName) {
var page = null,
k;
@@ -330,7 +351,9 @@ ghostdriver.Session = function(desiredCapabilities) {
return page;
},
- _initCurrentWindowIfNull = function() {
+ _init = function() {
+ var page;
+
// Ensure a Current Window is available, if it's found to be `null`
if (_currentWindowHandle === null) {
// First call to get the current window: need to create one
@@ -343,7 +366,6 @@ ghostdriver.Session = function(desiredCapabilities) {
_getCurrentWindow = function() {
var page = null;
- _initCurrentWindowIfNull();
if (_windows.hasOwnProperty(_currentWindowHandle)) {
page = _windows[_currentWindowHandle];
}
@@ -464,8 +486,12 @@ ghostdriver.Session = function(desiredCapabilities) {
}
};
- // console.log("Session '" + _id + "' - Capabilities: " + JSON.stringify(_negotiatedCapabilities, null, " "));
- // console.log("Desired: "+JSON.stringify(desiredCapabilities, null, " "));
+ // Initialize the Session.
+ // Particularly, create the first empty page/window.
+ _init();
+
+ _log.info("CONSTRUCTOR", "Desired Capabilities: " + JSON.stringify(desiredCapabilities));
+ _log.info("CONSTRUCTOR", "Negotiated Capabilities: " + JSON.stringify(_negotiatedCapabilities));
// public:
return {
@@ -477,7 +503,6 @@ ghostdriver.Session = function(desiredCapabilities) {
getWindow : _getWindow,
closeWindow : _closeWindow,
getWindowsCount : _getWindowsCount,
- initCurrentWindowIfNull : _initCurrentWindowIfNull,
getCurrentWindowHandle : _getCurrentWindowHandle,
getWindowHandles : _getWindowHandles,
isValidWindowHandle : _isValidWindowHandle,
@@ -491,7 +516,8 @@ ghostdriver.Session = function(desiredCapabilities) {
getAsyncScriptTimeout : _getAsyncScriptTimeout,
getImplicitTimeout : _getImplicitTimeout,
getPageLoadTimeout : _getPageLoadTimeout,
- timeoutNames : _const.TIMEOUT_NAMES
+ timeoutNames : _const.TIMEOUT_NAMES,
+ isLoading : _isLoading
};
};
View
287 src/ghostdriver/third_party/console++.js
@@ -0,0 +1,287 @@
+/*
+This file is part of the Console++ by Ivan De Marino <http://ivandemarino.me>.
+
+Copyright (c) 2012, Ivan De Marino <http://ivandemarino.me>
+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.
+*/
+
+if (console.LEVELS) {
+ // Already loaded. No need to manipulate the "console" further.
+ // NOTE: NodeJS already caches modules. This is just defensive coding.
+ exports = console;
+ return;
+}
+
+// private:
+var _ANSICODES = {
+ 'reset' : '\033[0m',
+ 'bold' : '\033[1m',
+ 'italic' : '\033[3m',
+ 'underline' : '\033[4m',
+ 'blink' : '\033[5m',
+ 'black' : '\033[30m',
+ 'red' : '\033[31m',
+ 'green' : '\033[32m',
+ 'yellow' : '\033[33m',
+ 'blue' : '\033[34m',
+ 'magenta' : '\033[35m',
+ 'cyan' : '\033[36m',
+ 'white' : '\033[37m'
+ },
+ _LEVELS = {
+ ERROR : 0,
+ WARN : 1,
+ WARNING : 1, //< just to please my OCD
+ INFO : 2,
+ INFORMATION : 2, //< just to please my OCD
+ DEBUG : 3
+ },
+ _LEVELS_COLOR = [ //< _LEVELS_COLOR position matches the _LEVELS values
+ "red",
+ "yellow",
+ "cyan",
+ "green"
+ ],
+ _LEVELS_NAME = [ //< _LEVELS_NAME position matches the _LEVELS values
+ "ERROR",
+ "WARN ",
+ "INFO ",
+ "DEBUG"
+ ],
+ _console = {
+ error : console.error,
+ warn : console.warn,
+ info : console.info,
+ debug : console.log,
+ log : console.log
+ },
+ _level = _LEVELS.DEBUG,
+ _colored = true,
+ _messageColored = false,
+ _timed = true,
+ _onOutput = null;
+
+/**
+ * Take a string and apply console ANSI colors for expressions "#color{msg}"
+ * NOTE: Does nothing if "console.colored === false".
+ *
+ * @param str Input String
+ * @returns Same string but with colors applied
+ */
+var _applyColors = function(str) {
+ var tag = /#([a-z]+)\{|\}/,
+ cstack = [],
+ matches = null,
+ orig = null,
+ name = null,
+ code = null;
+
+ while (tag.test(str)) {
+ matches = tag.exec(str);
+ orig = matches[0];
+
+ if (console.isColored()) {
+ if (orig === '}') {
+ cstack.pop();
+ } else {
+ name = matches[1];
+ if (name in _ANSICODES) {
+ code = _ANSICODES[name];
+ cstack.push(code);
+ }
+ }
+
+ str = str.replace(orig, _ANSICODES.reset + cstack.join(''));
+ } else {
+ str = str.replace(orig, '');
+ }
+ }
+ return str;
+};
+
+/**
+ * Decorate the Arguments passed to the console methods we override.
+ * First element, the message, is now colored, timed and more (based on config).
+ *
+ * @param argsArray Array of arguments to decorate
+ * @param level Logging level to apply (regulates coloring and text)
+ * @returns Array of Arguments, decorated.
+ */
+var _decorateArgs = function(argsArray, level) {
+ var args = Array.prototype.slice.call(argsArray, 1),
+ msg = argsArray[0],
+ levelMsg;
+
+ if (console.isColored()) {
+ levelMsg = _applyColors("#" + console.getLevelColor(level) + "{" + console.getLevelName(level) + "}");
+ msg = _applyColors(msg);
+
+ if (console.isMessageColored()) {
+ msg = _applyColors("#" + console.getLevelColor(level) + "{" + msg + "}");
+ }
+ } else {
+ levelMsg = console.getLevelName(level);
+ }
+
+ msg = _formatMessage(msg, levelMsg);
+
+ args.splice(0, 0, msg);
+
+ return args;
+};
+
+/**
+ * Formats the Message content.
+ * @param msg The message itself
+ * @param levelMsg The portion of message that contains the Level (maybe colored)
+ * @retuns The formatted message
+ */
+var _formatMessage = function(msg, levelMsg) {
+ if (console.isTimestamped()) {
+ return "[" + levelMsg + " - " + new Date().toJSON() + "] " + msg;
+ } else {
+ return "[" + levelMsg + "] " + msg;
+ }
+};
+
+/**
+ * Invokes the "console.onOutput()" callback, if it was set by user.
+ * This is useful in case the user wants to write the console output to another media as well.
+ *
+ * The callback is invoked with 2 parameters:
+ * - formattedMessage: formatted message, ready for output
+ * - levelName: the name of the logging level, to inform the user
+ *
+ * @param msg The Message itself
+ * @param level The Message Level (Number)
+ */
+var _invokeOnOutput = function(msg, level) {
+ var formattedMessage,
+ levelName;
+
+ if (_onOutput !== null && typeof(_onOutput) === "function") {
+ levelName = console.getLevelName(level);
+ formattedMessage = _formatMessage(msg, levelName);
+
+ _onOutput.call(null, formattedMessage, levelName);
+ }
+};
+
+
+// public:
+// CONSTANT: Logging Levels
+console.LEVELS = _LEVELS;
+
+// Set/Get Level
+console.setLevel = function(level) {
+ _level = level;
+};
+console.getLevel = function() {
+ return _level;
+};
+console.getLevelName = function(level) {
+ return _LEVELS_NAME[typeof(level) === "undefined" ? _level : level];
+};
+console.getLevelColor = function(level) {
+ return _LEVELS_COLOR[typeof(level) === "undefined" ? _level : level];
+};
+console.isLevelVisible = function(levelToCompare) {
+ return _level >= levelToCompare;
+};
+
+// Enable/Disable Colored Output
+console.enableColor = function() {
+ _colored = true;
+};
+console.disableColor = function() {
+ _colored = false;
+};
+console.isColored = function() {
+ return _colored;
+};
+
+// Enable/Disable Colored Message Output
+console.enableMessageColor = function() {
+ _messageColored = true;
+};
+console.disableMessageColor = function() {
+ _messageColored = false;
+};
+console.isMessageColored = function() {
+ return _messageColored;
+};
+
+// Enable/Disable Timestamped Output
+console.enableTimestamp = function() {
+ _timed = true;
+};
+console.disableTimestamp = function() {
+ _timed = false;
+};
+console.isTimestamped = function() {
+ return _timed;
+};
+
+// Set OnOutput Callback (useful to write to file or something)
+// Callback: `function(formattedMessage, levelName)`
+console.onOutput = function(callback) {
+ _onOutput = callback;
+};
+
+// Decodes coloring markup in string
+console.str2clr = function(str) {
+ return console.isColored() ? _applyColors(str): str;
+};
+
+// Overrides some key "console" Object methods
+console.error = function(msg) {
+ if (arguments.length > 0 && this.isLevelVisible(_LEVELS.ERROR)) {
+ _console.error.apply(this, _decorateArgs(arguments, _LEVELS.ERROR));
+ _invokeOnOutput(msg, _LEVELS.ERROR);
+ }
+};
+console.warn = function(msg) {
+ if (arguments.length > 0 && this.isLevelVisible(_LEVELS.WARN)) {
+ _console.warn.apply(this, _decorateArgs(arguments, _LEVELS.WARN));
+ _invokeOnOutput(msg, _LEVELS.WARN);
+ }
+};
+console.info = function(msg) {
+ if (arguments.length > 0 && this.isLevelVisible(_LEVELS.INFO)) {
+ _console.info.apply(this, _decorateArgs(arguments, _LEVELS.INFO));
+ _invokeOnOutput(msg, _LEVELS.INFO);
+ }
+};
+console.debug = function(msg) {
+ if (arguments.length > 0 && this.isLevelVisible(_LEVELS.DEBUG)) {
+ _console.debug.apply(this, _decorateArgs(arguments, _LEVELS.DEBUG));
+ _invokeOnOutput(msg, _LEVELS.DEBUG);
+ }
+};
+console.log = function(msg) {
+ if (arguments.length > 0) {
+ _console.log.apply(this, arguments);
+ }
+};
+
+exports = console;
View
95 src/ghostdriver/third_party/webdriver-atoms/active_element.js
@@ -1,47 +1,48 @@
-function(){return function(){function h(a){throw a;}var i=void 0,j=!0,k=null,l=!1;function m(a){return function(){return this[a]}}function n(a){return function(){return a}}var p=this;
-function aa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
-else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function q(a){return"string"==typeof a}Math.floor(2147483648*Math.random()).toString(36);var ba=Date.now||function(){return+new Date};function r(a,b){function c(){}c.prototype=b.prototype;a.da=b.prototype;a.prototype=new c};var ca=window;function s(a){Error.captureStackTrace?Error.captureStackTrace(this,s):this.stack=Error().stack||"";a&&(this.message=String(a))}r(s,Error);s.prototype.name="CustomError";function da(a,b){for(var c=1;c<arguments.length;c++)var d=String(arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}
-function ea(a,b){for(var c=0,d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=String(b).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),f=Math.max(d.length,e.length),g=0;0==c&&g<f;g++){var o=d[g]||"",z=e[g]||"",A=RegExp("(\\d*)(\\D*)","g"),na=RegExp("(\\d*)(\\D*)","g");do{var D=A.exec(o)||["","",""],E=na.exec(z)||["","",""];if(0==D[0].length&&0==E[0].length)break;c=((0==D[1].length?0:parseInt(D[1],10))<(0==E[1].length?0:parseInt(E[1],10))?-1:(0==D[1].length?0:parseInt(D[1],10))>(0==E[1].length?
-0:parseInt(E[1],10))?1:0)||((0==D[2].length)<(0==E[2].length)?-1:(0==D[2].length)>(0==E[2].length)?1:0)||(D[2]<E[2]?-1:D[2]>E[2]?1:0)}while(0==c)}return c};function fa(a,b){b.unshift(a);s.call(this,da.apply(k,b));b.shift();this.aa=a}r(fa,s);fa.prototype.name="AssertionError";function ga(a,b,c){if(!a){var d=Array.prototype.slice.call(arguments,2),e="Assertion failed";if(b)var e=e+(": "+b),f=d;h(new fa(""+e,f||[]))}};var ha=Array.prototype;function t(a,b){for(var c=a.length,d=q(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(i,d[e],e,a)}function ia(a,b){for(var c=a.length,d=[],e=0,f=q(a)?a.split(""):a,g=0;g<c;g++)if(g in f){var o=f[g];b.call(i,o,g,a)&&(d[e++]=o)}return d}function ja(a,b){for(var c=a.length,d=Array(c),e=q(a)?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(i,e[f],f,a));return d}function ka(a,b,c){if(a.reduce)return a.reduce(b,c);var d=c;t(a,function(c,f){d=b.call(i,d,c,f,a)});return d}
-function la(a,b){for(var c=a.length,d=q(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(i,d[e],e,a))return j;return l}function ma(a){return ha.concat.apply(ha,arguments)}function oa(a,b,c){ga(a.length!=k);return 2>=arguments.length?ha.slice.call(a,b):ha.slice.call(a,b,c)};var u,pa,qa,ra;function v(){return p.navigator?p.navigator.userAgent:k}ra=qa=pa=u=l;var sa;if(sa=v()){var ta=p.navigator;u=0==sa.indexOf("Opera");pa=!u&&-1!=sa.indexOf("MSIE");qa=!u&&-1!=sa.indexOf("WebKit");ra=!u&&!qa&&"Gecko"==ta.product}var ua=u,w=pa,x=ra,va=qa,wa;
-a:{var xa="",y;if(ua&&p.opera)var ya=p.opera.version,xa="function"==typeof ya?ya():ya;else if(x?y=/rv\:([^\);]+)(\)|;)/:w?y=/MSIE\s+([^\);]+)(