Permalink
Browse files

First import of `ghostdriver.qrc` & related files.

  • Loading branch information...
1 parent 63dd362 commit 2dcccc8968889c2dd80fa2d32d39c2e114db7c6c @detro detro committed with Nov 16, 2012
Showing with 19,013 additions and 0 deletions.
  1. +2 −0 src/filesystem.cpp
  2. +231 −0 src/ghostdriver/errors.js
  3. +79 −0 src/ghostdriver/ghostdriver.qrc
  4. +84 −0 src/ghostdriver/hub_register.js
  5. +344 −0 src/ghostdriver/inputs.js
  6. +7 −0 src/ghostdriver/lastupdate
  7. +81 −0 src/ghostdriver/main.js
  8. +205 −0 src/ghostdriver/request_handlers/request_handler.js
  9. +106 −0 src/ghostdriver/request_handlers/router_request_handler.js
  10. +180 −0 src/ghostdriver/request_handlers/session_manager_request_handler.js
  11. +762 −0 src/ghostdriver/request_handlers/session_request_handler.js
  12. +58 −0 src/ghostdriver/request_handlers/shutdown_request_handler.js
  13. +64 −0 src/ghostdriver/request_handlers/status_request_handler.js
  14. +496 −0 src/ghostdriver/request_handlers/webelement_request_handler.js
  15. +400 −0 src/ghostdriver/session.js
  16. +70 −0 src/ghostdriver/third_party/parseuri.js
  17. +249 −0 src/ghostdriver/third_party/uuid.js
  18. +47 −0 src/ghostdriver/third_party/webdriver-atoms/active_element.js
  19. +132 −0 src/ghostdriver/third_party/webdriver-atoms/clear.js
  20. +17 −0 src/ghostdriver/third_party/webdriver-atoms/clear_local_storage.js
  21. +17 −0 src/ghostdriver/third_party/webdriver-atoms/clear_session_storage.js
  22. +140 −0 src/ghostdriver/third_party/webdriver-atoms/click.js
  23. +47 −0 src/ghostdriver/third_party/webdriver-atoms/default_content.js
  24. +14,032 −0 src/ghostdriver/third_party/webdriver-atoms/deps.js
  25. +121 −0 src/ghostdriver/third_party/webdriver-atoms/double_click.js
  26. +120 −0 src/ghostdriver/third_party/webdriver-atoms/drag.js
  27. +15 −0 src/ghostdriver/third_party/webdriver-atoms/execute_async_script.js
  28. +13 −0 src/ghostdriver/third_party/webdriver-atoms/execute_script.js
  29. +16 −0 src/ghostdriver/third_party/webdriver-atoms/execute_sql.js
  30. +90 −0 src/ghostdriver/third_party/webdriver-atoms/find_element.js
  31. +90 −0 src/ghostdriver/third_party/webdriver-atoms/find_elements.js
  32. +103 −0 src/ghostdriver/third_party/webdriver-atoms/focus_on_element.js
  33. +91 −0 src/ghostdriver/third_party/webdriver-atoms/frame_by_id_or_name.js
  34. +47 −0 src/ghostdriver/third_party/webdriver-atoms/frame_by_index.js
  35. +16 −0 src/ghostdriver/third_party/webdriver-atoms/get_appcache_status.js
  36. +55 −0 src/ghostdriver/third_party/webdriver-atoms/get_attribute.js
  37. +60 −0 src/ghostdriver/third_party/webdriver-atoms/get_attribute_value.js
  38. +7 −0 src/ghostdriver/third_party/webdriver-atoms/get_current_position.js
  39. +7 −0 src/ghostdriver/third_party/webdriver-atoms/get_element_from_cache.js
  40. +47 −0 src/ghostdriver/third_party/webdriver-atoms/get_frame_window.js
  41. +40 −0 src/ghostdriver/third_party/webdriver-atoms/get_in_view_location.js
  42. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_item.js
  43. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_keys.js
  44. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_local_storage_size.js
  45. +9 −0 src/ghostdriver/third_party/webdriver-atoms/get_location.js
  46. +49 −0 src/ghostdriver/third_party/webdriver-atoms/get_location_in_view.js
  47. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_session_storage_item.js
  48. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_session_storage_keys.js
  49. +17 −0 src/ghostdriver/third_party/webdriver-atoms/get_session_storage_size.js
  50. +65 −0 src/ghostdriver/third_party/webdriver-atoms/get_size.js
