Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

adding ghostdriver

  • Loading branch information...
commit 18613c5abee174e2b2741ef8b1811f4436c104e7 1 parent ea367e2
Pranav Verma authored
Showing with 13,994 additions and 0 deletions.
  1. +22 −0 ghostdriver/LICENSE.BSD
  2. +72 −0 ghostdriver/README.md
  3. +32 −0 ghostdriver/TODO.md
  4. +230 −0 ghostdriver/src/errors.js
  5. +61 −0 ghostdriver/src/main.js
  6. +191 −0 ghostdriver/src/request_handlers/request_handler.js
  7. +97 −0 ghostdriver/src/request_handlers/router_request_handler.js
  8. +182 −0 ghostdriver/src/request_handlers/session_manager_request_handler.js
  9. +545 −0 ghostdriver/src/request_handlers/session_request_handler.js
  10. +64 −0 ghostdriver/src/request_handlers/status_request_handler.js
  11. +334 −0 ghostdriver/src/request_handlers/webelement_request_handler.js
  12. +200 −0 ghostdriver/src/session.js
  13. +70 −0 ghostdriver/src/third_party/parseuri.js
  14. +214 −0 ghostdriver/src/third_party/patch_require.js
  15. +15 −0 ghostdriver/src/third_party/webdriver-atoms/active_element.js
  16. +6 −0 ghostdriver/src/third_party/webdriver-atoms/back.js
  17. +124 −0 ghostdriver/src/third_party/webdriver-atoms/clear.js
  18. +16 −0 ghostdriver/src/third_party/webdriver-atoms/clear_local_storage.js
  19. +16 −0 ghostdriver/src/third_party/webdriver-atoms/clear_session_storage.js
  20. +133 −0 ghostdriver/src/third_party/webdriver-atoms/click.js
  21. +15 −0 ghostdriver/src/third_party/webdriver-atoms/default_content.js
  22. +9,996 −0 ghostdriver/src/third_party/webdriver-atoms/deps.js
  23. +16 −0 ghostdriver/src/third_party/webdriver-atoms/execute_async_script.js
  24. +13 −0 ghostdriver/src/third_party/webdriver-atoms/execute_script.js
  25. +15 −0 ghostdriver/src/third_party/webdriver-atoms/execute_sql.js
  26. +45 −0 ghostdriver/src/third_party/webdriver-atoms/find_element.js
  27. +45 −0 ghostdriver/src/third_party/webdriver-atoms/find_elements.js
  28. +6 −0 ghostdriver/src/third_party/webdriver-atoms/forward.js
  29. +46 −0 ghostdriver/src/third_party/webdriver-atoms/frame_by_id_or_name.js
  30. +15 −0 ghostdriver/src/third_party/webdriver-atoms/frame_by_index.js
  31. +15 −0 ghostdriver/src/third_party/webdriver-atoms/get_appcache_status.js
  32. +97 −0 ghostdriver/src/third_party/webdriver-atoms/get_attribute.js
  33. +103 −0 ghostdriver/src/third_party/webdriver-atoms/get_attribute_value.js
  34. +7 −0 ghostdriver/src/third_party/webdriver-atoms/get_current_position.js
  35. +7 −0 ghostdriver/src/third_party/webdriver-atoms/get_element_from_cache.js
  36. +15 −0 ghostdriver/src/third_party/webdriver-atoms/get_frame_window.js
  37. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_local_storage_item.js
  38. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_local_storage_keys.js
  39. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_local_storage_size.js
  40. +7 −0 ghostdriver/src/third_party/webdriver-atoms/get_location.js
  41. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_session_storage_item.js
  42. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_session_storage_keys.js
  43. +16 −0 ghostdriver/src/third_party/webdriver-atoms/get_session_storage_size.js
  44. +100 −0 ghostdriver/src/third_party/webdriver-atoms/get_size.js
  45. +103 −0 ghostdriver/src/third_party/webdriver-atoms/get_text.js
  46. +105 −0 ghostdriver/src/third_party/webdriver-atoms/get_top_left_coordinates.js
  47. +100 −0 ghostdriver/src/third_party/webdriver-atoms/get_value_of_css_property.js
  48. +6 −0 ghostdriver/src/third_party/webdriver-atoms/get_window_position.js
  49. +6 −0 ghostdriver/src/third_party/webdriver-atoms/get_window_size.js
  50. +100 −0 ghostdriver/src/third_party/webdriver-atoms/is_displayed.js
  51. +100 −0 ghostdriver/src/third_party/webdriver-atoms/is_enabled.js
  52. +7 −0 ghostdriver/src/third_party/webdriver-atoms/is_online.js
  53. +101 −0 ghostdriver/src/third_party/webdriver-atoms/is_selected.js
  54. +7 −0 ghostdriver/src/third_party/webdriver-atoms/lastupdate
  55. +16 −0 ghostdriver/src/third_party/webdriver-atoms/remove_local_storage_item.js
  56. +16 −0 ghostdriver/src/third_party/webdriver-atoms/remove_session_storage_item.js
  57. +16 −0 ghostdriver/src/third_party/webdriver-atoms/set_local_storage_item.js
  58. +16 −0 ghostdriver/src/third_party/webdriver-atoms/set_session_storage_item.js
  59. +6 −0 ghostdriver/src/third_party/webdriver-atoms/set_window_position.js
  60. +6 −0 ghostdriver/src/third_party/webdriver-atoms/set_window_size.js
