diff options
author | jmikhail@google.com <jmikhail@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-24 19:28:34 +0000 |
---|---|---|
committer | jmikhail@google.com <jmikhail@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-24 19:28:34 +0000 |
commit | f7eb1ddf7852f37b01cdd6e163742e4d135f9b78 (patch) | |
tree | 729f70519f9d2e782ec8d0787aedf74603c83188 /third_party/webdriver | |
parent | e26d977f4f074cc68d40f7ff439992c105f260a8 (diff) | |
download | chromium_src-f7eb1ddf7852f37b01cdd6e163742e4d135f9b78.zip chromium_src-f7eb1ddf7852f37b01cdd6e163742e4d135f9b78.tar.gz chromium_src-f7eb1ddf7852f37b01cdd6e163742e4d135f9b78.tar.bz2 |
Python bindings for the remote webdriver protcol.
For more information see:
http://code.google.com/p/selenium/wiki/PythonBindings
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/3358016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@60507 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/webdriver')
-rw-r--r-- | third_party/webdriver/py/COPYING | 204 | ||||
-rw-r--r-- | third_party/webdriver/py/LICENSE | 4 | ||||
-rw-r--r-- | third_party/webdriver/py/README.chromium | 14 | ||||
-rw-r--r-- | third_party/webdriver/py/__init__.py | 16 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/__init__.py | 16 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/common/__init__.py | 16 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/common/exceptions.py | 76 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/__init__.py | 16 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/WebDriver.py | 362 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/__init__.py | 16 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/command.py | 79 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/errorhandler.py | 98 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/remote_connection.py | 245 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/utils.py | 112 | ||||
-rw-r--r-- | third_party/webdriver/py/selenium/remote/webdriver/webelement.py | 162 |
15 files changed, 1436 insertions, 0 deletions
diff --git a/third_party/webdriver/py/COPYING b/third_party/webdriver/py/COPYING new file mode 100644 index 0000000..80a4762 --- /dev/null +++ b/third_party/webdriver/py/COPYING @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2007-2009 Google Inc. + Copyright 2007-2009 WebDriver committers + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/third_party/webdriver/py/LICENSE b/third_party/webdriver/py/LICENSE new file mode 100644 index 0000000..d4e0e72 --- /dev/null +++ b/third_party/webdriver/py/LICENSE @@ -0,0 +1,4 @@ +NAME: WebDriver +URL: http://selenium.googlecode.com/svn/trunk/remote/client/src/py/ + http://selenium.googlecode.com/svn/trunk/common/src/py/ +LICENSE: Apache 2 diff --git a/third_party/webdriver/py/README.chromium b/third_party/webdriver/py/README.chromium new file mode 100644 index 0000000..c8c699f --- /dev/null +++ b/third_party/webdriver/py/README.chromium @@ -0,0 +1,14 @@ +Name: Webdriver +URL: http://seleniumhq.org +LICENSE: APACHE 2 +VERSION: Selenium 2 alpha 5 + +The code in this tree is a manual checkin of the python bindings +found on the Selenium HQ website. The source code is the same +however the directory layout has changed. + +Source code pulled from: +http://selenium.googlecode.com/svn/trunk/remote/client/src/py/ +http://selenium.googlecode.com/svn/trunk/common/src/py/ +At revision 9575 + diff --git a/third_party/webdriver/py/__init__.py b/third_party/webdriver/py/__init__.py new file mode 100644 index 0000000..5555f0b --- /dev/null +++ b/third_party/webdriver/py/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/third_party/webdriver/py/selenium/__init__.py b/third_party/webdriver/py/selenium/__init__.py new file mode 100644 index 0000000..5555f0b --- /dev/null +++ b/third_party/webdriver/py/selenium/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/third_party/webdriver/py/selenium/common/__init__.py b/third_party/webdriver/py/selenium/common/__init__.py new file mode 100644 index 0000000..5555f0b --- /dev/null +++ b/third_party/webdriver/py/selenium/common/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/third_party/webdriver/py/selenium/common/exceptions.py b/third_party/webdriver/py/selenium/common/exceptions.py new file mode 100644 index 0000000..7b54af9 --- /dev/null +++ b/third_party/webdriver/py/selenium/common/exceptions.py @@ -0,0 +1,76 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exceptions that may happen in all the webdriver code.""" + +class ErrorInResponseException(Exception): + """An error has occurred on the server side. + + This may happen when communicating with the firefox extension + or the remote driver server.""" + def __init__(self, response, msg): + Exception.__init__(self, msg) + self.response = response + +class InvalidSwitchToTargetException(Exception): + """The frame or window target to be switched doesn't exist.""" + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class NoSuchFrameException(InvalidSwitchToTargetException): + def __init__(self, msg=None): + InvalidSwitchToTargetException.__init__(self, msg) + +class NoSuchWindowException(InvalidSwitchToTargetException): + def __init__(self, msg=None): + InvalidSwitchToTargetException.__init__(self, msg) + +class NoSuchElementException(Exception): + """find_element_by_* can't find the element.""" + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class NoSuchAttributeException(Exception): + """find_element_by_* can't find the element.""" + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class StaleElementReferenceException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class ElementNotVisibleException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class InvalidElementStateException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class ElementNotSelectableException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class InvalidCookieDomainException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class UnableToSetCookieException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + +class RemoteDriverServerException(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) diff --git a/third_party/webdriver/py/selenium/remote/__init__.py b/third_party/webdriver/py/selenium/remote/__init__.py new file mode 100644 index 0000000..5555f0b --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/third_party/webdriver/py/selenium/remote/webdriver/WebDriver.py b/third_party/webdriver/py/selenium/remote/webdriver/WebDriver.py new file mode 100644 index 0000000..b7246a0 --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/WebDriver.py @@ -0,0 +1,362 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The WebDriver implementation.""" + +import base64 +from command import Command +from webelement import WebElement +from remote_connection import RemoteConnection +from errorhandler import ErrorHandler + +class WebDriver(object): + """Controls a browser by sending commands to a remote server. + + This server is expected to be running the WebDriver wire protocol as defined + here: + http://code.google.com/p/selenium/wiki/JsonWireProtocol + + Attributes: + command_executor - The command.CommandExecutor object used to execute + commands. + error_handler - errorhandler.ErrorHandler object used to verify that the + server did not return an error. + session_id - The session ID to send with every command. + capabilities - A dictionary of capabilities of the underlying browser for + this instance's session. + """ + + def __init__(self, command_executor, browser_name, platform, version='', + javascript_enabled=True): + """Create a new driver that will issue commands using the wire protocol. + + Args: + command_executor - Either a command.CommandExecutor object or a string + that specifies the URL of a remote server to send commands to. + browser_name - A string indicating which browser to request a new + session for from the remote server. Should be one of + {mobile safari|firefox|internet explorer|htmlunit|chrome}. + platform - A string indicating the desired platform to request from + the remote server. Should be one of + {WINDOWS|XP|VISTA|MAC|LINUX|UNIX|ANY}. + version - A string indicating a specific browser version to request, + or an empty string ot use any available browser. Defaults to the + empty string. + javascript_enabled - Whether the requested browser should support + JavaScript. Defaults to True. + """ + self.command_executor = command_executor + if type(self.command_executor) is str: + self.command_executor = RemoteConnection(command_executor) + + self.session_id = None + self.capabilities = {} + self.error_handler = ErrorHandler() + + self.start_client() + self.start_session(browser_name=browser_name, + platform=platform, + version=version, + javascript_enabled=javascript_enabled) + + @property + def name(self): + """Returns the name of the underlying browser for this instance.""" + if 'browserName' in self.capabilities: + return self.capabilities['browserName'] + else: + raise KeyError('browserName not specified in session capabilities') + + def start_client(self): + """Called before starting a new session. + + This method may be overridden to define custom startup behavior. + """ + pass + + def stop_client(self): + """Called after executing a quit command. + + This method may be overridden to define custom shutdown behavior. + """ + pass + + def start_session(self, browser_name, platform=None, version=None, + javascript_enabled=False): + """Creates a new session with the desired capabilities. + + Args: + browser_name: The name of the browser to request. + version: Which browser version to request. + platform: Which platform to request the browser on. + javascript_enabled: Whether the new session should support JavaScript. + """ + response = self._execute(Command.NEW_SESSION, { + 'desiredCapabilities': { + 'browserName': browser_name, + 'platform': platform or 'ANY', + 'version': version or '', + 'javascriptEnabled': javascript_enabled + } + }) + self.session_id = response['sessionId'] + self.capabilities = response['value'] + + def _wrap_value(self, value): + if isinstance(value, dict): + converted = {} + for key, val in value.items(): + converted[key] = self._wrap_value(val) + return converted + elif isinstance(value, WebElement): + return {'ELEMENT': value.id} + elif isinstance(value, list): + return list(self._wrap_value(item) for item in value) + else: + return value + + def create_web_element(self, element_id): + return WebElement(self, element_id) + + def _unwrap_value(self, value): + if isinstance(value, dict) and 'ELEMENT' in value: + return self.create_web_element(value['ELEMENT']) + elif isinstance(value, list): + return list(self._unwrap_value(item) for item in value) + else: + return value + + def _execute(self, driver_command, params=None): + """Sends a command to be executed by a command.CommandExecutor. + + Args: + driver_command: The name of the command to execute as a string. + params: A dictionary of named parameters to send with the command. + + Returns: + The command's JSON response loaded into a dictionary object. + """ + if not params: + params = {'sessionId': self.session_id} + elif 'sessionId' not in params: + params['sessionId'] = self.session_id + + params = self._wrap_value(params) + response = self.command_executor.execute(driver_command, params) + if response: + self.error_handler.check_response(response) + response['value'] = self._unwrap_value( + response.get('value', None)) + return response + # If the server doesn't send a response, assume the command was + # a success + return {'success': 0, 'value': None} + + def get(self, url): + """Loads a web page in the current browser.""" + self._execute(Command.GET, {'url': url}) + + def get_title(self): + """Gets the title of the current page.""" + resp = self._execute(Command.GET_TITLE) + return resp['value'] + + def find_element_by_id(self, id_): + """Finds element by id.""" + return self._find_element_by("id", id_) + + def find_elements_by_xpath(self, xpath): + """Finds multiple elements by xpath.""" + return self._find_elements_by("xpath", xpath) + + def find_element_by_xpath(self, xpath): + """Finds an element by xpath.""" + return self._find_element_by("xpath", xpath) + + def find_element_by_link_text(self, link_text): + """Finds an element by its link text.""" + return self._find_element_by("link text", link_text) + + def find_element_by_partial_link_text(self, link_text): + """Finds an element by a partial match of its link text.""" + return self._find_element_by("partial link text", link_text) + + def find_elements_by_link_text(self, link_text): + """Finds elements by their link text.""" + return self._find_elements_by("link text", link_text) + + def find_elements_by_partial_link_text(self, link_text): + """Finds elements by a partial match of their link text.""" + return self._find_elements_by("partial link text", link_text) + + def find_element_by_name(self, name): + """Finds an element by its name.""" + return self._find_element_by("name", name) + + def find_elements_by_name(self, name): + """Finds elements by their name.""" + return self._find_elements_by("name", name) + + def find_element_by_tag_name(self, name): + """Finds an element by its tag name.""" + return self._find_element_by("tag name", name) + + def find_elements_by_tag_name(self, name): + """Finds elements by their tag name.""" + return self._find_elements_by("tag name", name) + + def find_element_by_class_name(self, name): + """Finds an element by their class name.""" + return self._find_element_by("class name", name) + + def find_elements_by_class_name(self, name): + """Finds elements by their class name.""" + return self._find_elements_by("class name", name) + + def execute_script(self, script, *args): + if len(args) == 1: + converted_args = args[0] + else: + converted_args = list(args) + converted_args = list(args) + return self._execute( + Command.EXECUTE_SCRIPT, + {'script': script, 'args':converted_args})['value'] + + def get_current_url(self): + """Gets the current url.""" + return self._execute(Command.GET_CURRENT_URL)['value'] + + def get_page_source(self): + """Gets the page source.""" + return self._execute(Command.GET_PAGE_SOURCE)['value'] + + def close(self): + """Closes the current window.""" + self._execute(Command.CLOSE) + + def quit(self): + """Quits the driver and close every associated window.""" + try: + self._execute(Command.QUIT) + finally: + self.stop_client() + + def get_current_window_handle(self): + return self._execute(Command.GET_CURRENT_WINDOW_HANDLE)['value'] + + def get_window_handles(self): + return self._execute(Command.GET_WINDOW_HANDLES)['value'] + + def switch_to_active_element(self): + """Returns the element with focus, or BODY if nothing has focus.""" + return self._execute(Command.GET_ACTIVE_ELEMENT)['value'] + + def switch_to_window(self, window_name): + """Switches focus to a window.""" + self._execute(Command.SWITCH_TO_WINDOW, {'name': window_name}) + + def switch_to_frame(self, index_or_name): + """Switches focus to a frame by index or name.""" + self._execute(Command.SWITCH_TO_FRAME, {'id': index_or_name}) + + def refresh(self): + """Reloads the current page.""" + self._execute(Command.REFRESH) + + def back(self): + """Goes back in browser history.""" + self._execute(Command.GO_BACK) + + def forward(self): + """Goes forward in browser history.""" + self._execute(Command.GO_FORWARD) + # Options + def get_cookies(self): + """Gets all the cookies. Return a set of dicts.""" + return self._execute(Command.GET_ALL_COOKIES)['value'] + + def get_cookie(self, name): + """Get a single cookie. Returns the desired cookie dict or None.""" + cookies = self.get_cookies() + for cookie in cookies: + if cookie['name'] == name: + return cookie + return None + + def delete_cookie(self, name): + """Delete a cookie with the given name.""" + self._execute(Command.DELETE_COOKIE, {'name': name}) + + def delete_all_cookies(self): + """Delete all the cookies.""" + self._execute(Command.DELETE_ALL_COOKIES) + + def add_cookie(self, cookie_dict): + self._execute(Command.ADD_COOKIE, {'cookie': cookie_dict}) + + def _find_element_by(self, by, value): + return self._execute(Command.FIND_ELEMENT, + {'using': by, 'value': value})['value'] + + def _find_elements_by(self, by, value): + return self._execute(Command.FIND_ELEMENTS, + {'using': by, 'value': value})['value'] + + def get_screenshot_as_file(self, filename): + """Gets the screenshot of the current window. Returns False if there is + any IOError, else returns True.""" + png = self._execute(Command.SCREENSHOT)['value'] + f = None + try: + f = open(filename, 'w') + f.write(base64.decode(png)) + f.close() + except IOError: + return False + + return True + + def get_screenshot_as_base64(self): + """Gets the screenshot of the current window as a base64 encoded string which + is useful in embedded images in HTML.""" + return self._execute(Command.SCREENSHOT)['value'] + + +def connect(name, version="", server="http://localhost:4444", platform=None, + javascript_enabled=True, path="/wd/hub"): + """Convenience function to connect to a server + Args: + name - A string indicating which browser to request a new + session for from the remote server. Should be one of + {mobile safari|firefox|internet explorer|htmlunit|chrome}. + version - A string indicating a specific browser version to request, + or an empty string ot use any available browser. Defaults to the + empty string. + server - Server location (without path). Defaults to + "http://localhost:4444". + platform - A string indicating the desired platform to request from + the remote server. Should be one of + {WINDOWS|XP|VISTA|MAC|LINUX|UNIX|ANY} or None. Defaults to None. + javascript_enabled - Whether the requested browser should support + JavaScript. Defaults to True. + path - path in server url. Defaults to "/wd/hub/" + """ + if not path.startswith("/"): + path = "/" + path + url = "%s%s" % (server, path) + wd = WebDriver(url, name, platform, version, javascript_enabled) + + return wd diff --git a/third_party/webdriver/py/selenium/remote/webdriver/__init__.py b/third_party/webdriver/py/selenium/remote/webdriver/__init__.py new file mode 100644 index 0000000..5555f0b --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/third_party/webdriver/py/selenium/remote/webdriver/command.py b/third_party/webdriver/py/selenium/remote/webdriver/command.py new file mode 100644 index 0000000..3eae3b1 --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/command.py @@ -0,0 +1,79 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Command(object): + """Defines constants for the standard WebDriver commands. + + While these constants have no meaning in and of themselves, they are + used to marshal commands through a service that implements WebDriver's + remote wire protocol: + http://code.google.com/p/selenium/wiki/JsonWireProtocol + """ + + # Keep in sync with org.openqa.selenium.remote.DriverCommand + + NEW_SESSION = "newSession" + DELETE_SESSION = "deleteSession" + CLOSE = "close" + QUIT = "quit" + GET = "get" + REFRESH = "refresh" + GO_BACK = "goBack" + GO_FORWARD = "goForward" + REFRESH = "refresh" + ADD_COOKIE = "addCookie" + GET_COOKIE = "getCookie" + GET_ALL_COOKIES = "getCookies" + DELETE_COOKIE = "deleteCookie" + DELETE_ALL_COOKIES = "deleteAllCookies" + FIND_ELEMENT = "findElement" + FIND_ELEMENTS = "findElements" + FIND_CHILD_ELEMENT = "findChildElement" + FIND_CHILD_ELEMENTS = "findChildElements" + CLEAR_ELEMENT = "clearElement" + CLICK_ELEMENT = "clickElement" + HOVER_OVER_ELEMENT = "hoverOverElement" + SEND_KEYS_TO_ELEMENT = "sendKeysToElement" + SUBMIT_ELEMENT = "submitElement" + TOGGLE_ELEMENT = "toggleElement" + GET_CURRENT_WINDOW_HANDLE = "getCurrentWindowHandle" + GET_WINDOW_HANDLES = "getWindowHandles" + SWITCH_TO_WINDOW = "switchToWindow" + SWITCH_TO_FRAME = "switchToFrame" + GET_ACTIVE_ELEMENT = "getActiveElement" + GET_CURRENT_URL = "getCurrentUrl" + GET_PAGE_SOURCE = "getPageSource" + GET_TITLE = "getTitle" + EXECUTE_SCRIPT = "executeScript" + GET_SPEED = "getSpeed" + SET_SPEED = "setSpeed" + SET_BROWSER_VISIBLE = "setBrowserVisible" + IS_BROWSER_VISIBLE = "isBrowserVisible" + GET_ELEMENT_TEXT = "getElementText" + GET_ELEMENT_VALUE = "getElementValue" + GET_ELEMENT_TAG_NAME = "getElementTagName" + SET_ELEMENT_SELECTED = "setElementSelected" + DRAG_ELEMENT = "dragElement" + IS_ELEMENT_SELECTED = "isElementSelected" + IS_ELEMENT_ENABLED = "isElementEnabled" + IS_ELEMENT_DISPLAYED = "isElementDisplayed" + GET_ELEMENT_LOCATION = "getElementLocation" + GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = ( + "getElementLocationOnceScrolledIntoView") + GET_ELEMENT_SIZE = "getElementSize" + GET_ELEMENT_ATTRIBUTE = "getElementAttribute" + GET_ELEMENT_VALUE_OF_CSS_PROPERTY = "getElementValueOfCssProperty" + ELEMENT_EQUALS = "elementEquals" + SCREENSHOT = "screenshot" diff --git a/third_party/webdriver/py/selenium/remote/webdriver/errorhandler.py b/third_party/webdriver/py/selenium/remote/webdriver/errorhandler.py new file mode 100644 index 0000000..98058c1 --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/errorhandler.py @@ -0,0 +1,98 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from selenium.common.exceptions import ElementNotSelectableException +from selenium.common.exceptions import ElementNotVisibleException +from selenium.common.exceptions import InvalidCookieDomainException +from selenium.common.exceptions import InvalidElementStateException +from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import NoSuchFrameException +from selenium.common.exceptions import NoSuchWindowException +from selenium.common.exceptions import StaleElementReferenceException +from selenium.common.exceptions import UnableToSetCookieException +from selenium.common.exceptions import ErrorInResponseException + + +class ErrorCode(object): + """Error codes defined in the WebDriver wire protocol.""" + + # Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h + + SUCCESS = 0 + NO_SUCH_ELEMENT = 7 + NO_SUCH_FRAME = 8 + UNKNOWN_COMMAND = 9 + STALE_ELEMENT_REFERENCE = 10 + ELEMENT_NOT_VISIBLE = 11 + INVALID_ELEMENT_STATE = 12 + UNKNOWN_ERROR = 13 + ELEMENT_IS_NOT_SELECTABLE = 15 + XPATH_LOOKUP_ERROR = 19 + NO_SUCH_WINDOW = 23 + INVALID_COOKIE_DOMAIN = 24 + UNABLE_TO_SET_COOKIE = 25 + + +class ErrorHandler(object): + """Handles errors returned by the WebDriver server.""" + + def check_response(self, response): + """Checks that a JSON response from the WebDriver does not have an error. + + Args: + response - The JSON response from the WebDriver server as a dictionary + object. + + Raises: + If the response contains an error message. + """ + status = response['status'] + if status == ErrorCode.SUCCESS: + return + + exception_class = ErrorInResponseException + if status == ErrorCode.NO_SUCH_ELEMENT: + exception_class = NoSuchElementException + elif status == ErrorCode.NO_SUCH_FRAME: + exception_class = NoSuchFrameException + elif status == ErrorCode.NO_SUCH_WINDOW: + exception_class = NoSuchWindowException + elif status == ErrorCode.STALE_ELEMENT_REFERENCE: + exception_class = StaleElementReferenceException + elif status == ErrorCode.ELEMENT_NOT_VISIBLE: + exception_class = ElementNotVisibleException + elif status == ErrorCode.INVALID_ELEMENT_STATE: + exception_class = InvalidElementStateException + elif status == ErrorCode.ELEMENT_IS_NOT_SELECTABLE: + exception_class = ElementNotSelectableException + elif status == ErrorCode.INVALID_COOKIE_DOMAIN: + exception_class = InvalidCookieDomainException + elif status == ErrorCode.UNABLE_TO_SET_COOKIE: + exception_class = UnableToSetCookieException + + value = response['value'] + if type(value) is str: + if exception_class == ErrorInResponseException: + raise exception_class(response, value) + raise exception_class(value) + + message = '' + if 'message' in value: + message = value['message'] + + # TODO: What about 'screen' and 'stackTrace'? + if exception_class == ErrorInResponseException: + raise exception_class(response, message) + raise exception_class(message) diff --git a/third_party/webdriver/py/selenium/remote/webdriver/remote_connection.py b/third_party/webdriver/py/selenium/remote/webdriver/remote_connection.py new file mode 100644 index 0000000..6c23684 --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/remote_connection.py @@ -0,0 +1,245 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import string +import urllib2 + +from command import Command +import utils + + +class Request(urllib2.Request): + """Extends the urllib2.Request to support all HTTP request types.""" + + def __init__(self, url, data=None, method=None): + """Initialise a new HTTP request. + + Args: + url - String for the URL to send the request to. + data - Data to send with the request. + """ + if method is None: + method = data is not None and 'POST' or 'GET' + elif method != 'POST' and method != 'PUT': + data = None + self._method = method + urllib2.Request.__init__(self, url, data=data) + + def get_method(self): + """Returns the HTTP method used by this request.""" + return self._method + + +class Response(object): + """Represents an HTTP response. + + Attributes: + fp - File object for the response body. + code - The HTTP status code returned by the server. + headers - A dictionary of headers returned by the server. + url - URL of the retrieved resource represented by this Response. + """ + + def __init__(self, fp, code, headers, url): + """Initialise a new Response. + + Args: + fp - The response body file object. + code - The HTTP status code returned by the server. + headers - A dictionary of headers returned by the server. + url - URL of the retrieved resource represented by this Response. + """ + self.fp = fp + self.read = fp.read + self.code = code + self.headers = headers + self.url = url + + def close(self): + """Close the response body file object.""" + self.read = None + self.fp = None + + def info(self): + """Returns the response headers.""" + return self.headers + + def geturl(self): + """Returns the URL for the resource returned in this response.""" + return self.url + + +class HttpErrorHandler(urllib2.HTTPDefaultErrorHandler): + """A custom HTTP error handler. + + Used to return Response objects instead of raising an HTTPError exception. + """ + + def http_error_default(self, req, fp, code, msg, headers): + """Default HTTP error handler. + + Args: + req - The original Request object. + fp - The response body file object. + code - The HTTP status code returned by the server. + msg - The HTTP status message returned by the server. + headers - The response headers. + + Returns: + A new Response object. + """ + return Response(fp, code, headers, req.get_full_url()) + + +class RemoteConnection(object): + """A connection with the Remote WebDriver server. + + Communicates with the server using the WebDriver wire protocol: + http://code.google.com/p/selenium/wiki/JsonWireProtocol + """ + + def __init__(self, remote_server_addr): + self._url = remote_server_addr + self._commands = { + Command.NEW_SESSION: ('POST', '/session'), + Command.QUIT: ('DELETE', '/session/$sessionId'), + Command.GET_CURRENT_WINDOW_HANDLE: + ('GET', '/session/$sessionId/window_handle'), + Command.GET_WINDOW_HANDLES: + ('GET', '/session/$sessionId/window_handles'), + Command.GET: ('POST', '/session/$sessionId/url'), + Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'), + Command.GO_BACK: ('POST', '/session/$sessionId/back'), + Command.REFRESH: ('POST', '/session/$sessionId/refresh'), + Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'), + Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'), + Command.GET_TITLE: ('GET', '/session/$sessionId/title'), + Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'), + Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'), + Command.SET_BROWSER_VISIBLE: + ('POST', '/session/$sessionId/visible'), + Command.IS_BROWSER_VISIBLE: ('GET', '/session/$sessionId/visible'), + Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'), + Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'), + Command.GET_ACTIVE_ELEMENT: + ('POST', '/session/$sessionId/element/active'), + Command.FIND_CHILD_ELEMENT: + ('POST', '/session/$sessionId/element/$id/element'), + Command.FIND_CHILD_ELEMENTS: + ('POST', '/session/$sessionId/element/$id/elements'), + Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'), + Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'), + Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'), + Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'), + Command.SEND_KEYS_TO_ELEMENT: + ('POST', '/session/$sessionId/element/$id/value'), + Command.GET_ELEMENT_VALUE: + ('GET', '/session/$sessionId/element/$id/value'), + Command.GET_ELEMENT_TAG_NAME: + ('GET', '/session/$sessionId/element/$id/name'), + Command.IS_ELEMENT_SELECTED: + ('GET', '/session/$sessionId/element/$id/selected'), + Command.SET_ELEMENT_SELECTED: + ('POST', '/session/$sessionId/element/$id/selected'), + Command.TOGGLE_ELEMENT: + ('POST', '/session/$sessionId/element/$id/toggle'), + Command.IS_ELEMENT_ENABLED: + ('GET', '/session/$sessionId/element/$id/enabled'), + Command.IS_ELEMENT_DISPLAYED: + ('GET', '/session/$sessionId/element/$id/displayed'), + Command.HOVER_OVER_ELEMENT: + ('POST', '/session/$sessionId/element/$id/hover'), + Command.GET_ELEMENT_LOCATION: + ('GET', '/session/$sessionId/element/$id/location'), + Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW: + ('GET', '/session/$sessionId/element/$id/location_in_view'), + Command.GET_ELEMENT_SIZE: + ('GET', '/session/$sessionId/element/$id/size'), + Command.GET_ELEMENT_ATTRIBUTE: + ('GET', '/session/$sessionId/element/$id/attribute/$name'), + Command.ELEMENT_EQUALS: + ('GET', '/session/$sessionId/element/$id/equals/$other'), + Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'), + Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'), + Command.DELETE_ALL_COOKIES: + ('DELETE', '/session/$sessionId/cookie'), + Command.DELETE_COOKIE: + ('DELETE', '/session/$sessionId/cookie/$name'), + Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'), + Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'), + Command.CLOSE: ('DELETE', '/session/$sessionId/window'), + Command.DRAG_ELEMENT: + ('POST', '/session/$sessionId/element/$id/drag'), + Command.GET_SPEED: ('GET', '/session/$sessionId/speed'), + Command.SET_SPEED: ('POST', '/session/$sessionId/speed'), + Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY: + ('GET', '/session/$sessionId/element/$id/css/$propertyName') + } + + def execute(self, command, params): + """Send a command to the remote server. + + Any path subtitutions required for the URL mapped to the command should be + included in the command parameters. + + Args: + command - A string specifying the command to execute. + params - A dictionary of named parameters to send with the command as + its JSON payload. + """ + command_info = self._commands[command] + assert command_info is not None, 'Unrecognised command %s' % command + data = utils.dump_json(params) + path = string.Template(command_info[1]).substitute(params) + url = '%s%s' % (self._url, path) + return self._request(url, method=command_info[0], data=data) + + def _request(self, url, data=None, method=None): + """Send an HTTP request to the remote server. + + Args: + method - A string for the HTTP method to send the request with. + url - The URL to send the request to. + body - The message body to send. + + Returns: + A dictionary with the server's parsed JSON response. + """ + logging.debug('%s %s %s' % (method, url, data)) + + request = Request(url, data=data, method=method) + request.add_header('Accept', 'application/json') + + opener = urllib2.build_opener(urllib2.HTTPRedirectHandler(), + HttpErrorHandler()) + response = opener.open(request) + try: + if response.code > 399 and response.code < 500: + return {'status': response.code, 'value': response.read()} + body = response.read().replace('\x00', '').strip() + if body: + data = utils.load_json(body.strip()) + assert type(data) is dict, ( + 'Invalid server response body: %s' % body) + assert 'status' in data, ( + 'Invalid server response; no status: %s' % body) + # Some of the drivers incorrectly return a response + # with no 'value' field when they should return null. + if 'value' not in data: + data['value'] = None + return data + finally: + response.close() diff --git a/third_party/webdriver/py/selenium/remote/webdriver/utils.py b/third_party/webdriver/py/selenium/remote/webdriver/utils.py new file mode 100644 index 0000000..07d4aa6 --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/utils.py @@ -0,0 +1,112 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +import tempfile +import zipfile + +try: + import json +except ImportError: # < 2.6 + import simplejson as json + +if not hasattr(json, 'dumps'): + import simplejson as json + +from selenium.common.exceptions import NoSuchElementException + + +def format_json(json_struct): + return json.dumps(json_struct, indent=4) + +def dump_json(json_struct): + return json.dumps(json_struct) + +def load_json(s): + return json.loads(s) + +def handle_find_element_exception(e): + if ("Unable to find" in e.response["value"]["message"] or + "Unable to locate" in e.response["value"]["message"]): + raise NoSuchElementException("Unable to locate element:") + else: + raise e + +def return_value_if_exists(resp): + if resp and "value" in resp: + return resp["value"] + +def get_root_parent(elem): + parent = elem.parent + while True: + try: + parent.parent + parent = parent.parent + except AttributeError: + return parent + +def unzip_to_temp_dir(zip_file_name): + """Unzip zipfile to a temporary directory. + + The directory of the unzipped files is returned if success, + otherwise None is returned. """ + if not zip_file_name or not os.path.exists(zip_file_name): + return None + + zf = zipfile.ZipFile(zip_file_name) + + if zf.testzip() is not None: + return None + + # Unzip the files into a temporary directory + logging.info("Extracting zipped file: %s" % zip_file_name) + tempdir = tempfile.mkdtemp() + + try: + # Create directories that don't exist + for zip_name in zf.namelist(): + # We have no knowledge on the os where the zipped file was + # created, so we restrict to zip files with paths without + # charactor "\" and "/". + name = (zip_name.replace("\\", os.path.sep). + replace("/", os.path.sep)) + dest = os.path.join(tempdir, name) + if (name.endswith(os.path.sep) and not os.path.exists(dest)): + os.mkdir(dest) + logging.debug("Directory %s created." % dest) + + # Copy files + for zip_name in zf.namelist(): + # We have no knowledge on the os where the zipped file was + # created, so we restrict to zip files with paths without + # charactor "\" and "/". + name = (zip_name.replace("\\", os.path.sep). + replace("/", os.path.sep)) + dest = os.path.join(tempdir, name) + if not (name.endswith(os.path.sep)): + logging.debug("Copying file %s......" % dest) + outfile = open(dest, 'wb') + outfile.write(zf.read(zip_name)) + outfile.close() + logging.debug("File %s copied." % dest) + + logging.info("Unzipped file can be found at %s" % tempdir) + return tempdir + + except IOError, err: + logging.error( + "Error in extracting webdriver.xpi: %s" % err) + return None diff --git a/third_party/webdriver/py/selenium/remote/webdriver/webelement.py b/third_party/webdriver/py/selenium/remote/webdriver/webelement.py new file mode 100644 index 0000000..e924a4c --- /dev/null +++ b/third_party/webdriver/py/selenium/remote/webdriver/webelement.py @@ -0,0 +1,162 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""WebElement implementation.""" +from command import Command + +from selenium.common.exceptions import NoSuchAttributeException + +class WebElement(object): + """Represents an HTML element. + + Generally, all interesting operations to do with interacting with a page + will be performed through this interface.""" + def __init__(self, parent, id_): + self._parent = parent + self._id = id_ + + def get_text(self): + """Gets the text of the element.""" + return self._execute(Command.GET_ELEMENT_TEXT)['value'] + + def click(self): + """Clicks the element.""" + self._execute(Command.CLICK_ELEMENT) + + def submit(self): + """Submits a form.""" + self._execute(Command.SUBMIT_ELEMENT) + + def get_value(self): + """Gets the value of the element's value attribute.""" + return self._execute(Command.GET_ELEMENT_VALUE)['value'] + + def clear(self): + """Clears the text if it's a text entry element.""" + self._execute(Command.CLEAR_ELEMENT) + + def get_attribute(self, name): + """Gets the attribute value.""" + try: + resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name':name}) + return str(resp['value']) + # FIXME: This is a hack around selenium server bad response, remove this + # code when it's fixed + except AssertionError, e: + raise NoSuchAttributeException(name) + + def toggle(self): + """Toggles the element state.""" + self._execute(Command.TOGGLE_ELEMENT) + + def is_selected(self): + """Whether the element is selected.""" + return self._execute(Command.IS_ELEMENT_SELECTED)['value'] + + def set_selected(self): + """Selects an elmeent.""" + self._execute(Command.SET_ELEMENT_SELECTED) + + def is_enabled(self): + """Whether the element is enabled.""" + return self._execute(Command.IS_ELEMENT_ENABLED)['value'] + + def is_displayed(self): + """Whether the element would be visible to a user""" + return self._execute(Command.IS_ELEMENT_DISPLAYED)['value'] + + def find_element_by_id(self, id_): + """Finds element by id.""" + return self._get_elem_by("id", id_) + + def find_elements_by_id(self, id_): + return self._get_elems_by("id", id_) + + def find_element_by_name(self, name): + """Find element by name.""" + return self._get_elem_by("name", name) + + def find_elements_by_name(self, name): + return self._get_elems_by("name", name) + + def find_element_by_link_text(self, link_text): + """Finds element by link text.""" + return self._get_elem_by("link text", link_text) + + def find_elements_by_link_text(self, link_text): + return self._get_elems_by("link text", link_text) + + def find_element_by_partial_link_text(self, link_text): + return self._get_elem_by("partial link text", link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self._get_elems_by("partial link text", link_text) + + def find_element_by_tag_name(self, name): + return self._get_elem_by("tag name", name) + + def find_elements_by_tag_name(self, name): + return self._get_elems_by("tag name", name) + + def find_element_by_xpath(self, xpath): + """Finds element by xpath.""" + return self._get_elem_by("xpath", xpath) + + def find_elements_by_xpath(self, xpath): + """Finds elements within the elements by xpath.""" + return self._get_elems_by("xpath", xpath) + + def find_element_by_class_name(self, name): + """Finds an element by their class name.""" + return self._get_elem_by("class name", name) + + def find_elements_by_class_name(self, name): + """Finds elements by their class name.""" + return self._get_elems_by("class name", name) + + def send_keys(self, *value): + """Simulates typing into the element.""" + self._execute(Command.SEND_KEYS_TO_ELEMENT, {'value':value}) + + @property + def parent(self): + return self._parent + + @property + def id(self): + return self._id + + def _execute(self, command, params=None): + """Executes a command against the underlying HTML element. + + Args: + command: The name of the command to execute as a string. + params: A dictionary of named parameters to send with the command. + + Returns: + The command's JSON response loaded into a dictionary object. + """ + if not params: + params = {} + params['id'] = self._id + return self._parent._execute(command, params) + + def _get_elem_by(self, by, value): + return self._execute(Command.FIND_CHILD_ELEMENT, + {"using": by, "value": value})['value'] + + def _get_elems_by(self, by, value): + return self._execute(Command.FIND_CHILD_ELEMENTS, + {"using": by, "value": value})['value'] |