Sorry, we could not display the entire diff because it was too big.
View
2 src/filesystem.cpp
@@ -360,6 +360,8 @@ QString FileSystem::absolute(const QString &relativePath) const
// Files
QObject *FileSystem::_open(const QString &path, const QVariantMap &opts) const
{
+ qDebug() << "FileSystem - _open:" << path << opts;
+
const QVariant modeVar = opts["mode"];
// Ensure only strings
if (modeVar.type() != QVariant::String) {
View
231 src/ghostdriver/errors.js
@@ -0,0 +1,231 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+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.
+*/
+
+//------------------------------------------------------- Invalid Request Errors
+//----- http://code.google.com/p/selenium/wiki/JsonWireProtocol#Invalid_Requests
+exports.INVALID_REQ = {
+ "UNKNOWN_COMMAND" : "Unknown Command",
+ "UNIMPLEMENTED_COMMAND" : "Unimplemented Command",
+ "VARIABLE_RESOURCE_NOT_FOUND" : "Variable Resource Not Found",
+ "INVALID_COMMAND_METHOD" : "Invalid Command Method",
+ "MISSING_COMMAND_PARAMETER" : "Missing Command Parameter"
+};
+
+var _invalidReqHandle = function(res) {
+ // Set the right Status Code
+ switch(this.name) {
+ case exports.INVALID_REQ.UNIMPLEMENTED_COMMAND:
+ res.statusCode = 501; //< 501 Not Implemented
+ break;
+ case exports.INVALID_REQ.INVALID_COMMAND_METHOD:
+ res.statusCode = 405; //< 405 Method Not Allowed
+ break;
+ case exports.INVALID_REQ.MISSING_COMMAND_PARAMETER:
+ res.statusCode = 400; //< 400 Bad Request
+ break;
+ default:
+ res.statusCode = 404; //< 404 Not Found
+ break;
+ }
+
+ res.setHeader("Content-Type", "text/plain");
+ res.writeAndClose(this.name + " - " + this.message);
+};
+
+// Invalid Request Error Handler
+exports.createInvalidReqEH = function(errorName, req) {
+ var e = new Error();
+
+ e.name = errorName;
+ e.message = "Request => " + JSON.stringify(req);
+ e.handle = _invalidReqHandle;
+
+ return e;
+};
+exports.handleInvalidReqEH = function(errorName, req, res) {
+ exports.createInvalidReqEH(errorName, req).handle(res);
+};
+
+// Invalid Request Unknown Command Error Handler
+exports.createInvalidReqUnknownCommandEH = function(req) {
+ return exports.createInvalidReqEH (
+ exports.INVALID_REQ.UNKNOWN_COMMAND,
+ req);
+};
+exports.handleInvalidReqUnknownCommandEH = function(req, res) {
+ exports.createInvalidReqUnknownCommandEH(req).handle(res);
+};
+
+// Invalid Request Unimplemented Command Error Handler
+exports.createInvalidReqUnimplementedCommandEH = function(req) {
+ return exports.createInvalidReqEH (
+ exports.INVALID_REQ.UNIMPLEMENTED_COMMAND,
+ req);
+};
+exports.handleInvalidReqUnimplementedCommandEH = function(req, res) {
+ exports.createInvalidReqUnimplementedCommandEH(req).handle(res);
+};
+
+// Invalid Request Variable Resource Not Found Error Handler
+exports.createInvalidReqVariableResourceNotFoundEH = function(req) {
+ return exports.createInvalidReqEH (
+ exports.INVALID_REQ.VARIABLE_RESOURCE_NOT_FOUND,
+ req);
+};
+exports.handleInvalidReqVariableResourceNotFoundEH = function(req, res) {
+ exports.createInvalidReqVariableResourceNotFoundEH(req).handle(res);
+};
+
+// Invalid Request Invalid Command Method Error Handler
+exports.createInvalidReqInvalidCommandMethodEH = function(req) {
+ return exports.createInvalidReqEH (
+ exports.INVALID_REQ.INVALID_COMMAND_METHOD,
+ req);
+};
+exports.handleInvalidReqInvalidCommandMethodEH = function(req, res) {
+ exports.createInvalidReqInvalidCommandMethodEH(req).handle(res);
+};
+
+// Invalid Request Missing Command Parameter Error Handler
+exports.createInvalidReqMissingCommandParameterEH = function(req) {
+ return exports.createInvalidReqEH (
+ exports.INVALID_REQ.MISSING_COMMAND_PARAMETER,
+ req);
+};
+exports.handleInvalidReqMissingCommandParameterEH = function(req, res) {
+ exports.createInvalidReqMissingCommandParameterEH(req).handle(res);
+};
+
+//-------------------------------------------------------- Failed Command Errors
+//------ http://code.google.com/p/selenium/wiki/JsonWireProtocol#Failed_Commands
+exports.FAILED_CMD_STATUS = {
+ "SUCCESS" : "Success",
+ "NO_SUCH_ELEMENT" : "NoSuchElement",
+ "NO_SUCH_FRAME" : "NoSuchFrame",
+ "UNKNOWN_COMMAND" : "UnknownCommand",
+ "STALE_ELEMENT_REFERENCE" : "StaleElementReference",
+ "ELEMENT_NOT_VISIBLE" : "ElementNotVisible",
+ "INVALID_ELEMENT_STATE" : "InvalidElementState",
+ "UNKNOWN_ERROR" : "UnknownError",
+ "ELEMENT_IS_NOT_SELECTABLE" : "ElementIsNotSelectable",
+ "JAVA_SCRIPT_ERROR" : "JavaScriptError",
+ "XPATH_LOOKUP_ERROR" : "XPathLookupError",
+ "TIMEOUT" : "Timeout",
+ "NO_SUCH_WINDOW" : "NoSuchWindow",
+ "INVALID_COOKIE_DOMAIN" : "InvalidCookieDomain",
+ "UNABLE_TO_SET_COOKIE" : "UnableToSetCookie",
+ "UNEXPECTED_ALERT_OPEN" : "UnexpectedAlertOpen",
+ "NO_ALERT_OPEN_ERROR" : "NoAlertOpenError",
+ "SCRIPT_TIMEOUT" : "ScriptTimeout",
+ "INVALID_ELEMENT_COORDINATES" : "InvalidElementCoordinates",
+ "IME_NOT_AVAILABLE" : "IMENotAvailable",
+ "IME_ENGINE_ACTIVATION_FAILED" : "IMEEngineActivationFailed",
+ "INVALID_SELECTOR" : "InvalidSelector"
+};
+exports.FAILED_CMD_STATUS_CODES = {
+ "Success" : 0,
+ "NoSuchElement" : 7,
+ "NoSuchFrame" : 8,
+ "UnknownCommand" : 9,
+ "StaleElementReference" : 10,
+ "ElementNotVisible" : 11,
+ "InvalidElementState" : 12,
+ "UnknownError" : 13,
+ "ElementIsNotSelectable" : 15,
+ "JavaScriptError" : 17,
+ "XPathLookupError" : 19,
+ "Timeout" : 21,
+ "NoSuchWindow" : 23,
+ "InvalidCookieDomain" : 24,
+ "UnableToSetCookie" : 25,
+ "UnexpectedAlertOpen" : 26,
+ "NoAlertOpenError" : 27,
+ "ScriptTimeout" : 28,
+ "InvalidElementCoordinates" : 29,
+ "IMENotAvailable" : 30,
+ "IMEEngineActivationFailed" : 31,
+ "InvalidSelector" : 32
+};
+exports.FAILED_CMD_STATUS_CODES_NAMES = [];
+exports.FAILED_CMD_STATUS_CODES_NAMES[0] = "Success";
+exports.FAILED_CMD_STATUS_CODES_NAMES[7] = "NoSuchElement";
+exports.FAILED_CMD_STATUS_CODES_NAMES[8] = "NoSuchFrame";
+exports.FAILED_CMD_STATUS_CODES_NAMES[9] = "UnknownCommand";
+exports.FAILED_CMD_STATUS_CODES_NAMES[10] = "StaleElementReference";
+exports.FAILED_CMD_STATUS_CODES_NAMES[11] = "ElementNotVisible";
+exports.FAILED_CMD_STATUS_CODES_NAMES[12] = "InvalidElementState";
+exports.FAILED_CMD_STATUS_CODES_NAMES[13] = "UnknownError";
+exports.FAILED_CMD_STATUS_CODES_NAMES[15] = "ElementIsNotSelectable";
+exports.FAILED_CMD_STATUS_CODES_NAMES[17] = "JavaScriptError";
+exports.FAILED_CMD_STATUS_CODES_NAMES[19] = "XPathLookupError";
+exports.FAILED_CMD_STATUS_CODES_NAMES[21] = "Timeout";
+exports.FAILED_CMD_STATUS_CODES_NAMES[23] = "NoSuchWindow";
+exports.FAILED_CMD_STATUS_CODES_NAMES[24] = "InvalidCookieDomain";
+exports.FAILED_CMD_STATUS_CODES_NAMES[25] = "UnableToSetCookie";
+exports.FAILED_CMD_STATUS_CODES_NAMES[26] = "UnexpectedAlertOpen";
+exports.FAILED_CMD_STATUS_CODES_NAMES[27] = "NoAlertOpenError";
+exports.FAILED_CMD_STATUS_CODES_NAMES[28] = "ScriptTimeout";
+exports.FAILED_CMD_STATUS_CODES_NAMES[29] = "InvalidElementCoordinates";
+exports.FAILED_CMD_STATUS_CODES_NAMES[30] = "IMENotAvailable";
+exports.FAILED_CMD_STATUS_CODES_NAMES[31] = "IMEEngineActivationFailed";
+exports.FAILED_CMD_STATUS_CODES_NAMES[32] = "InvalidSelector";
+
+var _failedCommandHandle = function(res) {
+ // Generate response body
+ var body = {
+ "sessionId" : this.errorSessionId,
+ "status" : this.errorStatusCode,
+ "value" : {
+ "message" : this.message,
+ "screen" : this.errorScreenshot,
+ "class" : this.errorClassName
+ }
+ };
+
+ // Send it
+ res.statusCode = 500; //< 500 Internal Server Error
+ res.writeJSONAndClose(body);
+};
+
+// Failed Command Error Handler
+exports.createFailedCommandEH = function(errorName, errorMsg, req, session, className) {
+ var e = new Error();
+
+ e.name = errorName;
+ e.message = "Error Message => '" + errorMsg + "'\n" + " caused by Request => " + JSON.stringify(req);
+ e.errorStatusCode = exports.FAILED_CMD_STATUS_CODES[errorName] || 13; //< '13' Unkown Error
+ e.errorSessionId = session.getId() || null;
+ e.errorClassName = className || "unknown";
+ e.errorScreenshot = (session.getCapabilities().takesScreenshot && session.getCurrentWindow() !== null) ?
+ session.getCurrentWindow().renderBase64("png") : "";
+ e.handle = _failedCommandHandle;
+
+ return e;
+};
+exports.handleFailedCommandEH = function(errorName, errorMsg, req, res, session, className) {
+ exports.createFailedCommandEH(errorName, errorMsg, req, session, className).handle(res);
+};
View
79 src/ghostdriver/ghostdriver.qrc
@@ -0,0 +1,79 @@
+<RCC>
+ <qresource prefix="ghostdriver/">
+ <file>errors.js</file>
+ <file>hub_register.js</file>
+ <file>inputs.js</file>
+ <file>main.js</file>
+ <file>request_handlers/request_handler.js</file>
+ <file>request_handlers/router_request_handler.js</file>
+ <file>request_handlers/session_manager_request_handler.js</file>
+ <file>request_handlers/session_request_handler.js</file>
+ <file>request_handlers/shutdown_request_handler.js</file>
+ <file>request_handlers/status_request_handler.js</file>
+ <file>request_handlers/webelement_request_handler.js</file>
+ <file>session.js</file>
+ <file>third_party/parseuri.js</file>
+ <file>third_party/uuid.js</file>
+ <file>third_party/webdriver-atoms/active_element.js</file>
+ <file>third_party/webdriver-atoms/clear.js</file>
+ <file>third_party/webdriver-atoms/clear_local_storage.js</file>
+ <file>third_party/webdriver-atoms/clear_session_storage.js</file>
+ <file>third_party/webdriver-atoms/click.js</file>
+ <file>third_party/webdriver-atoms/default_content.js</file>
+ <file>third_party/webdriver-atoms/deps.js</file>
+ <file>third_party/webdriver-atoms/double_click.js</file>
+ <file>third_party/webdriver-atoms/drag.js</file>
+ <file>third_party/webdriver-atoms/execute_async_script.js</file>
+ <file>third_party/webdriver-atoms/execute_script.js</file>
+ <file>third_party/webdriver-atoms/execute_sql.js</file>
+ <file>third_party/webdriver-atoms/find_element.js</file>
+ <file>third_party/webdriver-atoms/find_elements.js</file>
+ <file>third_party/webdriver-atoms/focus_on_element.js</file>
+ <file>third_party/webdriver-atoms/frame_by_id_or_name.js</file>
+ <file>third_party/webdriver-atoms/frame_by_index.js</file>
+ <file>third_party/webdriver-atoms/get_appcache_status.js</file>
+ <file>third_party/webdriver-atoms/get_attribute.js</file>
+ <file>third_party/webdriver-atoms/get_attribute_value.js</file>
+ <file>third_party/webdriver-atoms/get_current_position.js</file>
+ <file>third_party/webdriver-atoms/get_element_from_cache.js</file>
+ <file>third_party/webdriver-atoms/get_frame_window.js</file>
+ <file>third_party/webdriver-atoms/get_in_view_location.js</file>
+ <file>third_party/webdriver-atoms/get_local_storage_item.js</file>
+ <file>third_party/webdriver-atoms/get_local_storage_keys.js</file>
+ <file>third_party/webdriver-atoms/get_local_storage_size.js</file>
+ <file>third_party/webdriver-atoms/get_location.js</file>
+ <file>third_party/webdriver-atoms/get_location_in_view.js</file>
+ <file>third_party/webdriver-atoms/get_session_storage_item.js</file>
+ <file>third_party/webdriver-atoms/get_session_storage_keys.js</file>
+ <file>third_party/webdriver-atoms/get_session_storage_size.js</file>
+ <file>third_party/webdriver-atoms/get_size.js</file>
+ <file>third_party/webdriver-atoms/get_text.js</file>
+ <file>third_party/webdriver-atoms/get_top_left_coordinates.js</file>
+ <file>third_party/webdriver-atoms/get_value_of_css_property.js</file>
+ <file>third_party/webdriver-atoms/get_window_position.js</file>
+ <file>third_party/webdriver-atoms/get_window_size.js</file>
+ <file>third_party/webdriver-atoms/is_displayed.js</file>
+ <file>third_party/webdriver-atoms/is_enabled.js</file>
+ <file>third_party/webdriver-atoms/is_online.js</file>
+ <file>third_party/webdriver-atoms/is_selected.js</file>
+ <file>third_party/webdriver-atoms/lastupdate</file>
+ <file>third_party/webdriver-atoms/move_mouse.js</file>
+ <file>third_party/webdriver-atoms/pinch.js</file>
+ <file>third_party/webdriver-atoms/remove_local_storage_item.js</file>
+ <file>third_party/webdriver-atoms/remove_session_storage_item.js</file>
+ <file>third_party/webdriver-atoms/right_click.js</file>
+ <file>third_party/webdriver-atoms/rotate.js</file>
+ <file>third_party/webdriver-atoms/scroll_into_view.js</file>
+ <file>third_party/webdriver-atoms/scroll_mouse.js</file>
+ <file>third_party/webdriver-atoms/set_local_storage_item.js</file>
+ <file>third_party/webdriver-atoms/set_session_storage_item.js</file>
+ <file>third_party/webdriver-atoms/set_window_position.js</file>
+ <file>third_party/webdriver-atoms/set_window_size.js</file>
+ <file>third_party/webdriver-atoms/submit.js</file>
+ <file>third_party/webdriver-atoms/swipe.js</file>
+ <file>third_party/webdriver-atoms/tap.js</file>
+ <file>third_party/webdriver-atoms/type.js</file>
+ <file>webdriver_atoms.js</file>
+ <file>webelementlocator.js</file>
+ </qresource>
+</RCC>
View
84 src/ghostdriver/hub_register.js
@@ -0,0 +1,84 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+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.
+*/
+
+/* generate node configuration for this node */
+var nodeconf = function(ip, port, hub) {
+ var ref$, hubHost, hubPort;
+ ref$ = hub.match(/([\w\d\.]+):(\d+)/), hubHost = ref$[1], hubPort = ref$[2];
+ hubPort = +hubPort;
+ 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
+ }
+ };
+};
+
+module.exports = {
+ register: function(ip, port, hub) {
+ var page = require('webpage').create();
+ port = +port;
+ if(!hub.match(/\/$/)) {
+ hub += '/';
+ }
+
+ /* Register with selenium grid server */
+ page.open(hub + 'grid/register', {
+ operation: 'post',
+ data: JSON.stringify(nodeconf(ip, port, hub)),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }, function(status) {
+ if(status !== 'success') {
+ console.error("Unable to contact grid " + hub + ": " + status);
+ phantom.exit(1);
+ }
+ if(page.framePlainText !== "ok") {
+ console.error("Problem registering with grid " + hub + ": " + page.content);
+ phantom.exit(1);
+ }
+ console.log("Registered with grid hub: " + hub + " (" + page.framePlainText + ")");
+ });
+ }
+};
View
344 src/ghostdriver/inputs.js
@@ -0,0 +1,344 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+Copyright (c) 2012, Jim Evans <james.h.evans.jr@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+ghostdriver.Inputs = function () {
+ // private:
+ var
+ _mousePos = { x: 0, y: 0 },
+ _keyboardState = {},
+ _specialKeys = {
+ '\uE000': "Escape", // NULL
+ '\uE001': "Cancel", // Cancel
+ '\uE002': "F1", // Help
+ '\uE003': "Backspace", // Backspace
+ '\uE004': "Tab", // Tab
+ '\uE005': "Clear", // Clear
+ '\uE006': "\n",
+ '\uE007': "\n",
+ '\uE008': "Shift", // Shift
+ '\uE009': "Control", // Control
+ '\uE00A': "Alt", // Alt
+ '\uE00B': "Pause", // Pause
+ '\uE00C': "Escape", // Escape
+ '\uE00D': "Space", // Space
+ '\uE00E': "PageUp", // PageUp
+ '\uE00F': "PageDown", // PageDown
+ '\uE010': "End", // End
+ '\uE011': "Home", // Home
+ '\uE012': "Left", // Left arrow
+ '\uE013': "Up", // Up arrow
+ '\uE014': "Right", // Right arrow
+ '\uE015': "Down", // Down arrow
+ '\uE016': "Insert", // Insert
+ '\uE017': "Delete", // Delete
+ '\uE018': ";", // Semicolon
+ '\uE019': "=", // Equals
+ '\uE01A': "0", // Numpad 0
+ '\uE01B': "1", // Numpad 1
+ '\uE01C': "2", // Numpad 2
+ '\uE01D': "3", // Numpad 3
+ '\uE01E': "4", // Numpad 4
+ '\uE01F': "5", // Numpad 5
+ '\uE020': "6", // Numpad 6
+ '\uE021': "7", // Numpad 7
+ '\uE022': "8", // Numpad 8
+ '\uE023': "9", // Numpad 9
+ '\uE024': "*", // Multiply
+ '\uE025': "+", // Add
+ '\uE026': ",", // Separator
+ '\uE027': "-", // Subtract
+ '\uE028': ".", // Decimal
+ '\uE029': "/", // Divide
+ '\uE031': "F1", // F1
+ '\uE032': "F2", // F2
+ '\uE033': "F3", // F3
+ '\uE034': "F4", // F4
+ '\uE035': "F5", // F5
+ '\uE036': "F6", // F6
+ '\uE037': "F7", // F7
+ '\uE038': "F8", // F8
+ '\uE039': "F9", // F9
+ '\uE03A': "F10", // F10
+ '\uE03B': "F11", // F11
+ '\uE03C': "F12", // F12
+ '\uE03D': "Meta" // Command/Meta
+ },
+
+ _implicitShiftKeys = {
+ "A": "a",
+ "B": "b",
+ "C": "c",
+ "D": "d",
+ "E": "e",
+ "F": "f",
+ "G": "g",
+ "H": "h",
+ "I": "i",
+ "J": "j",
+ "K": "k",
+ "L": "l",
+ "M": "m",
+ "N": "n",
+ "O": "o",
+ "P": "p",
+ "Q": "q",
+ "R": "r",
+ "S": "s",
+ "T": "t",
+ "U": "u",
+ "V": "v",
+ "W": "w",
+ "X": "x",
+ "Y": "y",
+ "Z": "z",
+ "!": "1",
+ "@": "2",
+ "#": "3",
+ "$": "4",
+ "%": "5",
+ "^": "6",
+ "&": "7",
+ "*": "8",
+ "(": "9",
+ ")": "0",
+ "_": "-",
+ "+": "=",
+ "{": "[",
+ "}": "]",
+ "|": "\\",
+ ":": ";",
+ "<": ",",
+ ">": ".",
+ "?": "/",
+ "~": "`",
+ "\"": "'"
+ },
+
+ _shiftKeys = {
+ "a": "A",
+ "b": "B",
+ "c": "C",
+ "d": "D",
+ "e": "E",
+ "f": "F",
+ "g": "G",
+ "h": "H",
+ "i": "I",
+ "j": "J",
+ "k": "K",
+ "l": "L",
+ "m": "M",
+ "n": "N",
+ "o": "O",
+ "p": "P",
+ "q": "Q",
+ "r": "R",
+ "s": "S",
+ "t": "T",
+ "u": "U",
+ "v": "V",
+ "w": "W",
+ "x": "X",
+ "y": "Y",
+ "z": "Z",
+ "1": "!",
+ "2": "@",
+ "3": "#",
+ "4": "$",
+ "5": "%",
+ "6": "^",
+ "7": "&",
+ "8": "*",
+ "9": "(",
+ "0": ")",
+ "-": "_",
+ "=": "+",
+ "[": "{",
+ "]": "}",
+ "\\": "|",
+ ";": ":",
+ ",": "<",
+ ".": ">",
+ "/": "?",
+ "`": "~",
+ "'": "\""
+ },
+
+ _modifierKeyValues = {
+ "SHIFT": 0x02000000, // A Shift key on the keyboard is pressed.
+ "CONTROL": 0x04000000, // A Ctrl key on the keyboard is pressed.
+ "ALT": 0x08000000, // An Alt key on the keyboard is pressed.
+ "META": 0x10000000, // A Meta key on the keyboard is pressed.
+ "NUMPAD": 0x20000000 // Keypad key.
+ },
+
+ _currentModifierKeys = 0,
+
+ _isModifierKey = function (key) {
+ return key === "\uE008" || key === "\uE009" || key === "\uE00A" || key === "\uE03D";
+ },
+
+ _isModifierKeyPressed = function (key) {
+ return _currentModifierKeys & _modifierKeyValues[_specialKeys[key].toUpperCase()];
+ },
+
+ _sendKeys = function (session, keys) {
+ var keySequence = keys.split('');
+ for (var i = 0; i < keySequence.length; i++) {
+ var key = keys[i];
+ var actualKey = _translateKey(session, key);
+
+ if (key === '\uE000') {
+ _clearModifierKeys(session);
+ } else {
+ if (_isModifierKey(key)) {
+ if (_isModifierKeyPressed(key)) {
+ _keyUp(session, actualKey);
+ } else {
+ _keyDown(session, actualKey);
+ }
+ } else {
+ if (_implicitShiftKeys.hasOwnProperty(actualKey)) {
+ session.getCurrentWindow().sendEvent("keydown", _translateKey(session, "\uE008"));
+ _pressKey(session, actualKey);
+ session.getCurrentWindow().sendEvent("keyup", _translateKey(session, "\uE008"));
+ } else {
+ if ((_currentModifierKeys & _modifierKeyValues.SHIFT) && _shiftKeys.hasOwnProperty(actualKey)) {
+ _pressKey(session, _shiftKeys[actualKey]);
+ } else {
+ _pressKey(session, actualKey);
+ }
+ }
+ }
+ }
+ }
+ },
+
+ _clearModifierKeys = function (session) {
+ if (_currentModifierKeys & _modifierKeyValues.SHIFT) {
+ _keyUp(session, _translateKey(session, "\uE008"));
+ }
+ if (_currentModifierKeys & _modifierKeyValues.CONTROL) {
+ _keyUp(session, _translateKey(session, "\uE009"));
+ }
+ if (_currentModifierKeys & _modifierKeyValues.ALT) {
+ _keyUp(session, _translateKey(session, "\uE00A"));
+ }
+ },
+
+ _updateModifierKeys = function (modifierKeyValue, on) {
+ if (on) {
+ _currentModifierKeys = _currentModifierKeys | modifierKeyValue;
+ } else {
+ _currentModifierKeys = _currentModifierKeys & ~modifierKeyValue;
+ }
+ },
+
+ _translateKey = function (session, key) {
+ var actualKey = key;
+ var 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];
+ }
+ }
+ return actualKey;
+ },
+
+ _pressKey = function (session, key) {
+ // translate WebDriver key value to key code.
+ _keyEvent(session, "keypress", key);
+ },
+
+ _keyDown = function (session, key) {
+ _keyEvent(session, "keydown", key);
+ if (key == _translateKey(session, "\uE008")) {
+ _updateModifierKeys(_modifierKeyValues.SHIFT, true);
+ } else if (key == _translateKey(session, "\uE009")) {
+ _updateModifierKeys(_modifierKeyValues.CONTROL, true);
+ } else if (key == _translateKey(session, "\uE00A")) {
+ _updateModifierKeys(_modifierKeyValues.ALT, true);
+ }
+ },
+
+ _keyUp = function (session, key) {
+ if (key == _translateKey(session, "\uE008")) {
+ _updateModifierKeys(_modifierKeyValues.SHIFT, false);
+ } else if (key == _translateKey(session, "\uE009")) {
+ _updateModifierKeys(_modifierKeyValues.CONTROL, false);
+ } else if (key == _translateKey(session, "\uE00A")) {
+ _updateModifierKeys(_modifierKeyValues.ALT, false);
+ }
+ _keyEvent(session, "keyup", key);
+ },
+
+ _mouseClick = function (session, coords) {
+ _mouseMove(session, coords);
+ _mouseButtonEvent(session, "click", "left");
+ },
+
+ _mouseMove = function (session, coords) {
+ session.getCurrentWindow().sendEvent("mousemove", coords.x, coords.y);
+ _mousePos = { x: coords.x, y: coords.y };
+ },
+
+ _mouseButtonDown = function (session, button) {
+ _mouseButtonClick(session, "mousedown", button);
+ },
+
+ _mouseButtonUp = function (session, button) {
+ _mouseButtonClick(session, "mouseUp", button);
+ },
+
+ _keyEvent = function (session, eventType, keyCode) {
+ eventType = eventType || "keypress";
+ session.getCurrentWindow().sendEvent(eventType, keyCode, null, null, _currentModifierKeys);
+ },
+
+ _mouseButtonEvent = function (session, eventType, button) {
+ button = button || "left";
+ eventType = eventType || "click";
+ session.getCurrentWindow().sendEvent(eventType,
+ _mousePos.x, _mousePos.y, //< x, y
+ button, _currentModifierKeys);
+ };
+
+ return {
+ getCurrentCoordinates: function () { return _mousePos; },
+ mouseClick: _mouseClick,
+ mouseMove: _mouseMove,
+ mouseButtonDown: _mouseButtonDown,
+ mouseButtonUp: _mouseButtonUp,
+ mouseButtonClick: _mouseButtonEvent,
+ sendKeys: _sendKeys,
+ clearModifierKeys: _clearModifierKeys
+ };
+};
View
7 src/ghostdriver/lastupdate
@@ -0,0 +1,7 @@
+2012-11-18 00:51:11
+
+commit 7c23d7684929bf26deb5be901ba1e5bc51f3e48a
+Author: Ivan De Marino <ivan.de.marino@gmail.com>
+Date: Sun Nov 18 00:42:32 2012 +0000
+
+ Avoid listing directories into `ghostdriver.qrc`
View
81 src/ghostdriver/main.js
@@ -0,0 +1,81 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+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.
+*/
+
+// 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')
+ },
+ server = require('webserver').create(),
+ router,
+ parseURI,
+ listenOn,
+ listenOnIp = "127.0.0.1",
+ listenOnPort = "8080";
+
+// Enable "strict mode" for the 'parseURI' library
+parseURI = require("./third_party/parseuri.js");
+parseURI.options.strictMode = true;
+
+phantom.injectJs("session.js");
+phantom.injectJs("inputs.js");
+phantom.injectJs("request_handlers/request_handler.js");
+phantom.injectJs("request_handlers/status_request_handler.js");
+phantom.injectJs("request_handlers/shutdown_request_handler.js");
+phantom.injectJs("request_handlers/session_manager_request_handler.js");
+phantom.injectJs("request_handlers/session_request_handler.js");
+phantom.injectJs("request_handlers/webelement_request_handler.js");
+phantom.injectJs("request_handlers/router_request_handler.js");
+phantom.injectJs("webelementlocator.js");
+
+// 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 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]);
+ }
+} else {
+ console.error("ERROR: Could not start Ghost Driver");
+ phantom.exit(1);
+}
View
205 src/ghostdriver/request_handlers/request_handler.js
@@ -0,0 +1,205 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+ghostdriver.RequestHandler = function() {
+ // private:
+ var
+ _errors = require("./errors.js"),
+ _handle = function(request, response) {
+ // NOTE: Some language bindings result in a malformed "post" object.
+ // This might have to do with PhantomJS poor WebServer implementation.
+ // Here we override "request.post" with the "request.postRaw" that
+ // is usually left intact.
+ if (request.hasOwnProperty("postRaw")) {
+ request["post"] = request["postRaw"];
+ }
+
+ _decorateRequest(request);
+ _decorateResponse(response);
+ },
+
+ _reroute = function(request, response, prefixToRemove) {
+ // Store the original URL before re-routing in 'request.urlOriginal':
+ // This is done only for requests never re-routed.
+ // We don't want to override the original URL during a second re-routing.
+ if (typeof(request.urlOriginal) === "undefined") {
+ request.urlOriginal = request.url;
+ }
+
+ // Rebase the "url" to start from AFTER the given prefix to remove
+ request.url = request.urlParsed.source.substr((prefixToRemove).length);
+ // Re-decorate the Request object
+ _decorateRequest(request);
+
+ // Handle the re-routed request
+ this.handle(request, response);
+ },
+
+ _decorateRequest = function(request) {
+ request.urlParsed = require("./third_party/parseuri.js").parse(request.url);
+ },
+
+ _writeJSONDecorator = function(obj) {
+ this.write(JSON.stringify(obj));
+ },
+
+ _successDecorator = function(sessionId, value) {
+ this.statusCode = 200;
+ if (arguments.length > 0) {
+ // write something, only if there is something to write
+ this.writeJSONAndClose(_buildSuccessResponseBody(sessionId, value));
+ } else {
+ this.closeGracefully();
+ }
+ },
+
+ _writeAndCloseDecorator = function(body) {
+ this.setHeader("Content-Length", unescape(encodeURIComponent(body)).length);
+ this.write(body);
+ this.close();
+ },
+
+ _writeJSONAndCloseDecorator = function(obj) {
+ var objStr = JSON.stringify(obj);
+ this.setHeader("Content-Length", unescape(encodeURIComponent(objStr)).length);
+ this.write(objStr);
+ this.close();
+ },
+
+ _respondBasedOnResultDecorator = function(session, req, result) {
+ //console.log("respondBasedOnResult => "+JSON.stringify(result));
+
+ // Convert string to JSON
+ if (typeof(result) === "string") {
+ try {
+ result = JSON.parse(result);
+ } catch (e) {
+ // In case the conversion fails, report and "Invalid Command Method" error
+ _errors.handleInvalidReqInvalidCommandMethodEH(req, this);
+ }
+ }
+
+ // In case the JSON doesn't contain the expected fields
+ if (result === null ||
+ typeof(result) === "undefined" ||
+ typeof(result) !== "object" ||
+ typeof(result.status) === "undefined" ||
+ typeof(result.value) === "undefined") {
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.UNKNOWN_ERROR,
+ "Command failed without producing the expected error report",
+ req,
+ this,
+ session,
+ "ReqHand");
+ return;
+ }
+
+ // An error occurred but we got an error report to use
+ if (result.status !== 0) {
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS_CODES_NAMES[result.status],
+ result.value.message,
+ req,
+ this,
+ session,
+ "ReqHand");
+ return;
+ }
+
+ // If we arrive here, everything should be fine, birds are singing, the sky is blue
+ this.success(session.getId(), result.value);
+ },
+
+ _decorateResponse = function(response) {
+ response.setHeader("Cache", "no-cache");
+ response.setHeader("Content-Type", "application/json;charset=UTF-8");
+ response.writeAndClose = _writeAndCloseDecorator;
+ response.writeJSON = _writeJSONDecorator;
+ response.writeJSONAndClose = _writeJSONAndCloseDecorator;
+ response.success = _successDecorator;
+ response.respondBasedOnResult = _respondBasedOnResultDecorator;
+ },
+
+ _buildResponseBody = function(sessionId, value, statusCode) {
+ // Need to check for undefined to prevent errors when trying to return boolean false
+ if(typeof(value) === "undefined") value = {};
+ return {
+ "sessionId" : sessionId || null,
+ "status" : statusCode || 0, //< '0' is Success
+ "value" : value
+ };
+ },
+
+ _buildSuccessResponseBody = function(sessionId, value) {
+ return _buildResponseBody(sessionId, value, 0); //< '0' is Success
+ },
+
+ _getSessionCurrWindow = function(session, req) {
+ return _getSessionWindow(null, session, req);
+ },
+
+ _getSessionWindow = function(handleOrName, session, req) {
+ var win,
+ errorMsg;
+
+ // Fetch the right window
+ win = handleOrName === null ?
+ session.getCurrentWindow() : //< current window
+ session.getWindow(handleOrName); //< window by handle
+ if (win !== null) {
+ return win;
+ }
+
+ errorMsg = handleOrName === null ?
+ "Currently Window handle/name is invalid (closed?)" :
+ "Window handle/name '"+handleOrName+"' is invalid (closed?)";
+
+ // Report the error throwing the appropriate exception
+ throw _errors.createFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_WINDOW, //< error name
+ errorMsg, //< error message
+ req, //< request
+ session, //< session
+ "SessionReqHand"); //< class name
+ };
+
+ // public:
+ return {
+ handle : _handle,
+ reroute : _reroute,
+ buildResponseBody : _buildResponseBody,
+ buildSuccessResponseBody : _buildSuccessResponseBody,
+ decorateRequest : _decorateRequest,
+ decorateResponse : _decorateResponse,
+ errors : _errors,
+ getSessionWindow : _getSessionWindow,
+ getSessionCurrWindow : _getSessionCurrWindow
+ };
+};
View
106 src/ghostdriver/request_handlers/router_request_handler.js
@@ -0,0 +1,106 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+/**
+ * This Class does first level routing: based on the REST Path, sends Request and Response to the right Request Handler.
+ */
+ghostdriver.RouterReqHand = function() {
+ // private:
+ var
+ _protoParent = ghostdriver.RouterReqHand.prototype,
+ _statusRH = new ghostdriver.StatusReqHand(),
+ _shutdownRH = new ghostdriver.ShutdownReqHand(),
+ _sessionManRH = new ghostdriver.SessionManagerReqHand(),
+ _const = {
+ STATUS : "status",
+ SESSION : "session",
+ SESSIONS : "sessions",
+ SESSION_DIR : "/session/",
+ SHUTDOWN : "shutdown"
+ },
+ _errors = _protoParent.errors,
+
+ _handle = function(req, res) {
+ var session,
+ sessionRH;
+
+ // Invoke parent implementation
+ _protoParent.handle.call(this, req, res);
+
+ // console.log("Request => " + JSON.stringify(req, null, ' '));
+
+ try {
+ if (req.urlParsed.directory.match(/^\/wd\/hub/)) {
+ req.url = req.urlParsed.source.replace(/^\/wd\/hub/, '');
+ req.urlParsed = require("./third_party/parseuri.js").parse(req.url);
+ }
+ if (req.urlParsed.file === _const.STATUS) { // GET '/status'
+ _statusRH.handle(req, res);
+ } else if (req.urlParsed.file === _const.SHUTDOWN) { // GET '/shutdown'
+ _shutdownRH.handle(req, res);
+ phantom.exit();
+ } else if (req.urlParsed.file === _const.SESSION || // POST '/session'
+ req.urlParsed.file === _const.SESSIONS || // GET '/sessions'
+ req.urlParsed.directory === _const.SESSION_DIR) { // GET or DELETE '/session/:id'
+ _sessionManRH.handle(req, res);
+ } else if (req.urlParsed.chunks[0] === _const.SESSION) { // GET, POST or DELETE '/session/:id/...'
+ // Retrieve session
+ session = _sessionManRH.getSession(req.urlParsed.chunks[1]);
+
+ if (session !== null) {
+ // Create a new Session Request Handler and re-route the request to it
+ sessionRH = _sessionManRH.getSessionReqHand(req.urlParsed.chunks[1]);
+ _protoParent.reroute.call(sessionRH, req, res, _const.SESSION_DIR + session.getId());
+ } else {
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ }
+ } else {
+ throw _errors.createInvalidReqUnknownCommandEH(req);
+ }
+ } catch (e) {
+ console.error("Error => " + JSON.stringify(e, null, ' '));
+
+ if (typeof(e.handle) === "function") {
+ e.handle(res);
+ } else {
+ // This should never happen, if we handle all the possible error scenario
+ res.statusCode = 404; //< "404 Not Found"
+ res.setHeader("Content-Type", "text/plain");
+ res.writeAndClose(e.name + " - " + e.message);
+ }
+ }
+ };
+
+ // public:
+ return {
+ handle : _handle
+ };
+};
+// prototype inheritance:
+ghostdriver.RouterReqHand.prototype = new ghostdriver.RequestHandler();
View
180 src/ghostdriver/request_handlers/session_manager_request_handler.js
@@ -0,0 +1,180 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+ghostdriver.SessionManagerReqHand = function() {
+ // private:
+ var
+ _protoParent = ghostdriver.SessionManagerReqHand.prototype,
+ _sessions = {}, //< will store key/value pairs like 'SESSION_ID : SESSION_OBJECT'
+ _sessionRHs = {},
+ _errors = _protoParent.errors,
+ _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT = 60000,
+
+ _handle = function(req, res) {
+ _protoParent.handle.call(this, req, res);
+
+ if (req.urlParsed.file === "session" && req.method === "POST") {
+ _postNewSessionCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === "sessions" && req.method === "GET") {
+ _getActiveSessionsCommand(req, res);
+ return;
+ } else if (req.urlParsed.directory === "/session/") {
+ if (req.method === "GET") {
+ _getSessionCapabilitiesCommand(req, res);
+ } else if (req.method === "DELETE") {
+ _deleteSessionCommand(req, res);
+ }
+ return;
+ }
+
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ },
+
+ _postNewSessionCommand = function(req, res) {
+ var newSession,
+ postObj = JSON.parse(req.post);
+
+ if (typeof(postObj) === "object") {
+ // Create and store a new Session
+ newSession = new ghostdriver.Session(postObj.desiredCapabilities);
+ _sessions[newSession.getId()] = newSession;
+
+ // console.log("New Session Created: " + newSession.getId());
+
+ // Redirect to the newly created Session
+ res.statusCode = 303; //< "303 See Other"
+ res.setHeader("Location", "http://" + req.headers.Host + "/wd/hub/session/"+newSession.getId());
+ res.closeGracefully();
+ return;
+ }
+
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ },
+
+ _getActiveSessionsCommand = function(req, res) {
+ var activeSessions = [],
+ sessionId;
+
+ // Create array of format '[{ "id" : SESSION_ID, "capabilities" : SESSION_CAPABILITIES_OBJECT }]'
+ for (sessionId in _sessions) {
+ activeSessions.push({
+ "id" : sessionId,
+ "capabilities" : _sessions[sessionId].getCapabilities()
+ });
+ }
+
+ res.success(null, activeSessions);
+ },
+
+ _deleteSession = function(sessionId) {
+ if (typeof(_sessions[sessionId]) !== "undefined") {
+ // Prepare the session to be deleted
+ _sessions[sessionId].aboutToDelete();
+ // Delete the session and the handler
+ delete _sessions[sessionId];
+ delete _sessionRHs[sessionId];
+ }
+ },
+
+ _deleteSessionCommand = function(req, res) {
+ var sId = req.urlParsed.file;
+
+ if (sId === "")
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+
+ if (typeof(_sessions[sId]) !== "undefined") {
+ _deleteSession(sId);
+ res.success(sId);
+ } else {
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ }
+ },
+
+ _getSessionCapabilitiesCommand = function(req, res) {
+ var sId = req.urlParsed.file,
+ session;
+
+ if (sId === "")
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+
+ session = _getSession(sId);
+ if (session !== null) {
+ res.success(sId, _sessions[sId].getCapabilities());
+ } else {
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ }
+ },
+
+ _getSession = function(sessionId) {
+ if (typeof(_sessions[sessionId]) !== "undefined") {
+ return _sessions[sessionId];
+ }
+ return null;
+ },
+
+ _getSessionReqHand = function(sessionId) {
+ if (_getSession(sessionId) !== null) {
+ // The session exists: what about the relative Session Request Handler?
+ if (typeof(_sessionRHs[sessionId]) === "undefined") {
+ _sessionRHs[sessionId] = new ghostdriver.SessionReqHand(_getSession(sessionId));
+ }
+ return _sessionRHs[sessionId];
+ }
+ return null;
+ },
+
+ _cleanupWindowlessSessions = function() {
+ var sId;
+
+ // Do this cleanup only if there are sessions
+ if (Object.keys(_sessions).length > 0) {
+ console.log("Asynchronous Sessions cleanup 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!");
+ }
+ }
+ }
+ };
+
+ // Regularly cleanup un-used sessions
+ setInterval(_cleanupWindowlessSessions, _CLEANUP_WINDOWLESS_SESSIONS_TIMEOUT); //< every 60s
+
+ // public:
+ return {
+ handle : _handle,
+ getSession : _getSession,
+ getSessionReqHand : _getSessionReqHand
+ };
+};
+// prototype inheritance:
+ghostdriver.SessionManagerReqHand.prototype = new ghostdriver.RequestHandler();
View
762 src/ghostdriver/request_handlers/session_request_handler.js
@@ -0,0 +1,762 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+Copyright (c) 2012, Alex Anderson <@alxndrsn>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+ghostdriver.SessionReqHand = function(session) {
+ // private:
+ var
+ _session = session,
+ _protoParent = ghostdriver.SessionReqHand.prototype,
+ _locator = new ghostdriver.WebElementLocator(session),
+ _const = {
+ URL : "url",
+ ELEMENT : "element",
+ ELEMENTS : "elements",
+ ELEMENT_DIR : "/element/",
+ ACTIVE : "active",
+ TITLE : "title",
+ WINDOW : "window",
+ CURRENT : "current",
+ SIZE : "size",
+ POSITION : "position",
+ MAXIMIZE : "maximize",
+ FORWARD : "forward",
+ BACK : "back",
+ REFRESH : "refresh",
+ EXECUTE : "execute",
+ EXECUTE_ASYNC : "execute_async",
+ SCREENSHOT : "screenshot",
+ TIMEOUTS : "timeouts",
+ TIMEOUTS_DIR : "/timeouts/",
+ ASYNC_SCRIPT : "async_script",
+ IMPLICIT_WAIT : "implicit_wait",
+ WINDOW_HANDLE : "window_handle",
+ WINDOW_HANDLES : "window_handles",
+ FRAME : "frame",
+ SOURCE : "source",
+ COOKIE : "cookie",
+ KEYS : "keys",
+ MOVE_TO : "moveto",
+ CLICK : "click",
+ BUTTON_DOWN : "buttondown",
+ BUTTON_UP : "buttonup",
+ DOUBLE_CLICK : "doubleclick"
+ },
+ _errors = _protoParent.errors,
+
+ _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") {
+ _getUrlCommand(req, res);
+ } else if (req.method === "POST") {
+ _postUrlCommand(req, res);
+ }
+ return;
+ } else if (req.urlParsed.file === _const.SCREENSHOT && req.method === "GET") {
+ _getScreenshotCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.WINDOW) { //< ".../window"
+ if (req.method === "DELETE") {
+ _deleteWindowCommand(req, res); //< close window
+ } else if (req.method === "POST") {
+ _postWindowCommand(req, res); //< change focus to the given window
+ }
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.WINDOW) {
+ _doWindowHandleCommands(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.ELEMENT && req.method === "POST" && req.urlParsed.chunks.length === 1) { //< ".../element"
+ _locator.handleLocateCommand(req, res, _locator.locateElement);
+ return;
+ } else if (req.urlParsed.file === _const.ELEMENTS && req.method === "POST" && req.urlParsed.chunks.length === 1) { //< ".../elements"
+ _locator.handleLocateCommand(req, res, _locator.locateElements);
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.ELEMENT && req.urlParsed.chunks[1] === _const.ACTIVE && req.method === "POST") { //< ".../element/active"
+ _locator.handleLocateCommand(req, res, _locator.locateActiveElement);
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.ELEMENT) { //< ".../element/:elementId/COMMAND"
+ // Get the WebElementRH and, if found, re-route request to it
+ element = new ghostdriver.WebElementReqHand(req.urlParsed.chunks[1], _session);
+ if (element !== null) {
+ _protoParent.reroute.call(element, req, res, _const.ELEMENT_DIR + req.urlParsed.chunks[1]);
+ } else {
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ }
+ return;
+ } else if (req.urlParsed.file === _const.TITLE && req.method === "GET") { //< ".../title"
+ // Get the current Page title
+ _getTitleCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.KEYS && req.method === "POST") {
+ _postKeysCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.FORWARD && req.method === "POST") {
+ _forwardCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.BACK && req.method === "POST") {
+ _backCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.REFRESH && req.method === "POST") {
+ _refreshCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.EXECUTE && req.method === "POST") {
+ _executeCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.EXECUTE_ASYNC && req.method === "POST") {
+ _executeAsyncCommand(req, res);
+ return;
+ } else if ((req.urlParsed.file === _const.TIMEOUTS || req.urlParsed.directory === _const.TIMEOUTS_DIR) && req.method === "POST") {
+ _postTimeout(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.WINDOW_HANDLE && req.method === "GET") {
+ _getWindowHandle(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.WINDOW_HANDLES && req.method === "GET") {
+ _getWindowHandles(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.FRAME && req.method === "POST") {
+ _postFrameCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.SOURCE && req.method === "GET") {
+ _getSourceCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.MOVE_TO && req.method === "POST") {
+ _postMouseMoveToCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.CLICK && req.method === "POST") {
+ _postMouseClickCommand(req, res, "click");
+ return;
+ } else if (req.urlParsed.file === _const.BUTTON_DOWN && req.method === "POST") {
+ _postMouseClickCommand(req, res, "mousedown");
+ return;
+ } else if (req.urlParsed.file === _const.BUTTON_UP && req.method === "POST") {
+ _postMouseClickCommand(req, res, "mouseup");
+ return;
+ } else if (req.urlParsed.file === _const.DOUBLE_CLICK && req.method === "POST") {
+ _postMouseClickCommand(req, res, "doubleclick");
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.COOKIE) {
+ if (req.method === "POST") {
+ _postCookieCommand(req, res);
+ } else if (req.method === "GET") {
+ _getCookieCommand(req, res);
+ } else if(req.method === "DELETE") {
+ _deleteCookieCommand(req, res);
+ }
+ return;
+ }
+
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ },
+
+ _createOnSuccessHandler = function(res) {
+ return function (status) {
+ res.success(_session.getId());
+ };
+ },
+
+ _doWindowHandleCommands = function(req, res) {
+ var windowHandle,
+ command,
+ targetWindow;
+
+ // console.log("_doWindowHandleCommands");
+ // console.log(JSON.stringify(req, null, " "));
+
+ // Ensure all the parameters are provided
+ if (req.urlParsed.chunks.length === 3) {
+ windowHandle = req.urlParsed.chunks[1];
+ command = req.urlParsed.chunks[2];
+
+ // Fetch the right window
+ if (windowHandle === _const.CURRENT) {
+ targetWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+ } else {
+ targetWindow = _protoParent.getSessionWindow.call(this, windowHandle, _session, req);
+ }
+
+ // Act on the window (page)
+ if(command === _const.SIZE && req.method === "POST") {
+ _postWindowSizeCommand(req, res, targetWindow);
+ return;
+ } else if(command === _const.SIZE && req.method === "GET") {
+ _getWindowSizeCommand(req, res, targetWindow);
+ return;
+ } else if(command === _const.POSITION && req.method === "POST") {
+ _postWindowPositionCommand(req, res, targetWindow);
+ return;
+ } else if(command === _const.POSITION && req.method === "GET") {
+ _getWindowPositionCommand(req, res, targetWindow);
+ return;
+ } else if(command === _const.MAXIMIZE && req.method === "POST") {
+ _postWindowMaximizeCommand(req, res, targetWindow);
+ return;
+ }
+
+ // No command matched: error
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _postWindowSizeCommand = function(req, res, targetWindow) {
+ var params = JSON.parse(req.post),
+ newWidth = params.width,
+ newHeight = params.height;
+
+ // If width/height are passed in string, force them to numbers
+ if (typeof(params.width) === "string") {
+ newWidth = parseInt(params.width, 10);
+ }
+ if (typeof(params.height) === "string") {
+ newHeight = parseInt(params.height, 10);
+ }
+
+ // If a number was not found, the command is
+ if (isNaN(newWidth) || isNaN(newHeight)) {
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ }
+
+ targetWindow.viewportSize = {
+ width : newWidth,
+ height : newHeight
+ };
+ res.success(_session.getId());
+ },
+
+ _getWindowSizeCommand = function(req, res, targetWindow) {
+ // Returns response in the format "{width: number, height: number}"
+ res.success(_session.getId(), targetWindow.viewportSize);
+ },
+
+ _postWindowPositionCommand = function(req, res, targetWindow) {
+ var params = JSON.parse(req.post),
+ newX = params.x,
+ newY = params.y;
+
+ // If width/height are passed in string, force them to numbers
+ if (typeof(params.x) === "string") {
+ newX = parseInt(params.x, 10);
+ }
+ if (typeof(params.y) === "string") {
+ newY = parseInt(params.y, 10);
+ }
+
+ // If a number was not found, the command is
+ if (isNaN(newX) || isNaN(newY)) {
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ }
+
+ // NOTE: Nothing to do! PhantomJS is headless. :)
+ res.success(_session.getId());
+ },
+
+ _getWindowPositionCommand = function(req, res, targetWindow) {
+ // Returns response in the format "{width: number, height: number}"
+ res.success(_session.getId(), { x : 0, y : 0 });
+ },
+
+ _postWindowMaximizeCommand = function(req, res, targetWindow) {
+ // NOTE: Nothing to do! PhantomJS is headless. :)
+ res.success(_session.getId());
+ },
+
+ _postKeysCommand = function(req, res) {
+ var activeEl = _locator.locateActiveElement();
+ var elReqHand = new ghostdriver.WebElementReqHand(activeEl.value, _session);
+ elReqHand.postValueCommand(req, res);
+ },
+
+ _refreshCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res),
+ currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+
+ currWindow.execFuncAndWaitForLoad(
+ function() { currWindow.reload(); },
+ successHand,
+ successHand); //< We don't care if 'refresh' fails
+ },
+
+ _backCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res),
+ currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+
+ if (currWindow.canGoBack) {
+ currWindow.execFuncAndWaitForLoad(
+ function() { currWindow.back(); },
+ successHand,
+ successHand); //< We don't care if 'back' fails
+ } else {
+ // We can't go back, and that's ok
+ successHand();
+ }
+ },
+
+ _forwardCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res),
+ currWindow = _protoParent.getSessionCurrWindow.call(this, _session, req);
+
+ if (currWindow.canGoForward) {
+ currWindow.execFuncAndWaitForLoad(
+ function() { currWindow.forward(); },
+ successHand,
+ successHand); //< We don't care if 'back' fails
+ } else {
+ // We can't go back, and that's ok
+ successHand();
+ }
+ },
+
+ _executeCommand = function(req, res) {
+ var postObj = JSON.parse(req.post),
+ result,
+ timer,
+ scriptTimeout = _session.getTimeout(_session.timeoutNames().SCRIPT),
+ timedOut = false;
+
+ if (typeof(postObj) === "object" && postObj.script && postObj.args) {
+ // Execute script, but within a limited timeframe
+ timer = setTimeout(function() {
+ // The script didn't return within the expected timeframe
+ timedOut = true;
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.TIMEOUT,
+ "Script didn't return within "+scriptTimeout+"ms",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ }, scriptTimeout);
+
+ // Launch the actual script
+ result = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ postObj.script,
+ postObj.args,
+ true);
+
+ // If we are here, we don't need the timer anymore
+ clearTimeout(timer);
+
+ // Respond with result ONLY if this hasn't ALREADY timed-out
+ if (!timedOut) {
+ res.respondBasedOnResult(_session, req, result);
+ }
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _executeAsyncCommand = function(req, res) {
+ var postObj = JSON.parse(req.post);
+
+ // console.log("executeAsync - " + 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));
+ res.respondBasedOnResult(_session, req, arguments[0]);
+ });
+
+ _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(
+ "function(script, args, timeout) { " +
+ "return (" + require("./webdriver_atoms.js").get("execute_async_script") + ")" +
+ "(script, args, timeout, callPhantom, true); " +
+ "}",
+ postObj.script,
+ postObj.args,
+ _session.getTimeout(_session.timeoutNames().ASYNC_SCRIPT));
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _getWindowHandle = function (req, res) {
+ var handle = _session.getCurrentWindowHandle();
+ if (handle !== null) {
+ res.success(_session.getId(), handle);
+ } else {
+ throw _errors.createFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_WINDOW, //< error name
+ "Current window handle invalid (closed?)", //< error message
+ req, //< request
+ _session, //< session
+ "SessionReqHand"); //< class name
+ }
+ },
+
+ _getWindowHandles = function(req, res) {
+ res.success(_session.getId(), _session.getWindowHandles());
+ },
+
+ _getScreenshotCommand = function(req, res) {
+ var rendering = _protoParent.getSessionCurrWindow.call(this, _session, req).renderBase64("png");
+ res.success(_session.getId(), rendering);
+ },
+
+ _getUrlCommand = function(req, res) {
+ // Get the URL at which the Page currently is
+ var result = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return location.toString()",
+ []);
+
+ res.respondBasedOnResult(_session, res, result);
+ },
+
+ _postUrlCommand = function(req, res) {
+ // Load the given URL in the Page
+ 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);
+
+ 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(
+ function() {
+ currWindow.open(postObj.url);
+ },
+ _createOnSuccessHandler(res), //< success
+ function() { //< failure/timeout
+ // Request timed out
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.TIMEOUT,
+ "URL '" + postObj.url + "' didn't load within " +
+ _session.getTimeout(_session.timeoutNames().PAGE_LOAD) +
+ "ms",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ });
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _postTimeout = function(req, res) {
+ var postObj = JSON.parse(req.post);
+
+ // Normalize the call: the "type" is read from the URL, not a POST parameter
+ if (req.urlParsed.file === _const.IMPLICIT_WAIT) {
+ postObj["type"] = _session.timeoutNames().IMPLICIT;
+ } else if (req.urlParsed.file === _const.ASYNC_SCRIPT) {
+ postObj["type"] = _session.timeoutNames().ASYNC_SCRIPT;
+ }
+
+ if (typeof(postObj["type"]) !== "undefined" && typeof(postObj["ms"]) !== "undefined") {
+ _session.setTimeout(postObj["type"], postObj["ms"]);
+ res.success(_session.getId());
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _postFrameCommand = function(req, res) {
+ var postObj = JSON.parse(req.post),
+ frameName,
+ switched = false;
+
+ if (typeof(postObj) === "object" && typeof(postObj.id) !== "undefined") {
+ if(postObj.id === null) {
+ // Reset focus on the topmost (main) Frame
+ _protoParent.getSessionCurrWindow.call(this, _session, req).switchToMainFrame();
+ switched = true;
+ } else if (typeof(postObj.id) === "number") {
+ // Switch frame by "index"
+ switched = _protoParent.getSessionCurrWindow.call(this, _session, req).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);
+
+ // 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) {
+ var el = null;
+ el = document.querySelector('#'+frameId);
+ if (el !== null) {
+ return el.name;
+ }
+ return null;
+ }, postObj.id);
+
+ // Switch frame by "name"
+ switched = _protoParent.getSessionCurrWindow.call(this, _session, req).switchToFrame(frameName);
+ }
+ } else if (typeof(postObj.id) === "object" && typeof(postObj.id["ELEMENT"]) === "string") {
+ // Will use the Element JSON to find the frame name
+ frameName = _protoParent.getSessionCurrWindow.call(this, _session, req).evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return arguments[0].name;",
+ [postObj.id]);
+
+ // If a name 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;
+ }
+ } else {
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ }
+
+ // Send a positive response if the switch was successful
+ if (switched) {
+ res.success(_session.getId());
+ } else {
+ // ... otherwise, throw the appropriate exception
+ throw _errors.createFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_FRAME, //< error name
+ "Unable to switch to frame", //< error message
+ req, //< request
+ _session, //< session
+ "SessionReqHand"); //< class name
+ }
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _getSourceCommand = function(req, res) {
+ var source = _protoParent.getSessionCurrWindow.call(this, _session, req).frameContent;
+ res.success(_session.getId(), source);
+ },
+
+ _postMouseMoveToCommand = function(req, res) {
+ var postObj = JSON.parse(req.post),
+ coords = { x: 0, y: 0 },
+ elementLocation,
+ elementSize,
+ elementSpecified = false,
+ offsetSpecified = false;
+
+ if (typeof postObj === "object") {
+ elementSpecified = postObj.element && postObj.element != null;
+ offsetSpecified = typeof postObj.xoffset !== "undefined" && typeof postObj.yoffset !== "undefined";
+ }
+ // Check that either an Element ID or an X-Y Offset was provided
+ if (elementSpecified || offsetSpecified) {
+ // console.log("element: " + elementSpecified + ", offset: " + offsetSpecified);
+ // If an Element was provided...
+ if (elementSpecified) {
+ // Get Element's Location and add it to the coordinates
+ var requestHandler = new ghostdriver.WebElementReqHand(postObj.element, _session);
+ elementLocation = requestHandler.getLocationInView();
+ elementSize = requestHandler.getSize();
+ // If the Element has a valid location
+ if (elementLocation !== null) {
+ 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 + ")");
+ }
+
+ 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 + ")");
+ }
+
+ // Send the Mouse Move as native event
+ _session.inputs.mouseMove(_session, coords);
+ res.success(_session.getId());
+ } else {
+ // Neither "element" nor "xoffset/yoffset" were provided
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _postMouseClickCommand = function(req, res, clickType) {
+ var postObj = {},
+ mouseButton = "left";
+ // normalize click
+ clickType = clickType || "click";
+
+ // The protocol allows language bindings to send an empty string
+ if (req.post.length > 0) {
+ postObj = JSON.parse(req.post);
+ }
+
+ // Check that either an Element ID or an X-Y Offset was provided
+ if (typeof(postObj) === "object") {
+ // Determine which button to click
+ if (typeof(postObj.button) === "number") {
+ // 0 is left, 1 is middle, 2 is right
+ mouseButton = (postObj.button === 2) ? "right" : (postObj.button === 1) ? "middle" : "left";
+ }
+ // Send the Mouse Click as native event
+ _session.inputs.mouseButtonClick(_session, clickType, mouseButton);
+ res.success(_session.getId());
+ } else {
+ // Neither "element" nor "xoffset/yoffset" were provided
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _postCookieCommand = function(req, res) {
+ var postObj = JSON.parse(req.post || "{}");
+
+ if (postObj.cookie) {
+ // JavaScript deals with Timestamps in "milliseconds since epoch": normalize!
+ if (postObj.cookie.expiry) {
+ postObj.cookie.expiry *= 1000;
+ }
+
+ // 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)) {
+ // 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) {
+ // Domain mismatch
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.INVALID_COOKIE_DOMAIN,
+ "Can only set Cookies for the current domain",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ } else {
+ // Something else went wrong
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.UNABLE_TO_SET_COOKIE,
+ "Unable to set Cookie",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ }
+ }
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _getCookieCommand = function(req, res) {
+ // Get all the cookies the session at current URL can see/access
+ res.success(
+ _session.getId(),
+ _protoParent.getSessionCurrWindow.call(this, _session, req).cookies);
+ },
+
+ _deleteCookieCommand = function(req, res) {
+ if (req.urlParsed.chunks.length === 2) {
+ // delete only 1 cookie among the one visible to this page
+ _protoParent.getSessionCurrWindow.call(this, _session, req).deleteCookie(req.urlParsed.chunks[1]);
+ } else {
+ // delete all the cookies visible to this page
+ _protoParent.getSessionCurrWindow.call(this, _session, req).clearCookies();
+ }
+ res.success(_session.getId());
+ },
+
+ _deleteWindowCommand = function(req, res) {
+ var params = JSON.parse(req.post || "{}"), //< in case nothing is posted at all
+ closed = false;
+
+ // Use the "name" parameter if it was provided
+ if (typeof(params) === "object" && params.name) {
+ closed = _session.closeWindow(params.name);
+ } else {
+ closed = _session.closeCurrentWindow();
+ }
+
+ // Report a success if we manage to close the window,
+ // otherwise throw a Failed Command Error
+ if (closed) {
+ res.success(_session.getId());
+ } else {
+ throw _errors.createFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_WINDOW, //< error name
+ "Unable to close window (closed already?)", //< error message
+ req, //< request
+ _session, //< session
+ "SessionReqHand"); //< class name
+ }
+ },
+
+ _postWindowCommand = function(req, res) {
+ var params = JSON.parse(req.post);
+
+ if (typeof(params) === "object" && params.name) {
+ // Report a success if we manage to switch the current window,
+ // otherwise throw a Failed Command Error
+ if (_session.switchToWindow(params.name)) {
+ res.success(_session.getId());
+ } else {
+ throw _errors.createFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_WINDOW, //< error name
+ "Unable to switch to window (closed?)", //< error message
+ req, //< request
+ _session, //< session
+ "SessionReqHand"); //< class name
+ }
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _getTitleCommand = function(req, res) {
+ res.success(_session.getId(), _protoParent.getSessionCurrWindow.call(this, _session, req).title);
+ };
+
+ // public:
+ return {
+ handle : _handle,
+ getSessionId : function() { return _session.getId(); }
+ };
+};
+// prototype inheritance:
+ghostdriver.SessionReqHand.prototype = new ghostdriver.RequestHandler();
View
58 src/ghostdriver/request_handlers/shutdown_request_handler.js
@@ -0,0 +1,58 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com / detronizator@gmail.com>
+Copyright (c) 2010, Jim Evans <james.h.evans.jr@gmail.com> - Salesforce.com
+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