Sorry, we could not display the entire diff because it was too big.
22 ghostdriver/LICENSE.BSD
View
@@ -0,0 +1,22 @@
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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.
72 ghostdriver/README.md
View
@@ -0,0 +1,72 @@
+# Ghost Driver
+
+Ghost Driver is a pure JavaScript implementation of the
+[WebDriver Wire Protocol](http://code.google.com/p/selenium/wiki/JsonWireProtocol)
+for [PhantomJS](http://phantomjs.org/).
+It's going to be a Remote WebDriver that uses PhantomJS as back-end.
+
+## Status
+
+* Under development
+* Far from complete
+* Only **~40%** of the WireProtocol currently implemented
+* You can monitor development progress [at this Google Spreadsheet](https://docs.google.com/spreadsheet/ccc?key=0Am63grtxc7bDdGNqX1ZPX2VoZlE2ZHZhd09lNDkzbkE)
+* Core released to get people interested and get contributions
+* Don't raise BUGS: send PULL REQUESTS pleaase!
+
+## Presentation and Slides
+
+In April 2012 I (Ivan De Marino) presented GhostDriver at the [Selenium Conference](http://www.seleniumconf.org/speakers/#IDM):
+[slides](http://detro.github.com/ghostdriver/slides/index.html)
+and
+[video](http://blog.ivandemarino.me/2012/05/01/Me-the-Selenium-Conference-2012).
+
+## Requirements
+
+* PhantomJS [ghostdriver-dev branch](https://github.com/detro/phantomjs/tree/ghostdriver-dev),
+taken from my (Ivan De Marino) port: [github.com/detro/phantomjs](https://github.com/detro/phantomjs).
+
+## How to use it
+
+Check out the [ghostdriver-dev branch](https://github.com/detro/phantomjs/tree/ghostdriver-dev)
+of PhantomJS, and build it (I assume you know Git).
+
+There is **plenty to do before this is usable**, but if you can't wait to try
+PhantomJS's speed when it acts as a RemoteWebDriver Server, do the following:
+
+1. Start GhostDriver on a terminal:
+
+ ```bash
+ $> phantomjs ghostdriver/src/main.js
+ Ghost Driver running on port 8080
+ ```
+
+2. Build and Launch the test suite (written in Java, built with [Gradle](http://www.gradle.org/)):
+
+ ```bash
+ $> cd ghostdriver/test
+ $> ./gradlew test
+ ```
+
+## Reasoning: pros and cons
+
+### Pros of using an Headless browser for your Selenium testing
+* Speed: makes development faster
+* Speed: makes THE developer happier
+* Speed: makes leaves more time for beer, video-games, cycling or whatever you fancy
+* ...
+
+### Cons of using an Headless browser for your Selenium testing
+* PhantomJS is not a "Real" Browser, but _"just"_ very very close to one
+
+## Contributions
+
+You can contribute testing it and reporting bugs and issues, or submitting Pull Requests.
+Any **help is welcome**, but bear in mind the following base principles:
+
+* Squash your commits by theme: I prefer a clean, readable log
+* Maintain consistency with the code-style you are surrounded by
+* If you are going to make a big, substantial change, let's discuss it first
+
+## License
+GhostDriver is distributed under [BSD License](http://www.opensource.org/licenses/BSD-2-Clause).
32 ghostdriver/TODO.md
View
@@ -0,0 +1,32 @@
+# Top Priority
+
+* Decorate req/res objects OR extend SessionReqHand to better manage "after open is done" scenarios
+* Implement all the commands :)
+ * "/session/:sessionid/element" and "/session/:sessionid/elements" need to be handled into 1 (++gracefulness)
+
+
+# Mid Priority
+
+* Support for Proxying
+* Support for Screenshots
+* Proper Capabilities negotiation
+
+
+# Low Priority
+
+* Subclass "RemoteWebDriver" to make setup via simple API
+* Refactor all the Erros in "error.js" to allow an optional, custom Error Handling.
+ * This will help to comply with [Wire Protocol Error Handling](http://code.google.com/p/selenium/wiki/JsonWireProtocol#Error_Handling)
+ * See "error.js/InvalidCommandMethod"
+* Add another kind of "Error", like "CommandFailed", that have a different error handling and report
+ * See [Wire Protocol Failed Commands Error Handling](http://code.google.com/p/selenium/wiki/JsonWireProtocol#Failed_Commands)
+* More complex examples
+
+
+# Things to add to PhantomJS
+
+* Ability to read informations like Operating System, Architecture and so forth
+* Ability to handle "alert(), confirm() and prompt()"
+* PhantomJS "res.write()" doesn't set the 'Content-Length' header automatically :(
+* Add support for Frames and IFrames in PhantomJS
+ * And than add it to the driver
230 ghostdriver/src/errors.js
View
@@ -0,0 +1,230 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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.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);
+};
61 ghostdriver/src/main.js
View
@@ -0,0 +1,61 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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')
+ },
+ server = require('webserver').create(),
+ router,
+ parseURI;
+
+phantom.injectJs("third_party/patch_require.js"); //< TODO - Remove as soon as 'require' is fully implemented in PhantomJS
+
+// Enable "strict mode" for the 'parseURI' library
+parseURI = require("./third_party/parseuri.js");
+parseURI.options.strictMode = true;
+
+phantom.injectJs("session.js");
+phantom.injectJs("request_handlers/request_handler.js");
+phantom.injectJs("request_handlers/status_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();
+
+// Start the server
+if (server.listen(ghostdriver.system.args[1] || 8080, router.handle)) {
+ console.log('Ghost Driver running on port ' + server.port);
+} else {
+ console.error("ERROR: Could not start Ghost Driver");
+ phantom.exit();
+}
191 ghostdriver/src/request_handlers/request_handler.js
View
@@ -0,0 +1,191 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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.RequestHandler = function() {
+ // private:
+ var
+ _errors = require("./errors.js"),
+ _handle = function(request, response) {
+ _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", body.length);
+ this.write(body);
+ this.close();
+ },
+
+ _writeJSONAndCloseDecorator = function(obj) {
+ var objStr = JSON.stringify(obj);
+ this.setHeader("Content-Length", 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
+ };
+
+ // public:
+ return {
+ handle : _handle,
+ reroute : _reroute,
+ buildResponseBody : _buildResponseBody,
+ buildSuccessResponseBody : _buildSuccessResponseBody,
+ decorateRequest : _decorateRequest,
+ decorateResponse : _decorateResponse
+ };
+};
+
+// List of all the possible Response Status Codes
+ghostdriver.ResponseStatusCodes = {};
+ghostdriver.ResponseStatusCodes.SUCCESS = 0;
+ghostdriver.ResponseStatusCodes.NO_SUCH_ELEMENT = 7;
+ghostdriver.ResponseStatusCodes.NO_SUCH_FRAME = 8;
+ghostdriver.ResponseStatusCodes.UNKNOWN_COMMAND = 9;
+ghostdriver.ResponseStatusCodes.STALE_ELEMENT_REFERENCE = 10;
+ghostdriver.ResponseStatusCodes.ELEMENT_NOT_VISIBLE = 11;
+ghostdriver.ResponseStatusCodes.INVALID_ELEMENT_STATE = 12;
+ghostdriver.ResponseStatusCodes.UNKNOWN_ERROR = 13;
+ghostdriver.ResponseStatusCodes.ELEMENT_IS_NOT_SELECTABLE = 15;
+ghostdriver.ResponseStatusCodes.JAVA_SCRIPT_ERROR = 17;
+ghostdriver.ResponseStatusCodes.XPATH_LOOKUP_ERROR = 19;
+ghostdriver.ResponseStatusCodes.TIMEOUT = 21;
+ghostdriver.ResponseStatusCodes.NO_SUCH_WINDOW = 23;
+ghostdriver.ResponseStatusCodes.INVALID_COOKIE_DOMAIN = 24;
+ghostdriver.ResponseStatusCodes.UNABLE_TO_SET_COOKIE = 25;
+ghostdriver.ResponseStatusCodes.UNEXPECTED_ALERT_OPEN = 26;
+ghostdriver.ResponseStatusCodes.NO_ALERT_OPEN_ERROR = 27;
+ghostdriver.ResponseStatusCodes.SCRIPT_TIMEOUT = 28;
+ghostdriver.ResponseStatusCodes.INVALID_ELEMENT_COORDINATES = 29;
+ghostdriver.ResponseStatusCodes.IME_NOT_AVAILABLE = 30;
+ghostdriver.ResponseStatusCodes.IME_ENGINE_ACTIVATION_FAILED = 31;
+ghostdriver.ResponseStatusCodes.INVALID_SELECTOR = 32;
97 ghostdriver/src/request_handlers/router_request_handler.js
View
@@ -0,0 +1,97 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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(),
+ _sessionManRH = new ghostdriver.SessionManagerReqHand(),
+ _const = {
+ STATUS : "status",
+ SESSION : "session",
+ SESSIONS : "sessions",
+ SESSION_DIR : "/session/"
+ },
+ _errors = require("./errors.js"),
+
+ _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.file === _const.STATUS) { // GET '/status'
+ _statusRH.handle(req, res);
+ } 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();
182 ghostdriver/src/request_handlers/session_manager_request_handler.js
View
@@ -0,0 +1,182 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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 = require("./errors.js"),
+ _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 desiredCapabilities,
+ newSession;
+
+ try {
+ desiredCapabilities = req.post; //< this must exist
+ if (typeof(desiredCapabilities) !== "object") {
+ desiredCapabilities = JSON.parse(desiredCapabilities);
+ }
+
+ // Create and store a new Session
+ newSession = new ghostdriver.Session(desiredCapabilities);
+ _sessions[newSession.getId()] = newSession;
+
+ // Redirect to the newly created Session
+ res.statusCode = 303; //< "303 See Other"
+ res.setHeader("Location", "/session/"+newSession.getId());
+ res.closeGracefully();
+ } catch (e) {
+ 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();
545 ghostdriver/src/request_handlers/session_request_handler.js
View
@@ -0,0 +1,545 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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
+ _protoParent = ghostdriver.SessionReqHand.prototype,
+ _session = session,
+ _locator = new ghostdriver.WebElementLocator(_session),
+ _const = {
+ URL : "url",
+ ELEMENT : "element",
+ ELEMENTS : "elements",
+ ELEMENT_DIR : "/element/",
+ TITLE : "title",
+ WINDOW : "window",
+ CURRENT : "current",
+ SIZE : "size",
+ 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"
+ },
+ _errors = require("./errors.js"),
+
+ _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.TITLE && req.method === "GET") { //< ".../title"
+ // Get the current Page title
+ _getTitleCommand(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) {
+ _handleWindow(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.ELEMENT && req.method === "POST" && req.urlParsed.chunks.length == 1) { //< ".../element"
+ _postElementCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.ELEMENTS && req.method === "POST" && req.urlParsed.chunks.length == 1) { //< ".../elements"
+ _postElementsCommand(req, res);
+ return;
+ } else if (req.urlParsed.directory === _const.ELEMENT_DIR) { //< ".../element/:elementId" or ".../element/active"
+ // TODO
+ } else if (req.urlParsed.chunks[0] === _const.ELEMENT) { //< ".../element/:elementId/COMMAND"
+ // Get the WebElementRH and, if found, re-route request to it
+ element = _locator.getElement(decodeURIComponent(req.urlParsed.chunks[1]));
+ 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.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.COOKIE) {
+ if(req.method === "DELETE") {
+ _deleteCookieCommand(req, res);
+ return;
+ }
+ }
+
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ },
+
+ _createOnSuccessHandler = function(res) {
+ return function (status) {
+ res.success(_session.getId());
+ };
+ },
+
+ _handleWindow = function(req, res) {
+ var windowHandle = req.urlParsed.chunks[1],
+ command = req.urlParsed.chunks[2];
+
+ // TODO - We need support for multiple windows:
+ // instead of checking request is for "current", we should
+ // check if it's not, and if not then apply to specific window instead
+ // of _session.getCurrentWindow().
+ // TODO handle NoSuchWindow - If the specified window cannot be found.
+ if(windowHandle === _const.CURRENT) {
+ if(command === _const.SIZE && req.method === "POST") {
+ _postWindowSizeCommand(req, res);
+ return;
+ } else if(command === _const.SIZE && req.method === "GET") {
+ _getWindowSizeCommand(req, res);
+ return;
+ }
+ }
+ },
+
+ _refreshCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res);
+
+ _session.getCurrentWindow().evaluateAndWaitForLoad(
+ function() { window.location.reload(true); }, //< 'reload(true)' force reload from the server
+ successHand,
+ successHand); //< We don't care if 'refresh' fails
+ },
+
+ _backCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res);
+
+ _session.getCurrentWindow().evaluateAndWaitForLoad(
+ require("./webdriver_atoms.js").get("back"),
+ successHand,
+ successHand); //< We don't care if 'back' fails
+ },
+
+ _forwardCommand = function(req, res) {
+ var successHand = _createOnSuccessHandler(res);
+
+ _session.getCurrentWindow().evaluateAndWaitForLoad(
+ require("./webdriver_atoms.js").get("forward"),
+ successHand,
+ successHand); //< We don't care if 'forward' fails
+ },
+
+ _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 = _session.getCurrentWindow().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);
+
+ if (typeof(postObj) === "object" && postObj.script && postObj.args) {
+ _session.getCurrentWindow().setOneShotCallback("onCallback", function() {
+ res.respondBasedOnResult(_session, req, arguments[0]);
+ });
+
+ _session.getCurrentWindow().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) {
+ res.success(_session.getId(), _session.getCurrentWindowHandle());
+ },
+
+ _getWindowHandles = function(req, res) {
+ res.success(_session.getId(), _session.getWindowHandles());
+ },
+
+ _getScreenshotCommand = function(req, res) {
+ var rendering = _session.getCurrentWindow().renderBase64("png");
+ res.success(_session.getId(), rendering);
+ },
+
+ _getUrlCommand = function(req, res) {
+ // Get the URL at which the Page currently is
+ var result = _session.getCurrentWindow().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),
+ pageOpenTimeout = _session.getTimeout(_session.timeoutNames().PAGE_LOAD),
+ pageOpenTimedout = false,
+ timer;
+
+ if (typeof(postObj) === "object" && postObj.url) {
+ // Open the given URL and, when done, return "HTTP 200 OK"
+ _session.getCurrentWindow().open(postObj.url, function(status) {
+ if (!pageOpenTimedout) {
+ // Callback received: don't need the timer anymore
+ clearTimeout(timer);
+
+ if (status === "success") {
+ res.success(_session.getId());
+ } else {
+ _errors.handleInvalidReqInvalidCommandMethodEH(req, res);
+ }
+ }
+ });
+ timer = setTimeout(function() {
+ // Command Failed (Timed-out)
+ pageOpenTimedout = true;
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.TIMEOUT,
+ "URL '"+postObj.url+"' didn't load within "+pageOpenTimeout+"ms",
+ req,
+ res,
+ _session,
+ "SessionReqHand");
+ }, pageOpenTimeout);
+ } 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),
+ frameElement = null,
+ result;
+
+ if (typeof(postObj) === "object" && typeof(postObj.id) !== "undefined") {
+ if(postObj.id === null) {
+ // Reset focus on the topmost (main) Frame
+ _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return top.focus();");
+ res.success(_session.getId());
+ return; //< we are done here: no need go further
+ }
+
+ if (typeof(postObj.id) === "number") {
+ // Search frame by "index" within the "window.frames" array
+ result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "var el = null; " +
+ "if (typeof(window.frames[arguments[0]]) !== 'undefined') { " +
+ "el = window.frames[arguments[0]].frameElement; " +
+ "} " +
+ "return el; ",
+ [postObj.id]);
+
+ // Check if the Frame was found
+ if (result.status === 0 && typeof(result.value) === "object") {
+ frameElement = result.value;
+ }
+ } else if (typeof(postObj.id) === "string") {
+ // Search frame by "name" and, if not found, by "id"
+ result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "var el = null; " +
+ "el = document.querySelector(\"[name='\"+arguments[0]+\"']\"); " +
+ "if (el === null) { " +
+ "el = document.querySelector('#'+arguments[0]); " +
+ "} " +
+ "return el; ",
+ [postObj.id]);
+
+ // Check if the Frame was found
+ if (result.status === 0 && typeof(result.value) === "object") {
+ frameElement = result.value;
+ }
+ } else if (typeof(postObj.id) === "object" && typeof(postObj.id["ELEMENT"]) === "string") {
+ // Search frame by "{ELEMENT : id}" object
+ result = _locator.getElement(decodeURIComponent(postObj.id["ELEMENT"]));
+
+ // Check if the Frame was found
+ if (result !== null && typeof(result) === "object") {
+ frameElement = result.getJSON();
+ }
+ } else {
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ }
+
+ // Send a positive response if the element was found...
+ if (frameElement !== null) {
+ result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return arguments[0].focus();",
+ [frameElement]);
+ res.success(_session.getId());
+ } else {
+ // ... otherwise, throw the appropriate exception
+ _errors.handleFailedCommandEH(
+ _errors.FAILED_CMD_STATUS.NO_SUCH_FRAME, //< error name
+ "Frame not found", //< error message
+ req, //< request
+ res,
+ _session, //< session
+ "SessionReqHand"); //< class name
+ }
+ } else {
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ }
+ },
+
+ _getSourceCommand = function(req, res) {
+ var source = _session.getCurrentWindow().content;
+ res.success(_session.getId(), source);
+ },
+
+ _deleteCookieCommand = function(req, res) {
+ // Delete all cookies visible to the current page.
+ _session.getCurrentWindow().evaluate(function() {
+ var p = document.cookie.split(";"),
+ i, key;
+
+ for(i = p.length -1; i >= 0; --i) {
+ key = p[i].split("=");
+ document.cookie = key + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ }
+ });
+ res.success(_session.getId());
+ },
+
+ _deleteWindowCommand = function(req, res) {
+ // TODO An optional JSON parameter "name" might be given
+ _session.closeCurrentWindow();
+ res.success(_session.getId());
+ },
+
+ _postWindowCommand = function(req, res) {
+ // TODO
+ // TODO An optional JSON parameter "name" might be given
+ },
+
+ _postWindowSizeCommand = function(req, res) {
+ 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);
+ }
+
+ _session.getCurrentWindow().viewportSize = {
+ width : newWidth,
+ height : newHeight
+ };
+ res.success(_session.getId());
+ },
+
+ _getWindowSizeCommand = function(req, res) {
+ // Returns response in the format "{width: number, height: number}"
+ res.success(_session.getId(), _session.getCurrentWindow().viewportSize);
+ },
+
+ _getTitleCommand = function(req, res) {
+ var result = _session.getCurrentWindow().evaluate(function() { return document.title; });
+ res.success(_session.getId(), result);
+ },
+
+ _postElementCommand = function(req, res) {
+ // Search for a WebElement on the Page
+ var element,
+ searchStartTime = new Date().getTime();
+
+ // Try to find the element
+ // and retry if "startTime + implicitTimeout" is
+ // greater (or equal) than current time
+ do {
+ element = _locator.locateElement(JSON.parse(req.post));
+ if (element) {
+ res.success(_session.getId(), element.getJSON());
+ return;
+ }
+ } while(searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT) >= new Date().getTime());
+
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ };
+
+ _postElementsCommand = function(req, res) {
+ // Search for a WebElement on the Page
+ var elements,
+ searchStartTime = new Date().getTime(),
+ elementsResponse = "";
+
+ // Try to find at least one element
+ // and retry if "startTime + implicitTimeout" is
+ // greater (or equal) than current time
+ do {
+ elements = _locator.locateElements(JSON.parse(req.post));
+ if (elements.length > 0) {
+ // Manually construct the JSON response, since the return
+ // from locateElements is an array of WebElementReqHand
+ // objects.
+ for (var i = 0; i < elements.length; i++) {
+ if (elementsResponse.length > 0) {
+ elementsResponse += ", ";
+ }
+ elementsResponse += JSON.stringify(elements[i].getJSON());
+ }
+ break;
+ }
+ } while(searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT) >= new Date().getTime());
+
+ res.success(_session.getId(), JSON.parse("[" + elementsResponse + "]"));
+ };
+
+ // public:
+ return {
+ handle : _handle,
+ setSession : function(s) { _session = s; },
+ getSessionId : function() { return _session.getId(); }
+ };
+};
+// prototype inheritance:
+ghostdriver.SessionReqHand.prototype = new ghostdriver.RequestHandler();
64 ghostdriver/src/request_handlers/status_request_handler.js
View
@@ -0,0 +1,64 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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.StatusReqHand = function() {
+ // private:
+ var
+ _protoParent = ghostdriver.StatusReqHand.prototype,
+ _statusObj = {
+ "build" : {
+ "version" : "1.0-dev",
+ "revision" : "unknown",
+ "time" : "unknown"
+ },
+ "os" : {
+ "name" : ghostdriver.system.os.name,
+ "version" : ghostdriver.system.os.version,
+ "arch" : ghostdriver.system.os.architecture
+ }
+ },
+
+ _handle = function(req, res) {
+ _protoParent.handle.call(this, req, res);
+
+ if (req.method === "GET" && req.urlParsed.file === "status") {
+ res.success(null, _statusObj);
+ return;
+ }
+
+ throw require("./errors.js").createInvalidReqInvalidCommandMethodEH(req);
+ };
+
+ // public:
+ return {
+ handle : _handle
+ };
+};
+// prototype inheritance:
+ghostdriver.StatusReqHand.prototype = new ghostdriver.RequestHandler();
334 ghostdriver/src/request_handlers/webelement_request_handler.js
View
@@ -0,0 +1,334 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+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.WebElementReqHand = function(id, session) {
+ // private:
+ var
+ _id = id + '', //< ensure this is always a string
+ _session = session,
+ _locator = new ghostdriver.WebElementLocator(_session),
+ _protoParent = ghostdriver.WebElementReqHand.prototype,
+ _const = {
+ ELEMENT : "element",
+ ELEMENTS : "elements",
+ VALUE : "value",
+ SUBMIT : "submit",
+ DISPLAYED : "displayed",
+ ENABLED : "enabled",
+ ATTRIBUTE : "attribute",
+ NAME : "name",
+ CLICK : "click",
+ SELECTED : "selected",
+ CLEAR : "clear",
+ CSS : "css",
+ TEXT : "text",
+ EQUALS_DIR : "equals"
+ },
+ _errors = require("./errors.js"),
+
+ _handle = function(req, res) {
+ _protoParent.handle.call(this, req, res);
+
+ // console.log("Request => " + JSON.stringify(req, null, ' '));
+
+ // TODO lots to do...
+
+ if (req.urlParsed.file === _const.ELEMENT && req.method === "POST") {
+ _findElementCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.ELEMENTS && req.method === "POST") {
+ _findElementsCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.VALUE && req.method === "POST") {
+ _valueCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.SUBMIT && req.method === "POST") {
+ _submitCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.DISPLAYED && req.method === "GET") {
+ _getDisplayedCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.ENABLED && req.method === "GET") {
+ _getEnabledCommand(req, res);
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.ATTRIBUTE && req.method === "GET") {
+ _getAttributeCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.NAME && req.method === "GET") {
+ _getNameCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.CLICK && req.method === "POST") {
+ _postClickCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.SELECTED && req.method === "GET") {
+ _getSelectedCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.CLEAR && req.method === "POST") {
+ _postClearCommand(req, res);
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.CSS && req.method === "GET") {
+ _getCssCommand(req, res);
+ return;
+ } else if (req.urlParsed.file === _const.TEXT && req.method === "GET") {
+ _getTextCommand(req, res);
+ return;
+ } else if (req.urlParsed.chunks[0] === _const.EQUALS && req.method === "GET") {
+ _getEqualsCommand(req, res);
+ return;
+ } // else ...
+
+ // TODO lots to do...
+
+ throw _errors.createInvalidReqInvalidCommandMethodEH(req);
+ },
+
+ _getDisplayedCommand = function(req, res) {
+ var displayed = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("is_displayed"),
+ _getJSON());
+ res.respondBasedOnResult(_session, req, displayed);
+ },
+
+ _getEnabledCommand = function(req, res) {
+ var enabled = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("is_enabled"),
+ _getJSON());
+ res.respondBasedOnResult(_session, req, enabled);
+ },
+
+ _valueCommand = function(req, res) {
+ var i, ilen,
+ postObj = JSON.parse(req.post),
+ typeAtom = require("./webdriver_atoms.js").get("type"),
+ typeRes;
+
+ // Ensure all required parameters are available
+ if (typeof(postObj) === "object" && typeof(postObj.value) === "object") {
+ // Execute the "type" atom
+ typeRes = _getSession().getCurrentWindow().evaluate(typeAtom, _getJSON(), postObj.value);
+
+ // TODO - Error handling based on the value of "typeRes"
+
+ res.success(_session.getId());
+ return;
+ }
+
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ },
+
+ _getNameCommand = function(req, res) {
+ var result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return arguments[0].tagName;",
+ [_getJSON()]);
+
+ // Convert value to a lowercase string as per WebDriver JSONWireProtocol spec
+ // @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/name
+ if(result.status === 0) {
+ result.value = result.value.toLowerCase();
+ }
+
+ res.respondBasedOnResult(_session, req, result);
+ },
+
+ _getAttributeCommand = function(req, res) {
+ var attributeValueAtom = require("./webdriver_atoms.js").get("get_attribute_value"),
+ result;
+
+ if (typeof(req.urlParsed.file) === "string" && req.urlParsed.file.length > 0) {
+ // Read the attribute
+ result = _session.getCurrentWindow().evaluate(
+ attributeValueAtom, // < Atom to read an attribute
+ _getJSON(), // < Element to read from
+ req.urlParsed.file); // < Attribute to read
+
+ res.respondBasedOnResult(_session, req, result);
+ return;
+ }
+
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ },
+
+ _getTextCommand = function(req, res) {
+ var result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("get_text"),
+ _getJSON());
+ res.respondBasedOnResult(_session, req, result);
+ },
+
+ _getEqualsCommand = function(req, res) {
+ var result;
+
+ if (typeof(req.urlParsed.file) === "string" && req.urlParsed.file.length > 0) {
+ result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return arguments[0].isSameNode(arguments[1]);",
+ [_getJSON(), _getJSON(req.urlParsed.file)]);
+ res.success(_session.getId(), result);
+ }
+
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ },
+
+ _submitCommand = function(req, res) {
+ var submitRes;
+
+ // Listen for the page to Finish Loading after the submit
+ _getSession().getCurrentWindow().setOneShotCallback("onLoadFinished", function(status) {
+ if (status === "success") {
+ res.success(_session.getId());
+ }
+
+ // TODO - what do we do if this fails?
+ // TODO - clear thing up after we are done waiting
+ });
+
+ submitRes = _getSession().getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("submit"),
+ _getJSON());
+
+ // TODO - Error handling based on the value of "submitRes"
+ },
+
+ _postClickCommand = function(req, res) {
+ var result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("click"),
+ _getJSON());
+
+ res.respondBasedOnResult(_session, req, result);
+ },
+
+ _getSelectedCommand = function(req, res) {
+ var result = JSON.parse(_session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("is_selected"),
+ _getJSON()));
+
+ res.respondBasedOnResult(_session, req, result);
+ },
+
+ _postClearCommand = function(req, res) {
+ var result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("clear"),
+ _getJSON());
+ res.respondBasedOnResult(_session, req, result);
+ },
+
+ _getCssCommand = function(req, res) {
+ var cssPropertyName = req.urlParsed.file,
+ result;
+
+ // Check that a property name was indeed provided
+ if (typeof(cssPropertyName) === "string" || cssPropertyName.length > 0) {
+ result = _session.getCurrentWindow().evaluate(
+ require("./webdriver_atoms.js").get("execute_script"),
+ "return window.getComputedStyle(arguments[0]).getPropertyValue(arguments[1]);",
+ [_getJSON(), cssPropertyName]);
+
+ res.respondBasedOnResult(_session, req, result);
+ return;
+ }
+
+ throw _errors.createInvalidReqMissingCommandParameterEH(req);
+ },
+
+ _findElementCommand = function(req, res) {
+ // Search for a WebElement on the Page
+ var element,
+ searchStartTime = new Date().getTime();
+
+ // Try to find the element
+ // and retry if "startTime + implicitTimeout" is
+ // greater (or equal) than current time
+ do {
+ element = _locator.locateElement(JSON.parse(req.post), _getId());
+ if (element) {
+ res.success(_session.getId(), element.getJSON());
+ return;
+ }
+ } while(searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT) >= new Date().getTime());
+
+ throw _errors.createInvalidReqVariableResourceNotFoundEH(req);
+ };
+
+ _findElementsCommand = function(req, res) {
+ // Search for a WebElement on the Page
+ var elements,
+ searchStartTime = new Date().getTime(),
+ elementsResponse = "";
+
+ // Try to find at least one element
+ // and retry if "startTime + implicitTimeout" is
+ // greater (or equal) than current time
+ do {
+ elements = _locator.locateElements(JSON.parse(req.post), _getId());
+ if (elements.length > 0) {
+ // Manually construct the JSON response, since the return
+ // from locateElements is an array of WebElementReqHand
+ // objects.
+ for (var i = 0; i < elements.length; i++) {
+ if (elementsResponse.length > 0) {
+ elementsResponse += ", ";
+ }
+ elementsResponse += JSON.stringify(elements[i].getJSON());
+ }
+ break;
+ }
+ } while(searchStartTime + _session.getTimeout(_session.timeoutNames().IMPLICIT) >= new Date().getTime());
+
+ res.success(_session.getId(), JSON.parse("[" + elementsResponse + "]"));
+ };
+
+
+ /** This method can generate any Element JSON: just provide an ID.
+ * Will return the one of the current Element if no ID is provided.
+ * @param elementId ID of the Element to describe in JSON format,
+ * or undefined to get the one fo the current Element.
+ */
+ _getJSON = function(elementId) {
+ return {
+ "ELEMENT" : elementId || _getId()
+ };
+ },
+
+ _getId = function() { return _id; },
+ _getSession = function() { return _session; };
+
+ // public:
+ return {
+ handle : _handle,
+ getId : _getId,
+ getJSON : _getJSON,
+ getSession : _getSession//,
+ // isAttachedToDOM : _isAttachedToDOM,
+ // isVisible : _isVisible
+ };
+};
+// prototype inheritance:
+ghostdriver.WebElementReqHand.prototype = new ghostdriver.RequestHandler();
200 ghostdriver/src/session.js
View
@@ -0,0 +1,200 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+var ghostdriver = ghostdriver || {};
+
+ghostdriver.Session = function(desiredCapabilities) {
+ // private:
+ var
+ _defaultCapabilities = { // TODO - Actually try to match the "desiredCapabilities" instead of ignoring them
+ "browserName" : "phantomjs",
+ "version" : phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch,
+ "platform" : ghostdriver.system.os.name + '-' + ghostdriver.system.os.version + '-' + ghostdriver.system.os.architecture,
+ "javascriptEnabled" : true,
+ "takesScreenshot" : true,
+ "handlesAlerts" : true,
+ "databaseEnabled" : true,
+ "locationContextEnabled" : false,
+ "applicationCacheEnabled" : true,
+ "browserConnectionEnabled" : false,
+ "cssSelectorsEnabled" : true,
+ "webStorageEnabled" : true,
+ "rotatable" : true,
+ "acceptSslCerts" : false,
+ "nativeEvents" : true,
+ "proxy" : {
+ "proxyType" : "direct"
+ }
+ },
+ _timeouts = {
+ "script" : 500, //< 0.5s
+ "async script" : 5000, //< 5s
+ "implicit" : 0, //< 0s
+ "page load" : 10000 //< 10s
+ },
+ _const = {
+ DEFAULT_CURRENT_WINDOW_HANDLE : "1",
+ TIMEOUT_NAMES : {
+ SCRIPT : "script",
+ ASYNC_SCRIPT : "async script",
+ IMPLICIT : "implicit",
+ PAGE_LOAD : "page load"
+ }
+ },
+ _windows = {}, //< windows are "webpage" in Phantom-dialect
+ _currentWindowHandle = null,
+ _id = (++ghostdriver.Session.instanceCounter) + '', //< must be a string, even if I use progressive integers as unique ID
+
+ _evaluateAndWaitForLoadDecorator = function(evalFunc, onLoadFunc, onErrorFunc) {
+ var args = Array.prototype.splice.call(arguments, 0), //< convert 'arguments' to a real Array
+ timer;
+
+ // Separating arguments for the 'evaluate' call from the callback handlers
+ // NOTE: I'm also passing 'evalFunc' as first parameter for the 'evaluate' call, and '0' as timeout
+ args.splice(0, 3, evalFunc, 0);
+
+ // Register event handlers
+ this.setOneShotCallback("onLoadStarted", function() {
+ // console.log("onLoadStarted");
+ clearTimeout(timer); //< Load Started: we'll wait for "onLoadFinished" now
+ });
+ this.setOneShotCallback("onLoadFinished", function() {
+ // console.log("onLoadFinished");
+ onLoadFunc();
+ });
+ this.setOneShotCallback("onError", function() { //< TODO Currently broken in PhantomJS, fixed by using "evaluateAsync"
+ // console.log("onError");
+ clearTimeout(timer);
+ onErrorFunc();
+ });
+ // Starting timer
+ timer = setTimeout(onErrorFunc, _getTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD));
+
+ // We are ready to Eval
+ this.evaluateAsync.apply(this, args);
+
+ },
+
+ _setOneShotCallbackDecorator = function(callbackName, handlerFunc) {
+ var thePage = this;
+
+ this[callbackName] = function() {
+ handlerFunc.apply(thePage, arguments); //< call the actual handler
+ thePage[callbackName] = null; //< once done, get rid of the handling
+ };
+ };
+
+ _createNewWindow = function(page, newWindowHandle) {
+ // Decorating...
+ page.windowHandle = newWindowHandle;
+ page.evaluateAndWaitForLoad = _evaluateAndWaitForLoadDecorator;
+ page.setOneShotCallback = _setOneShotCallbackDecorator;
+
+ return page;
+ },
+
+ _getCurrentWindow = function() {
+ if (_currentWindowHandle === null) {
+ // First call to get the current window: need to create one
+ _currentWindowHandle = _const.DEFAULT_CURRENT_WINDOW_HANDLE;
+ _windows[_currentWindowHandle] = _createNewWindow(require("webpage").create(), _currentWindowHandle);
+ }
+ return _windows[_currentWindowHandle];
+ },
+
+ _closeCurrentWindow = function() {
+ if (_currentWindowHandle !== null) {
+ _closeWindow(_currentWindowHandle);
+ _currentWindowHandle = null;
+ }
+ },
+
+ _getWindow = function(windowHandle) {
+ return (typeof(_windows[windowHandle]) !== "undefined") ? _windows[windowHandle] : null;
+ },
+
+ _getWindowsCount = function() {
+ return Object.keys(_windows).length;
+ },
+
+ _getCurrentWindowHandle = function() {
+ return _currentWindowHandle;
+ },
+
+ _getWindowHandles = function() {
+ return Object.keys(_windows);
+ },
+
+ _closeWindow = function(windowHandle) {
+ _windows[windowHandle].release();
+ delete _windows[windowHandle];
+ },
+
+ _setTimeout = function(type, ms) {
+ _timeouts[type] = ms;
+ },
+
+ _getTimeout = function(type) {
+ return _timeouts[type];
+ },
+
+ _timeoutNames = function() {
+ return _const.TIMEOUT_NAMES;
+ },
+
+ _aboutToDelete = function() {
+ var k;
+
+ // Close current window first
+ _closeCurrentWindow();
+
+ // Releasing page resources and deleting the objects
+ for (k in _windows) {
+ _closeWindow(k);
+ }
+ };
+
+ // public:
+ return {
+ getCapabilities : function() { return _defaultCapabilities; },
+ getId : function() { return _id; },
+ getCurrentWindow : _getCurrentWindow,
+ closeCurrentWindow : _closeCurrentWindow,
+ getWindow : _getWindow,
+ getWindowsCount : _getWindowsCount,
+ closeWindow : _closeWindow,
+ aboutToDelete : _aboutToDelete,
+ setTimeout : _setTimeout,
+ getTimeout : _getTimeout,
+ timeoutNames : _timeoutNames,
+ getCurrentWindowHandle : _getCurrentWindowHandle,
+ getWindowHandles : _getWindowHandles
+ };
+};
+
+// public static:
+ghostdriver.Session.instanceCounter = 0;
70 ghostdriver/src/third_party/parseuri.js
View
@@ -0,0 +1,70 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+Copyright (c) 2011, Steven Levithan <stevenlevithan.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.
+*/
+
+// parseUri 1.3
+// This code was modified to fit the purpose of GhostDriver
+// URL: http://blog.stevenlevithan.com/archives/parseuri
+
+function parseUri (str) {
+ var o = parseUri.options,
+ m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
+ uri = {},
+ i = 14;
+
+ while (i--) uri[o.key[i]] = m[i] || "";
+
+ uri[o.q.name] = {};
+ uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
+ if ($1) uri[o.q.name][$1] = $2;
+ });
+
+ uri["chunks"] = (uri["source"].charAt(0) === '/')
+ ? uri["source"].substr(1).split('/')
+ : uri["source"].split('/');
+
+ return uri;
+};
+
+parseUri.options = {
+ strictMode: false,
+ key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+ q: {
+ name: "queryKey",
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+ },
+ parser: {
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+ }
+};
+
+// Adapt this to CommonJS Module Require
+if (exports) {
+ exports.options = parseUri.options;
+ exports.parse = parseUri;
+}
214 ghostdriver/src/third_party/patch_require.js
View
@@ -0,0 +1,214 @@
+/*
+This file is part of the GhostDriver project from Neustar inc.
+
+Copyright (c) 2012, Ivan De Marino <ivan.de.marino@gmail.com> - Neustar inc.
+Copyright (c) 2012, Juliusz Gonera <http://juliuszgonera.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.
+*/
+
+// This code is an almost-exact-cut-and-paste from
+// phantom-nodify (https://github.com/jgonera/phantomjs-nodify).
+// The code has been adapted by Ivan De Marino <ivan.de.marino@gmail.com>
+//
+// TODO: Remove this as soon as PhantomJS supports
+// "CommonJS Require" (http://wiki.commonjs.org/wiki/Modules/1.1).
+function patchRequire() {
+ var fs = require("fs"),
+ phantomRequire = require,
+ requireCache = {},
+ sourceIds = {},
+ phantomModules = ['fs', 'webpage', 'webserver', 'system'];
+
+ function dirname(path) {
+ return path.replace(/\/[^\/]*\/?$/, '');
+ };
+
+ function basename(path) {
+ return path.replace(/.*\//, '');
+ };
+
+ function joinPath() {
+ var args = Array.prototype.slice.call(arguments);
+ return args.join(fs.separator);
+ };
+
+ var rootPath = fs.absolute(phantom.libraryPath),
+ mainScript = joinPath(rootPath, basename(require('system').args[0]));
+
+ var loadByExt = {
+ '.js': function(module, filename) {
+ var code = fs.read(filename);
+ module._compile(code);
+ },
+
+ '.coffee': function(module, filename) {
+ var code = fs.read(filename);
+ if (typeof CoffeeScript === 'undefined') {
+ phantom.injectJs(joinPath(nodifyPath, 'coffee-script.js'));
+ }
+ try {
+ code = CoffeeScript.compile(code);
+ } catch (e) {
+ e.fileName = filename;
+ throw e;
+ }
+ module._compile(code);
+ },
+
+ '.json': function(module, filename) {
+ module.exports = JSON.parse(fs.read(filename));
+ }
+ };
+
+ var exts = Object.keys(loadByExt);
+
+ function tryFile(path) {
+ if (fs.isFile(path)) return path;
+ return null;
+ }
+
+ function tryExtensions(path) {
+ var filename;
+ for (var i=0; i<exts.length; ++i) {
+ filename = tryFile(path + exts[i]);
+ if (filename) return filename;
+ }
+ return null;
+ }
+
+ function tryPackage(path) {
+ var filename, package, packageFile = joinPath(path, 'package.json');
+ if (fs.isFile(packageFile)) {
+ package = JSON.parse(fs.read(packageFile));
+ if (!package || !package.main) return null;
+
+ filename = fs.absolute(joinPath(path, package.main));
+
+ return tryFile(filename) || tryExtensions(filename) ||
+ tryExtensions(joinPath(filename, 'index'));
+ }
+ return null;
+ }
+
+ function Module(filename) {
+ this.id = this.filename = filename;
+ this.dirname = dirname(filename);
+ this.exports = {};
+ }
+
+ Module.prototype._getPaths = function(request) {
+ var paths = [], dir;
+
+ if (request[0] === '.') {
+ paths.push(fs.absolute(joinPath(this.dirname, request)));
+ } else if (request[0] === '/') {
+ paths.push(fs.absolute(request));
+ } else {
+ dir = this.dirname;
+ while (dir !== '') {
+ paths.push(joinPath(dir, 'node_modules', request));
+ dir = dirname(dir);
+ }
+ paths.push(joinPath(nodifyPath, 'modules', request));
+ }
+
+ return paths;
+ };
+
+ Module.prototype._getFilename = function(request) {
+ var path, filename = null, paths = this._getPaths(request);
+
+ for (var i=0; i<paths.length && !filename; ++i) {
+ path = paths[i];
+ filename = tryFile(path) || tryExtensions(path) || tryPackage(path) ||
+ tryExtensions(joinPath(path, 'index'));
+ }
+
+ return filename;
+ };
+
+ Module.prototype._getRequire = function() {
+ var self = this;
+
+ function require(request) {
+ return self.require(request);
+ }
+ require.cache = requireCache;
+
+ return require;
+ };
+
+ Module.prototype._load = function() {
+ var ext = this.filename.match(/\.[^.]+$/);
+ if (!ext) ext = '.js';
+ loadByExt[ext](this, this.filename);
+ };
+
+ Module.prototype._compile = function(code) {
+ // a trick to associate Error's sourceId with file
+ code += ";throw new Error('__sourceId__');";
+ try {
+ var fn = new Function('require', 'exports', 'module', code);
+ fn(this._getRequire(), this.exports, this);
+ } catch (e) {
+ // assign source id (check if already assigned to avoid reassigning
+ // on exceptions propagated from other files)
+ if (!sourceIds.hasOwnProperty(e.sourceId)) {
+ sourceIds[e.sourceId] = this.filename;
+ }
+ // if it's not the error we added, propagate it
+ if (e.message !== '__sourceId__') {
+ throw e;
+ }
+ }
+ };
+
+ Module.prototype.require = function(request) {
+ if (phantomModules.indexOf(request) !== -1) {
+ return phantomRequire(request);
+ }
+
+ var filename = this._getFilename(request);
+ if (!filename) {
+ var e = new Error("Cannot find module '" + request + "'");
+ e.fileName = this.filename;
+ e.line = '';
+ throw e;
+ }
+
+ if (requireCache.hasOwnProperty(filename)) {
+ return requireCache[filename].exports;
+ }
+
+ var module = new Module(filename);
+ module._load();
+ requireCache[filename] = module;
+
+ return module.exports;
+ };
+
+ require = new Module(mainScript)._getRequire();
+};
+
+// Execute the patching
+patchRequire();
15 ghostdriver/src/third_party/webdriver-atoms/active_element.js
View
@@ -0,0 +1,15 @@
+function(){return function(){var g=void 0,h=!0,i=null,l=!1,m=this;
+function n(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 aa(a){var b=n(a);return"array"==b||"object"==b&&"number"==typeof a.length}function ba(a){a=n(a);return"object"==a||"array"==a||"function"==a}var ca=Date.now||function(){return+new Date};function o(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function da(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}
+function ea(a,b){for(var c=0,d=(""+a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=(""+b).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),f=Math.max(d.length,e.length),B=0;0==c&&B<f;B++){var ya=d[B]||"",za=e[B]||"",Aa=RegExp("(\\d*)(\\D*)","g"),Ba=RegExp("(\\d*)(\\D*)","g");do{var j=Aa.exec(ya)||["","",""],k=Ba.exec(za)||["","",""];if(0==j[0].length&&0==k[0].length)break;c=((0==j[1].length?0:parseInt(j[1],10))<(0==k[1].length?0:parseInt(k[1],10))?-1:(0==j[1].length?0:parseInt(j[1],10))>(0==k[1].length?
+0:parseInt(k[1],10))?1:0)||((0==j[2].length)<(0==k[2].length)?-1:(0==j[2].length)>(0==k[2].length)?1:0)||(j[2]<k[2]?-1:j[2]>k[2]?1:0)}while(0==c)}return c};var p,q,r,s;function t(){return m.navigator?m.navigator.userAgent:i}s=r=q=p=l;var u;if(u=t()){var fa=m.navigator;p=0==u.indexOf("Opera");q=!p&&-1!=u.indexOf("MSIE");r=!p&&-1!=u.indexOf("WebKit");s=!p&&!r&&"Gecko"==fa.product}var v=p,w=q,x=s,y=r,z;
+a:{var A="",C;if(v&&m.opera)var D=m.opera.version,A="function"==typeof D?D():D;else if(x?C=/rv\:([^\);]+)(\)|;)/:w?C=/MSIE\s+([^\);]+)(\)|;)/:y&&(C=/WebKit\/(\S+)/),C)var ga=C.exec(t()),A=ga?ga[1]:"";if(w){var E,ha=m.document;E=ha?ha.documentMode:g;if(E>parseFloat(A)){z=""+E;break a}}z=A}var ia={};function F(a){return ia[a]||(ia[a]=0<=ea(z,a))}var ja={};function ka(){return ja[9]||(ja[9]=w&&document.documentMode&&9<=document.documentMode)};var G=window;function H(a){this.stack=Error().stack||"";a&&(this.message=""+a)}o(H,Error);H.prototype.name="CustomError";function la(a,b){b.unshift(a);H.call(this,da.apply(i,b));b.shift()}o(la,H);la.prototype.name="AssertionError";function I(a,b){for(var c=a.length,d=Array(c),e="string"==typeof a?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(g,e[f],f,a));return d};!w||ka();!x&&!w||w&&ka()||x&&F("1.9.1");w&&F("9");function ma(a,b){var c={},d;for(d in a)b.call(g,a[d],d,a)&&(c[d]=a[d]);return c}function na(a,b){var c={},d;for(d in a)c[d]=b.call(g,a[d],d,a);return c}function oa(a,b){for(var c in a)if(b.call(g,a[c],c,a))return c};var J,K,L,M,N,O,P;P=O=N=M=L=K=J=l;var Q=t();Q&&(-1!=Q.indexOf("Firefox")?J=h:-1!=Q.indexOf("Camino")?K=h:-1!=Q.indexOf("iPhone")||-1!=Q.indexOf("iPod")?L=h:-1!=Q.indexOf("iPad")?M=h:-1!=Q.indexOf("Android")?N=h:-1!=Q.indexOf("Chrome")?O=h:-1!=Q.indexOf("Safari")&&(P=h));var pa=K,qa=L,ra=M,sa=N,ta=O,ua=P;a:{var R;if(J)R=/Firefox\/([0-9.]+)/;else{if(w||v)break a;ta?R=/Chrome\/([0-9.]+)/:ua?R=/Version\/([0-9.]+)/:qa||ra?R=/Version\/(\S+).*Mobile\/(\S+)/:sa?R=/Android\s+([0-9.]+)(?:.*Version\/([0-9.]+))?/:pa&&(R=/Camino\/([0-9.]+)/)}R&&R.exec(t())};var va;function S(a){return wa?va(a):w?0<=ea(document.documentMode,a):F(a)}var wa=function(){if(!x)return l;var a=m.Components;if(!a)return l;try{if(!a.classes)return l}catch(b){return l}var c=a.classes,a=a.interfaces,d=c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),e=c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).platformVersion;va=function(a){return 0<=d.i(e,""+a)};return h}();function T(a,b){this.code=a;this.message=b||"";this.name=xa[a]||xa[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}o(T,Error);
+var xa={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
+T.prototype.toString=function(){return"["+this.name+"] "+this.message};!v&&(!y||S("533"));var U="StopIteration"in m?m.StopIteration:Error("StopIteration");function Ca(){}Ca.prototype.next=function(){throw U;};function V(a,b,c,d,e){this.a=!!b;a&&W(this,a,d);this.depth=e!=g?e:this.c||0;this.a&&(this.depth*=-1);this.g=!c}o(V,Ca);V.prototype.b=i;V.prototype.c=0;V.prototype.f=l;function W(a,b,c){if(a.b=b)a.c="number"==typeof c?c:1!=a.b.nodeType?0:a.a?-1:1}
+V.prototype.next=function(){var a;if(this.f){if(!this.b||this.g&&0==this.depth)throw U;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?W(this,c):W(this,a,-1*b)}else(c=this.a?a.previousSibling:a.nextSibling)?W(this,c):W(this,a.parentNode,-1*b);this.depth+=this.c*(this.a?-1:1)}else this.f=h;a=this.b;if(!this.b)throw U;return a};
+V.prototype.splice=function(a){var b=this.b,c=this.a?1:-1;this.c==c&&(this.c=-1*c,this.depth+=this.c*(this.a?-1:1));this.a=!this.a;V.prototype.next.call(this);this.a=!this.a;for(var c=aa(arguments[0])?arguments[0]:arguments,d=c.length-1;0<=d;d--)b.parentNode&&b.parentNode.insertBefore(c[d],b.nextSibling);b&&b.parentNode&&b.parentNode.removeChild(b)};function Da(a,b,c,d){V.call(this,a,b,c,i,d)}o(Da,V);Da.prototype.next=function(){do Da.h.next.call(this);while(-1==this.c);return this.b};function Ea(){return document.activeElement||document.body};function Fa(a,b){var c=[];Ga(new Ha(b),a,c);return c.join("")}function Ha(a){this.d=a}