From 3f35b0a29919b777ab22ad002da355c0e2ef2b0c Mon Sep 17 00:00:00 2001 From: lixiang Date: Sat, 4 Nov 2023 12:10:58 +0800 Subject: [PATCH] chromium 99.0.4844.88 3.2release init Change-Id: Ib83b75f7b6089cc21a1ae4916574d140a9a8bdea Signed-off-by: lixiang --- CHANGES | 172 ++ README | 64 + __init__.py | 14 + build.desc | 71 + docs/Makefile | 131 + docs/source/api.rst | 111 + .../common/selenium.common.exceptions.rst | 4 + docs/source/conf.py | 268 +++ docs/source/index.rst | 64 + docs/source/selenium/selenium.selenium.rst | 4 + ...elenium.webdriver.common.action_chains.rst | 4 + .../selenium.webdriver.common.alert.rst | 4 + .../selenium.webdriver.common.by.rst | 4 + ....webdriver.common.desired_capabilities.rst | 4 + .../selenium.webdriver.common.keys.rst | 4 + .../selenium.webdriver.common.utils.rst | 4 + .../selenium.webdriver.chrome.service.rst | 4 + .../selenium.webdriver.chrome.webdriver.rst | 4 + ...webdriver.firefox.extension_connection.rst | 4 + ...enium.webdriver.firefox.firefox_binary.rst | 4 + ...nium.webdriver.firefox.firefox_profile.rst | 4 + .../selenium.webdriver.firefox.webdriver.rst | 4 + .../selenium.webdriver.ie.webdriver.rst | 4 + .../selenium.webdriver.phantomjs.service.rst | 4 + ...selenium.webdriver.phantomjs.webdriver.rst | 4 + .../selenium.webdriver.remote.command.rst | 4 + ...selenium.webdriver.remote.errorhandler.rst | 4 + ...ium.webdriver.remote.remote_connection.rst | 4 + .../selenium.webdriver.remote.utils.rst | 4 + .../selenium.webdriver.remote.webdriver.rst | 4 + .../selenium.webdriver.remote.webelement.rst | 4 + ...driver.support.abstract_event_listener.rst | 4 + .../selenium.webdriver.support.color.rst | 4 + ...bdriver.support.event_firing_webdriver.rst | 4 + ....webdriver.support.expected_conditions.rst | 4 + .../selenium.webdriver.support.select.rst | 4 + .../selenium.webdriver.support.wait.rst | 4 + selenium/__init__.py | 19 + selenium/common/__init__.py | 16 + selenium/common/exceptions.py | 122 + selenium/selenium.py | 2111 +++++++++++++++++ selenium/webdriver/__init__.py | 31 + selenium/webdriver/chrome/__init__.py | 14 + selenium/webdriver/chrome/options.py | 132 ++ selenium/webdriver/chrome/service.py | 104 + selenium/webdriver/chrome/webdriver.py | 82 + selenium/webdriver/common/__init__.py | 14 + selenium/webdriver/common/action_chains.py | 253 ++ selenium/webdriver/common/alert.py | 39 + selenium/webdriver/common/by.py | 25 + .../webdriver/common/desired_capabilities.py | 93 + selenium/webdriver/common/html5/__init__.py | 0 .../common/html5/application_cache.py | 17 + selenium/webdriver/common/keys.py | 84 + selenium/webdriver/common/proxy.py | 136 ++ selenium/webdriver/common/touch_actions.py | 165 ++ selenium/webdriver/common/utils.py | 46 + selenium/webdriver/firefox/__init__.py | 14 + .../webdriver/firefox/extension_connection.py | 79 + selenium/webdriver/firefox/firefox_binary.py | 175 ++ selenium/webdriver/firefox/firefox_profile.py | 376 +++ selenium/webdriver/firefox/webdriver.py | 84 + selenium/webdriver/ie/__init__.py | 16 + selenium/webdriver/ie/service.py | 106 + selenium/webdriver/ie/webdriver.py | 56 + selenium/webdriver/opera/__init__.py | 14 + selenium/webdriver/opera/service.py | 80 + selenium/webdriver/opera/webdriver.py | 68 + selenium/webdriver/phantomjs/__init__.py | 13 + selenium/webdriver/phantomjs/service.py | 93 + selenium/webdriver/phantomjs/webdriver.py | 74 + selenium/webdriver/remote/__init__.py | 14 + selenium/webdriver/remote/command.py | 144 ++ selenium/webdriver/remote/errorhandler.py | 152 ++ .../webdriver/remote/remote_connection.py | 402 ++++ selenium/webdriver/remote/utils.py | 112 + selenium/webdriver/remote/webdriver.py | 821 +++++++ selenium/webdriver/remote/webelement.py | 284 +++ selenium/webdriver/support/__init__.py | 16 + .../support/abstract_event_listener.py | 59 + selenium/webdriver/support/color.py | 303 +++ .../support/event_firing_webdriver.py | 326 +++ selenium/webdriver/support/events.py | 18 + .../webdriver/support/expected_conditions.py | 283 +++ selenium/webdriver/support/select.py | 223 ++ selenium/webdriver/support/ui.py | 19 + selenium/webdriver/support/wait.py | 87 + test/__init__.py | 14 + test/selenium/__init__.py | 0 test/selenium/common/__init__.py | 0 test/selenium/common/utils.py | 47 + test/selenium/selenium_test_suite.py | 35 + test/selenium/selenium_test_suite_headless.py | 31 + test/selenium/test_default_server.py | 52 + test/selenium/test_google.py | 38 + test/selenium/test_i18n.py | 49 + test/selenium/test_prompts.py | 46 + test/selenium/webdriver/__init__.py | 0 .../webdriver/browser_specific_template.py | 44 + test/selenium/webdriver/common/__init__.py | 15 + .../selenium/webdriver/common/alerts_tests.py | 209 ++ .../webdriver/common/api_example_tests.py | 264 +++ .../webdriver/common/appcache_tests.py | 40 + .../common/children_finding_tests.py | 169 ++ test/selenium/webdriver/common/clear_tests.py | 91 + .../webdriver/common/click_scrolling_tests.py | 76 + test/selenium/webdriver/common/click_tests.py | 25 + .../webdriver/common/connection_tests.py | 32 + .../selenium/webdriver/common/cookie_tests.py | 95 + .../common/correct_event_firing_tests.py | 140 ++ .../common/driver_element_finding_tests.py | 137 ++ .../common/element_attribute_tests.py | 265 +++ .../common/element_equality_tests.py | 48 + test/selenium/webdriver/common/example2.py | 39 + .../executing_async_javascript_tests.py | 226 ++ .../common/executing_javascript_tests.py | 232 ++ .../webdriver/common/form_handling_tests.py | 250 ++ .../webdriver/common/frame_switching_tests.py | 233 ++ .../webdriver/common/google_one_box.py | 42 + .../webdriver/common/implicit_waits_tests.py | 98 + .../webdriver/common/interactions_tests.py | 240 ++ .../webdriver/common/opacity_tests.py | 61 + .../common/page_load_timeout_tests.py | 58 + test/selenium/webdriver/common/page_loader.py | 30 + .../webdriver/common/page_loading_tests.py | 131 + test/selenium/webdriver/common/proxy_tests.py | 38 + .../common/rendered_webelement_tests.py | 64 + .../selenium/webdriver/common/results_page.py | 35 + .../webdriver/common/select_class_tests.py | 335 +++ .../common/select_element_handling_tests.py | 59 + .../webdriver/common/stale_reference_tests.py | 63 + .../webdriver/common/text_handling_tests.py | 203 ++ .../selenium/webdriver/common/typing_tests.py | 293 +++ test/selenium/webdriver/common/utils.py | 63 + .../webdriver/common/visibility_tests.py | 136 ++ .../webdriver/common/webdriverwait_tests.py | 324 +++ test/selenium/webdriver/common/webserver.py | 131 + .../common/window_switching_tests.py | 200 ++ .../selenium/webdriver/common/window_tests.py | 49 + test/selenium/webdriver/firefox/__init__.py | 29 + .../webdriver/firefox/ff_launcher_tests.py | 45 + .../webdriver/firefox/ff_profile_tests.py | 153 ++ .../firefox/ff_select_support_class_tests.py | 36 + test/selenium/webdriver/ie/__init__.py | 0 test/selenium/webdriver/support/__init__.py | 16 + .../selenium/webdriver/support/color_tests.py | 102 + .../support/event_firing_webdriver_tests.py | 180 ++ 147 files changed, 15181 insertions(+) create mode 100644 CHANGES create mode 100644 README create mode 100644 __init__.py create mode 100644 build.desc create mode 100644 docs/Makefile create mode 100644 docs/source/api.rst create mode 100644 docs/source/common/selenium.common.exceptions.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/selenium/selenium.selenium.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.action_chains.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.alert.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.by.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.desired_capabilities.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.keys.rst create mode 100644 docs/source/webdriver/selenium.webdriver.common.utils.rst create mode 100644 docs/source/webdriver_chrome/selenium.webdriver.chrome.service.rst create mode 100644 docs/source/webdriver_chrome/selenium.webdriver.chrome.webdriver.rst create mode 100644 docs/source/webdriver_firefox/selenium.webdriver.firefox.extension_connection.rst create mode 100644 docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_binary.rst create mode 100644 docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_profile.rst create mode 100644 docs/source/webdriver_firefox/selenium.webdriver.firefox.webdriver.rst create mode 100644 docs/source/webdriver_ie/selenium.webdriver.ie.webdriver.rst create mode 100644 docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.service.rst create mode 100644 docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.webdriver.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.command.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.errorhandler.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.remote_connection.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.utils.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.webdriver.rst create mode 100644 docs/source/webdriver_remote/selenium.webdriver.remote.webelement.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.abstract_event_listener.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.color.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.event_firing_webdriver.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.expected_conditions.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.select.rst create mode 100644 docs/source/webdriver_support/selenium.webdriver.support.wait.rst create mode 100644 selenium/__init__.py create mode 100644 selenium/common/__init__.py create mode 100644 selenium/common/exceptions.py create mode 100644 selenium/selenium.py create mode 100644 selenium/webdriver/__init__.py create mode 100644 selenium/webdriver/chrome/__init__.py create mode 100644 selenium/webdriver/chrome/options.py create mode 100644 selenium/webdriver/chrome/service.py create mode 100644 selenium/webdriver/chrome/webdriver.py create mode 100644 selenium/webdriver/common/__init__.py create mode 100644 selenium/webdriver/common/action_chains.py create mode 100644 selenium/webdriver/common/alert.py create mode 100644 selenium/webdriver/common/by.py create mode 100644 selenium/webdriver/common/desired_capabilities.py create mode 100644 selenium/webdriver/common/html5/__init__.py create mode 100644 selenium/webdriver/common/html5/application_cache.py create mode 100644 selenium/webdriver/common/keys.py create mode 100644 selenium/webdriver/common/proxy.py create mode 100644 selenium/webdriver/common/touch_actions.py create mode 100644 selenium/webdriver/common/utils.py create mode 100644 selenium/webdriver/firefox/__init__.py create mode 100644 selenium/webdriver/firefox/extension_connection.py create mode 100644 selenium/webdriver/firefox/firefox_binary.py create mode 100644 selenium/webdriver/firefox/firefox_profile.py create mode 100644 selenium/webdriver/firefox/webdriver.py create mode 100644 selenium/webdriver/ie/__init__.py create mode 100644 selenium/webdriver/ie/service.py create mode 100644 selenium/webdriver/ie/webdriver.py create mode 100644 selenium/webdriver/opera/__init__.py create mode 100644 selenium/webdriver/opera/service.py create mode 100644 selenium/webdriver/opera/webdriver.py create mode 100644 selenium/webdriver/phantomjs/__init__.py create mode 100644 selenium/webdriver/phantomjs/service.py create mode 100644 selenium/webdriver/phantomjs/webdriver.py create mode 100644 selenium/webdriver/remote/__init__.py create mode 100644 selenium/webdriver/remote/command.py create mode 100644 selenium/webdriver/remote/errorhandler.py create mode 100644 selenium/webdriver/remote/remote_connection.py create mode 100644 selenium/webdriver/remote/utils.py create mode 100644 selenium/webdriver/remote/webdriver.py create mode 100644 selenium/webdriver/remote/webelement.py create mode 100644 selenium/webdriver/support/__init__.py create mode 100644 selenium/webdriver/support/abstract_event_listener.py create mode 100644 selenium/webdriver/support/color.py create mode 100644 selenium/webdriver/support/event_firing_webdriver.py create mode 100644 selenium/webdriver/support/events.py create mode 100644 selenium/webdriver/support/expected_conditions.py create mode 100644 selenium/webdriver/support/select.py create mode 100644 selenium/webdriver/support/ui.py create mode 100644 selenium/webdriver/support/wait.py create mode 100644 test/__init__.py create mode 100644 test/selenium/__init__.py create mode 100644 test/selenium/common/__init__.py create mode 100644 test/selenium/common/utils.py create mode 100644 test/selenium/selenium_test_suite.py create mode 100644 test/selenium/selenium_test_suite_headless.py create mode 100644 test/selenium/test_default_server.py create mode 100644 test/selenium/test_google.py create mode 100644 test/selenium/test_i18n.py create mode 100644 test/selenium/test_prompts.py create mode 100644 test/selenium/webdriver/__init__.py create mode 100644 test/selenium/webdriver/browser_specific_template.py create mode 100644 test/selenium/webdriver/common/__init__.py create mode 100644 test/selenium/webdriver/common/alerts_tests.py create mode 100644 test/selenium/webdriver/common/api_example_tests.py create mode 100644 test/selenium/webdriver/common/appcache_tests.py create mode 100644 test/selenium/webdriver/common/children_finding_tests.py create mode 100644 test/selenium/webdriver/common/clear_tests.py create mode 100644 test/selenium/webdriver/common/click_scrolling_tests.py create mode 100644 test/selenium/webdriver/common/click_tests.py create mode 100644 test/selenium/webdriver/common/connection_tests.py create mode 100644 test/selenium/webdriver/common/cookie_tests.py create mode 100644 test/selenium/webdriver/common/correct_event_firing_tests.py create mode 100644 test/selenium/webdriver/common/driver_element_finding_tests.py create mode 100644 test/selenium/webdriver/common/element_attribute_tests.py create mode 100644 test/selenium/webdriver/common/element_equality_tests.py create mode 100644 test/selenium/webdriver/common/example2.py create mode 100644 test/selenium/webdriver/common/executing_async_javascript_tests.py create mode 100644 test/selenium/webdriver/common/executing_javascript_tests.py create mode 100644 test/selenium/webdriver/common/form_handling_tests.py create mode 100644 test/selenium/webdriver/common/frame_switching_tests.py create mode 100644 test/selenium/webdriver/common/google_one_box.py create mode 100644 test/selenium/webdriver/common/implicit_waits_tests.py create mode 100644 test/selenium/webdriver/common/interactions_tests.py create mode 100644 test/selenium/webdriver/common/opacity_tests.py create mode 100644 test/selenium/webdriver/common/page_load_timeout_tests.py create mode 100644 test/selenium/webdriver/common/page_loader.py create mode 100644 test/selenium/webdriver/common/page_loading_tests.py create mode 100644 test/selenium/webdriver/common/proxy_tests.py create mode 100644 test/selenium/webdriver/common/rendered_webelement_tests.py create mode 100644 test/selenium/webdriver/common/results_page.py create mode 100644 test/selenium/webdriver/common/select_class_tests.py create mode 100644 test/selenium/webdriver/common/select_element_handling_tests.py create mode 100644 test/selenium/webdriver/common/stale_reference_tests.py create mode 100644 test/selenium/webdriver/common/text_handling_tests.py create mode 100644 test/selenium/webdriver/common/typing_tests.py create mode 100644 test/selenium/webdriver/common/utils.py create mode 100644 test/selenium/webdriver/common/visibility_tests.py create mode 100644 test/selenium/webdriver/common/webdriverwait_tests.py create mode 100644 test/selenium/webdriver/common/webserver.py create mode 100644 test/selenium/webdriver/common/window_switching_tests.py create mode 100644 test/selenium/webdriver/common/window_tests.py create mode 100644 test/selenium/webdriver/firefox/__init__.py create mode 100644 test/selenium/webdriver/firefox/ff_launcher_tests.py create mode 100644 test/selenium/webdriver/firefox/ff_profile_tests.py create mode 100644 test/selenium/webdriver/firefox/ff_select_support_class_tests.py create mode 100644 test/selenium/webdriver/ie/__init__.py create mode 100644 test/selenium/webdriver/support/__init__.py create mode 100644 test/selenium/webdriver/support/color_tests.py create mode 100644 test/selenium/webdriver/support/event_firing_webdriver_tests.py diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..0effbbc --- /dev/null +++ b/CHANGES @@ -0,0 +1,172 @@ +Selenium 2.28 +* "null" can now be passed to executeScript +* Add transparent and extended colour keywords to color support module. Fixes issue 4866 + +Selenium 2.27 +* Added support for phantomjs / ghostdriver +* Fix python client, avoid duplicate chrome option items after reusing options class. Fixes Issue 4744. +* adding colour support to Python. fixes issue 4623 +* Adding log_path/service_log_path as named kwargs for chrome + +Selenium 2.26 +* Added location_when_scrolled_into_view - Bug 4357 +* Added new expected_conditions support module to be used with WebDriverWait + +Selenium 2.25 +* Jython 2.7 Support - Bug 3988 +* EventFiringWebDriver added to Support module - Bug 2267 +* Added IEDriverServer logging that can be accessed via desired capabilities +* Fixed by data being passed into find_elements - bug 3735 +* Removed deprecated ChromeDriver items around desiredcapabilites in favour of chrome options +* Added default values for a number of action_chains calls + +Selenium 2.24 +* Removing the ctypes approach of invoking IEDriver, you will need to download the IEDriverServer from + https://code.google.com/p/selenium/downloads/list + +Selenium 2.23 +* Support for FF13 native events + +Selenium 2.22 +* Moving IEDriver to be able to use IEDriverServer + +Selenium 2.21.3 +* Fix for File Upload to remote servers +* Better handling of typing in input=file. Bug 3831, 3736 +* Better handling of unicode URLS Bug 3740 + +Selenium 2.21.2 +* Fix typing to file input when not using Selenium Server. Bug 3736 + +Selenium 2.21.1 +* focusmanager.testmode messes with native events, removing it. + +Selenium 2.21 +* Local File upload capabilities for non-remote browser +* Adding maximize_window api call +* Updating default firefox profile to set focusmanager.testmode to true + see https://bugzilla.mozilla.org/show_bug.cgi?id=704583 +* bugs fixed: 3506, 3528, 3607 + +Selenium 2.20 +* disable native events for FF on Mac by default +* fix webdriverwait to execute at least once when using 0 timeout +* Fixed Issue 3438 + +Selenium 2.19 +* WebDriverBackedSelenium is now avalaible to all languages +* Addon installation fixes + +Selenium 2.18 +* Proxy capabilities passing + +Selenium 2.17 +* OperaDriver can now be invoked by webdriver.Opera() +* Support has been added for ChomeOptions. This deprecates support passing in DesiredCapabilities +* Proxy class to tell the browser a proxy is in use. Currently only for Firefox + +Selenium 2.16 +* bug fixes + +Selenium 2.15 +* bug fixes + +Selenium 2.14 +* Fix for LD_PRELOAD being polluted by WebDriver +* Added Orientation API +* A fix for Error Handling + +Selenium 2.13 +* Fixed switch_to_window so that it didnt crash Firefox Bug 2633 +* Fixed Screenshot handling to work in all browsers. Bug 2829 +* Force Firefox to the Foreground + +Selenium 2.12 +* Added Select as a support pacakge +* Added Beta window size / position api's +* Bug Fixes + +Selenium 2.11.0 2.11.1 +* no changes just packaging + +Selenum 2.10 +* "Choose which apps" dialog has been disabled +* Bug Fixes + +Selenium 2.9 +* Bug Fixes +* Documentation + +Selenium 2.8 +* Actions updates +* Bug Fixes + +Selenium 2.6 +* Documentation fixes + +Selenium 2.5 +* Fixed x64 IE Support +* Bug Fixes + +Selenium 2.4 +* Bug Fixes +* x64 IE Support +* Added WebDriverWait as a support package + +Selenium 2.3 +* Bug Fixes + +Selenium 2.2 +* Ability to get screenshots from Exceptions if they are given +* Access to Remote StackTrace on error + +Selenium 2.1 +* Bug Fixes + +Selenium 2 +* Removed toggle() and select() + +Selenium 2 RC 3 +* Added Opera to Desired Capabilities +* Removed deprecrated methods +* Deprecated toggle() and select() methods. This will be removed in the next release + +Selenium 2 Beta 4 +* Fix for using existing Firefox Profiles +* Alerts Support in IE +* Fix to dictionary returned from size +* Deprecated value property. Use the get_attribute("value") method +* Deprecated get_page_source method. Use page_source property +* Deprecated get_current_window_handle. Use current_window_handle property +* Deprecated get_window_handles. Use window_handles property +* Ability to install extensions into profiles +* Added Location to the WebElement +* ChromeDriver rewritten to use new built in mechanism +* Added Advanced User Interaction API. Only Available for HTMLUnit at the moment +* Profiles now delete their temp folders when driver.quit() is called + +Selenium 2 Beta 3 +* Accept Untrusted Certificates in Firefox +* Fixed Screenshots +* Added DesiredCapabilities to simplify choosing Drivers +* Fixed Firefox Profile creation +* Added Firefox 4 support +* DocStrings Improvements + +Selenium 2 Beta 2 + +* New bindings landed. Change webdriver namespace to "selenium.webdriver" +* Ability to move to default content +* Implicit Waits +* Change the API to use properties instead of get_x +* Changed the Element Finding to match other languages +* Added ability to execute asynchronous scripts from the driver +* Ability to get rendered element size +* Ability to get CSS Value on a webelement +* Corrected Element finding from the element +* Alert and Prompt handling +* Improved IEDriver +* Basic Authentication support for Selenium 2 +* Ability to have multiple Firefox instances + + diff --git a/README b/README new file mode 100644 index 0000000..20df6a2 --- /dev/null +++ b/README @@ -0,0 +1,64 @@ +============ +Introduction +============ + +:Author: David Burns + +Selenium Python Client Driver is a Python language binding for Selenium Remote +Control (version 1.0 and 2.0). + +Currently the remote protocol, Firefox and Chrome for Selenium 2.0 are +supported, as well as the Selenium 1.0 bindings. As work will progresses we'll +add more "native" drivers. + +See here_ for more information. + +.. _here: http://code.google.com/p/selenium/ + +Installing +========== + +Python Client +------------- +:: + + pip install -U selenium + +Java Server +----------- + +Download the server from http://selenium.googlecode.com/files/selenium-server-standalone-2.28.0.jar +:: + + java -jar selenium-server-standalone-2.28.0.jar + +Example +======= +:: + + from selenium import webdriver + from selenium.common.exceptions import NoSuchElementException + from selenium.webdriver.common.keys import Keys + import time + + browser = webdriver.Firefox() # Get local session of firefox + browser.get("http://www.yahoo.com") # Load page + assert "Yahoo!" in browser.title + elem = browser.find_element_by_name("p") # Find the query box + elem.send_keys("seleniumhq" + Keys.RETURN) + time.sleep(0.2) # Let the page load, will be added to the API + try: + browser.find_element_by_xpath("//a[contains(@href,'http://seleniumhq.org')]") + except NoSuchElementException: + assert 0, "can't find seleniumhq" + browser.close() + +Documentation +============= + +`Auto Generated API `_ + +Use The Source Luke! +==================== + +http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webdriver.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a8bb36b --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +# 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. diff --git a/build.desc b/build.desc new file mode 100644 index 0000000..4475e1f --- /dev/null +++ b/build.desc @@ -0,0 +1,71 @@ +# py_test targets implicitly get the following extra targets added: +# for each browser in browsers: +# :name_B to gather the sources of the tests for browser B +# :name_B:run to run the tests for browser B +# Also, if only one browser, B, is listed in browsers: +# :name:run is created as a synonym for :name_B:run +# +# Currently, pulling in other tests through deps is limited to deps declared in the same build.desc file + +py_test( + name = "firefox_test", + ff_specific_tests = [ "test/selenium/webdriver/firefox/*_tests.py" ], + deps = [ ":test_ff" ], + resources = [ + { "//javascript/firefox-driver:webdriver" : "selenium/webdriver/firefox/" } + ], + browsers = [ "ff" ]) + +py_test( + name = "phantomjs_test", + gd_specific_tests = [ "test/selenium/webdriver/phantomjs/*_tests.py" ], + deps = [ ":test_phantomjs" ], + browsers = [ "phantomjs" ]) + + +py_test( + name = "chrome_test", + chrome_specific_tests = [ "test/selenium/webdriver/chrome/*_tests.py" ], + deps = [ ":test_chrome" ], + browsers = [ "chrome" ]) + +py_test( + name = "opera_test", + opera_specific_tests = [ "test/selenium/webdriver/opera/*_tests.py" ], + deps = [ ":test_opera" ], + browsers = [ "opera" ]) + +py_test( + name = "ie_test", + deps = [ ":test_ie" ], + browsers = [ "ie" ]) + +py_test( + name = "remote_firefox_test", + remote_firefox_specific_tests = [ "test/selenium/webdriver/remote/*_tests.py" ], + deps = [ ":test_remote_firefox" ], + browsers = [ "remote_firefox" ]) + +py_env( + name = "test_env", + packages = [ + "third_party/py/simplejson-2.2.1.tar.gz", + "third_party/py/py-1.4.5.zip", + "third_party/py/pytest-2.0.3.zip", + ], + dest = "build/python") + +py_test( + name = "test", + common_tests = [ + "test/selenium/webdriver/common/*_tests.py", + "test/selenium/webdriver/support/*_tests.py" + ], + browsers = [ + "chrome", + "ff", + "ie", + "phantomjs", + "remote_firefox", + "opera" + ]) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..2d48370 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,131 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +HTML_DESTINATION = ../../docs/api/py + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: clean + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(HTML_DESTINATION) + @echo + @echo "Build finished. The HTML pages are in $(HTML_DESTINATION)." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Selenium.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Selenium.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Selenium" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Selenium" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..daa3559 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,111 @@ +====================== +Selenium Documentation +====================== + +Selenium +-------- + +.. currentmodule:: selenium +.. autosummary:: + :toctree: selenium + + selenium + +Common +------ + +.. currentmodule:: selenium.common +.. autosummary:: + :toctree: common + + selenium.common.exceptions + +Webdriver.common +---------------- + +.. currentmodule:: selenium.webdriver.common +.. autosummary:: + :toctree: webdriver + + selenium.webdriver.common.action_chains + selenium.webdriver.common.alert + selenium.webdriver.common.by + selenium.webdriver.common.desired_capabilities + selenium.webdriver.common.keys + selenium.webdriver.common.utils + +Webdriver.support +----------------- + +.. currentmodule:: selenium.webdriver.support +.. autosummary:: + :toctree: webdriver_support + + selenium.webdriver.support.abstract_event_listener + selenium.webdriver.support.color + selenium.webdriver.support.event_firing_webdriver + selenium.webdriver.support.expected_conditions + selenium.webdriver.support.select + selenium.webdriver.support.wait + +Webdriver.chrome +---------------- + +.. currentmodule:: selenium.webdriver.chrome +.. autosummary:: + :toctree: webdriver_chrome + + selenium.webdriver.chrome.service + selenium.webdriver.chrome.webdriver + +Webdriver.firefox +----------------- + +.. currentmodule:: selenium.webdriver.firefox +.. autosummary:: + :toctree: webdriver_firefox + + selenium.webdriver.firefox.extension_connection + selenium.webdriver.firefox.firefox_binary + selenium.webdriver.firefox.firefox_profile + selenium.webdriver.firefox.webdriver + +Webdriver.ie +------------ + +.. currentmodule:: selenium.webdriver.ie +.. autosummary:: + :toctree: webdriver_ie + + selenium.webdriver.ie.webdriver + +Webdriver.phantomjs +------------------- + +.. currentmodule:: selenium.webdriver.phantomjs +.. autosummary:: + :toctree: webdriver_phantomjs + + selenium.webdriver.phantomjs.service + selenium.webdriver.phantomjs.webdriver + +Webdriver.remote +---------------- + +.. currentmodule:: selenium.webdriver.remote +.. autosummary:: + :toctree: webdriver_remote + + selenium.webdriver.remote.command + selenium.webdriver.remote.errorhandler + selenium.webdriver.remote.remote_connection + selenium.webdriver.remote.utils + selenium.webdriver.remote.webdriver + selenium.webdriver.remote.webelement + +Indices and tables + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/source/common/selenium.common.exceptions.rst b/docs/source/common/selenium.common.exceptions.rst new file mode 100644 index 0000000..356d256 --- /dev/null +++ b/docs/source/common/selenium.common.exceptions.rst @@ -0,0 +1,4 @@ +selenium.common.exceptions +========================== + +.. automodule:: selenium.common.exceptions diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..1e52150 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +# +# Selenium documentation build configuration file, created by +# sphinx-quickstart on Thu May 12 10:57:00 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, os.path + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.join(os.getcwd(), "..", "..")) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Selenium' +copyright = u'2011, plightbo, simon.m.stewart, hbchai, jrhuggins, et al.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.0' +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Seleniumdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Selenium.tex', u'Selenium Documentation', + u'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'selenium', u'Selenium Documentation', + [u'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'], 1) +] + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Selenium' +epub_author = u'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.' +epub_publisher = u'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.' +epub_copyright = u'2011, plightbo, simon.m.stewart, hbchai, jrhuggins, et al.' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} + +# 'members' includes anything that has a docstring, 'undoc-members' includes +# functions without docstrings. +autodoc_default_flags = ['members', 'undoc-members'] + +# Include __init__ comments +autoclass_content = "both" diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..2aa55da --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,64 @@ +============ +Introduction +============ +:Author: David Burns + +Selenium Python Client Driver is a Python language binding for Selenium Remote +Control (version 1.0 and 2.0). + +Currently the remote protocol, Firefox and Chrome for Selenium 2.0 are +supported, as well as the Selenium 1.0 bindings. As work will progresses we'll +add more "native" drivers. + +See here_ for more information. + +.. _here: http://code.google.com/p/selenium/ + +Installing +========== + +Python Client +------------- +:: + + pip install -U selenium + +Java Server +----------- + +Download the server from http://selenium.googlecode.com/files/selenium-server-standalone-2.28.0.jar +:: + + java -jar selenium-server-standalone-2.28.0.jar + +Example +======= +:: + + from selenium import webdriver + from selenium.common.exceptions import NoSuchElementException + from selenium.webdriver.common.keys import Keys + import time + + browser = webdriver.Firefox() # Get local session of firefox + browser.get("http://www.yahoo.com") # Load page + assert "Yahoo!" in browser.title + elem = browser.find_element_by_name("p") # Find the query box + elem.send_keys("seleniumhq" + Keys.RETURN) + time.sleep(0.2) # Let the page load, will be added to the API + try: + browser.find_element_by_xpath("//a[contains(@href,'http://seleniumhq.org')]") + except NoSuchElementException: + assert 0, "can't find seleniumhq" + browser.close() + +Documentation +============= + +.. toctree:: + + Auto Generated API + +Use The Source Luke! +==================== +http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webdriver.py diff --git a/docs/source/selenium/selenium.selenium.rst b/docs/source/selenium/selenium.selenium.rst new file mode 100644 index 0000000..25e8f9a --- /dev/null +++ b/docs/source/selenium/selenium.selenium.rst @@ -0,0 +1,4 @@ +selenium.selenium +================= + +.. automodule:: selenium.selenium diff --git a/docs/source/webdriver/selenium.webdriver.common.action_chains.rst b/docs/source/webdriver/selenium.webdriver.common.action_chains.rst new file mode 100644 index 0000000..07fff7b --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.action_chains.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.action_chains +======================================= + +.. automodule:: selenium.webdriver.common.action_chains diff --git a/docs/source/webdriver/selenium.webdriver.common.alert.rst b/docs/source/webdriver/selenium.webdriver.common.alert.rst new file mode 100644 index 0000000..ac88438 --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.alert.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.alert +=============================== + +.. automodule:: selenium.webdriver.common.alert diff --git a/docs/source/webdriver/selenium.webdriver.common.by.rst b/docs/source/webdriver/selenium.webdriver.common.by.rst new file mode 100644 index 0000000..cdf6941 --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.by.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.by +============================ + +.. automodule:: selenium.webdriver.common.by diff --git a/docs/source/webdriver/selenium.webdriver.common.desired_capabilities.rst b/docs/source/webdriver/selenium.webdriver.common.desired_capabilities.rst new file mode 100644 index 0000000..30fdf17 --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.desired_capabilities.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.desired_capabilities +============================================== + +.. automodule:: selenium.webdriver.common.desired_capabilities diff --git a/docs/source/webdriver/selenium.webdriver.common.keys.rst b/docs/source/webdriver/selenium.webdriver.common.keys.rst new file mode 100644 index 0000000..1704a17 --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.keys.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.keys +============================== + +.. automodule:: selenium.webdriver.common.keys diff --git a/docs/source/webdriver/selenium.webdriver.common.utils.rst b/docs/source/webdriver/selenium.webdriver.common.utils.rst new file mode 100644 index 0000000..f30250a --- /dev/null +++ b/docs/source/webdriver/selenium.webdriver.common.utils.rst @@ -0,0 +1,4 @@ +selenium.webdriver.common.utils +=============================== + +.. automodule:: selenium.webdriver.common.utils diff --git a/docs/source/webdriver_chrome/selenium.webdriver.chrome.service.rst b/docs/source/webdriver_chrome/selenium.webdriver.chrome.service.rst new file mode 100644 index 0000000..ffe8b20 --- /dev/null +++ b/docs/source/webdriver_chrome/selenium.webdriver.chrome.service.rst @@ -0,0 +1,4 @@ +selenium.webdriver.chrome.service +================================= + +.. automodule:: selenium.webdriver.chrome.service diff --git a/docs/source/webdriver_chrome/selenium.webdriver.chrome.webdriver.rst b/docs/source/webdriver_chrome/selenium.webdriver.chrome.webdriver.rst new file mode 100644 index 0000000..7f54253 --- /dev/null +++ b/docs/source/webdriver_chrome/selenium.webdriver.chrome.webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.chrome.webdriver +=================================== + +.. automodule:: selenium.webdriver.chrome.webdriver diff --git a/docs/source/webdriver_firefox/selenium.webdriver.firefox.extension_connection.rst b/docs/source/webdriver_firefox/selenium.webdriver.firefox.extension_connection.rst new file mode 100644 index 0000000..413bada --- /dev/null +++ b/docs/source/webdriver_firefox/selenium.webdriver.firefox.extension_connection.rst @@ -0,0 +1,4 @@ +selenium.webdriver.firefox.extension_connection +=============================================== + +.. automodule:: selenium.webdriver.firefox.extension_connection diff --git a/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_binary.rst b/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_binary.rst new file mode 100644 index 0000000..e0b04b1 --- /dev/null +++ b/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_binary.rst @@ -0,0 +1,4 @@ +selenium.webdriver.firefox.firefox_binary +========================================= + +.. automodule:: selenium.webdriver.firefox.firefox_binary diff --git a/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_profile.rst b/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_profile.rst new file mode 100644 index 0000000..78ba949 --- /dev/null +++ b/docs/source/webdriver_firefox/selenium.webdriver.firefox.firefox_profile.rst @@ -0,0 +1,4 @@ +selenium.webdriver.firefox.firefox_profile +========================================== + +.. automodule:: selenium.webdriver.firefox.firefox_profile diff --git a/docs/source/webdriver_firefox/selenium.webdriver.firefox.webdriver.rst b/docs/source/webdriver_firefox/selenium.webdriver.firefox.webdriver.rst new file mode 100644 index 0000000..e9bfe53 --- /dev/null +++ b/docs/source/webdriver_firefox/selenium.webdriver.firefox.webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.firefox.webdriver +==================================== + +.. automodule:: selenium.webdriver.firefox.webdriver diff --git a/docs/source/webdriver_ie/selenium.webdriver.ie.webdriver.rst b/docs/source/webdriver_ie/selenium.webdriver.ie.webdriver.rst new file mode 100644 index 0000000..3623813 --- /dev/null +++ b/docs/source/webdriver_ie/selenium.webdriver.ie.webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.ie.webdriver +=============================== + +.. automodule:: selenium.webdriver.ie.webdriver diff --git a/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.service.rst b/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.service.rst new file mode 100644 index 0000000..8d6e5ff --- /dev/null +++ b/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.service.rst @@ -0,0 +1,4 @@ +selenium.webdriver.phantomjs.service +==================================== + +.. automodule:: selenium.webdriver.phantomjs.service diff --git a/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.webdriver.rst b/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.webdriver.rst new file mode 100644 index 0000000..5b3ab48 --- /dev/null +++ b/docs/source/webdriver_phantomjs/selenium.webdriver.phantomjs.webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.phantomjs.webdriver +====================================== + +.. automodule:: selenium.webdriver.phantomjs.webdriver diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.command.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.command.rst new file mode 100644 index 0000000..ff977b7 --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.command.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.command +================================= + +.. automodule:: selenium.webdriver.remote.command diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.errorhandler.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.errorhandler.rst new file mode 100644 index 0000000..5002887 --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.errorhandler.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.errorhandler +====================================== + +.. automodule:: selenium.webdriver.remote.errorhandler diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.remote_connection.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.remote_connection.rst new file mode 100644 index 0000000..88d1389 --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.remote_connection.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.remote_connection +=========================================== + +.. automodule:: selenium.webdriver.remote.remote_connection diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.utils.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.utils.rst new file mode 100644 index 0000000..e035f43 --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.utils.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.utils +=============================== + +.. automodule:: selenium.webdriver.remote.utils diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.webdriver.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.webdriver.rst new file mode 100644 index 0000000..b390c19 --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.webdriver +=================================== + +.. automodule:: selenium.webdriver.remote.webdriver diff --git a/docs/source/webdriver_remote/selenium.webdriver.remote.webelement.rst b/docs/source/webdriver_remote/selenium.webdriver.remote.webelement.rst new file mode 100644 index 0000000..7b6ab4d --- /dev/null +++ b/docs/source/webdriver_remote/selenium.webdriver.remote.webelement.rst @@ -0,0 +1,4 @@ +selenium.webdriver.remote.webelement +==================================== + +.. automodule:: selenium.webdriver.remote.webelement diff --git a/docs/source/webdriver_support/selenium.webdriver.support.abstract_event_listener.rst b/docs/source/webdriver_support/selenium.webdriver.support.abstract_event_listener.rst new file mode 100644 index 0000000..53828ad --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.abstract_event_listener.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.abstract_event_listener +================================================== + +.. automodule:: selenium.webdriver.support.abstract_event_listener diff --git a/docs/source/webdriver_support/selenium.webdriver.support.color.rst b/docs/source/webdriver_support/selenium.webdriver.support.color.rst new file mode 100644 index 0000000..44aa61c --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.color.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.color +================================ + +.. automodule:: selenium.webdriver.support.color diff --git a/docs/source/webdriver_support/selenium.webdriver.support.event_firing_webdriver.rst b/docs/source/webdriver_support/selenium.webdriver.support.event_firing_webdriver.rst new file mode 100644 index 0000000..abbeacd --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.event_firing_webdriver.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.event_firing_webdriver +================================================= + +.. automodule:: selenium.webdriver.support.event_firing_webdriver diff --git a/docs/source/webdriver_support/selenium.webdriver.support.expected_conditions.rst b/docs/source/webdriver_support/selenium.webdriver.support.expected_conditions.rst new file mode 100644 index 0000000..46417a9 --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.expected_conditions.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.expected_conditions +============================================== + +.. automodule:: selenium.webdriver.support.expected_conditions diff --git a/docs/source/webdriver_support/selenium.webdriver.support.select.rst b/docs/source/webdriver_support/selenium.webdriver.support.select.rst new file mode 100644 index 0000000..02b0eb0 --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.select.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.select +================================= + +.. automodule:: selenium.webdriver.support.select diff --git a/docs/source/webdriver_support/selenium.webdriver.support.wait.rst b/docs/source/webdriver_support/selenium.webdriver.support.wait.rst new file mode 100644 index 0000000..f42089b --- /dev/null +++ b/docs/source/webdriver_support/selenium.webdriver.support.wait.rst @@ -0,0 +1,4 @@ +selenium.webdriver.support.wait +=============================== + +.. automodule:: selenium.webdriver.support.wait diff --git a/selenium/__init__.py b/selenium/__init__.py new file mode 100644 index 0000000..55d62ee --- /dev/null +++ b/selenium/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-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 import selenium + + +__version__ = "2.28.0" diff --git a/selenium/common/__init__.py b/selenium/common/__init__.py new file mode 100644 index 0000000..af96027 --- /dev/null +++ b/selenium/common/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-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. + +import exceptions \ No newline at end of file diff --git a/selenium/common/exceptions.py b/selenium/common/exceptions.py new file mode 100644 index 0000000..61a0740 --- /dev/null +++ b/selenium/common/exceptions.py @@ -0,0 +1,122 @@ +# 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 WebDriverException(Exception): + def __init__(self, msg=None, screen=None, stacktrace=None): + self.msg = msg + self.screen = screen + self.stacktrace = stacktrace + + def __str__(self): + exception_msg = "Message: %s " % repr(self.msg) + if self.screen is not None: + exception_msg = "%s; Screenshot: available via screen " \ + % exception_msg + if self.stacktrace is not None: + exception_msg = "%s; Stacktrace: %s " \ + % (exception_msg, str(self.stacktrace)) + return exception_msg + +class ErrorInResponseException(WebDriverException): + """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): + WebDriverException.__init__(self, msg) + self.response = response + +class InvalidSwitchToTargetException(WebDriverException): + """The frame or window target to be switched doesn't exist.""" + pass + +class NoSuchFrameException(InvalidSwitchToTargetException): + pass + +class NoSuchWindowException(InvalidSwitchToTargetException): + pass + +class NoSuchElementException(WebDriverException): + """find_element_by_* can't find the element.""" + pass + +class NoSuchAttributeException(WebDriverException): + """find_element_by_* can't find the element.""" + pass + +class StaleElementReferenceException(WebDriverException): + """Indicates that a reference to an element is now "stale" --- the + element no longer appears on the DOM of the page.""" + pass + +class InvalidElementStateException(WebDriverException): + pass + +class NoAlertPresentException(WebDriverException): + pass + +class ElementNotVisibleException(InvalidElementStateException): + """Thrown to indicate that although an element is present on the + DOM, it is not visible, and so is not able to be interacted + with.""" + pass + +class ElementNotSelectableException(InvalidElementStateException): + pass + +class InvalidCookieDomainException(WebDriverException): + """Thrown when attempting to add a cookie under a different domain + than the current URL.""" + pass + +class UnableToSetCookieException(WebDriverException): + """Thrown when a driver fails to set a cookie.""" + pass + +class RemoteDriverServerException(WebDriverException): + pass + +class TimeoutException(WebDriverException): + """Thrown when a command does not complete in enough time.""" + pass + +class MoveTargetOutOfBoundsException(WebDriverException): + """Indicates that the target provided to the actions move() method is invalid""" + pass + +class UnexpectedTagNameException(WebDriverException): + """Thrown when a support class did not get an expected web element""" + pass + +class InvalidSelectorException(NoSuchElementException): + """ Thrown when the selector which is used to find an element does not return + a WebElement. Currently this only happens when the selector is an xpath + expression is used which is either syntactically invalid (i.e. it is not a + xpath expression) or the expression does not select WebElements + (e.g. "count(//input)"). + """ + pass + +class ImeNotAvailableException(WebDriverException): + """ + Indicates that IME support is not available. This exception is thrown for every IME-related + method call if IME support is not available on the machine. + """ + pass + +class ImeActivationFailedException(WebDriverException): + """ Indicates that activating an IME engine has failed. """ + pass diff --git a/selenium/selenium.py b/selenium/selenium.py new file mode 100644 index 0000000..9b12a03 --- /dev/null +++ b/selenium/selenium.py @@ -0,0 +1,2111 @@ + +""" +Copyright 2011 Software Freedom Conservancy. + +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. +""" +__docformat__ = "restructuredtext en" + +import httplib +import urllib + +class selenium(object): + """ + Defines an object that runs Selenium commands. + + **Element Locators** + + Element Locators tell Selenium which HTML element a command refers to. + The format of a locator is: + + \ *locatorType*\ **=**\ \ *argument* + + + We support the following strategies for locating elements: + + + * \ **identifier**\ =\ *id*: + Select the element with the specified @id attribute. If no match is + found, select the first element whose @name attribute is \ *id*. + (This is normally the default; see below.) + * \ **id**\ =\ *id*: + Select the element with the specified @id attribute. + * \ **name**\ =\ *name*: + Select the first element with the specified @name attribute. + + * username + * name=username + + + The name may optionally be followed by one or more \ *element-filters*, separated from the name by whitespace. If the \ *filterType* is not specified, \ **value**\ is assumed. + + * name=flavour value=chocolate + + + * \ **dom**\ =\ *javascriptExpression*: + + Find an element by evaluating the specified string. This allows you to traverse the HTML Document Object + Model using JavaScript. Note that you must not return a value in this string; simply make it the last expression in the block. + + * dom=document.forms['myForm'].myDropdown + * dom=document.images[56] + * dom=function foo() { return document.links[1]; }; foo(); + + + * \ **xpath**\ =\ *xpathExpression*: + Locate an element using an XPath expression. + + * xpath=//img[@alt='The image alt text'] + * xpath=//table[@id='table1']//tr[4]/td[2] + * xpath=//a[contains(@href,'#id1')] + * xpath=//a[contains(@href,'#id1')]/@class + * xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td + * xpath=//input[@name='name2' and @value='yes'] + * xpath=//\*[text()="right"] + + + * \ **link**\ =\ *textPattern*: + Select the link (anchor) element which contains text matching the + specified \ *pattern*. + + * link=The link text + + + * \ **css**\ =\ *cssSelectorSyntax*: + Select the element using css selectors. Please refer to CSS2 selectors, CSS3 selectors for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package. + + * css=a[href="#id3"] + * css=span#firstChild + span + + + Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). + + * \ **ui**\ =\ *uiSpecifierString*: + Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the Selenium UI-Element Reference for more details. + + * ui=loginPages::loginButton() + * ui=settingsPages::toggle(label=Hide Email) + * ui=forumPages::postBody(index=2)//a[2] + + + + + + Without an explicit locator prefix, Selenium uses the following default + strategies: + + + * \ **dom**\ , for locators starting with "document." + * \ **xpath**\ , for locators starting with "//" + * \ **identifier**\ , otherwise + + **Element Filters** + + Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator. + + Filters look much like locators, ie. + + \ *filterType*\ **=**\ \ *argument* + + Supported element-filters are: + + \ **value=**\ \ *valuePattern* + + + Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons. + + \ **index=**\ \ *index* + + + Selects a single element based on its position in the list (offset from zero). + + **String-match Patterns** + + Various Pattern syntaxes are available for matching string values: + + + * \ **glob:**\ \ *pattern*: + Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a + kind of limited regular-expression syntax typically used in command-line + shells. In a glob pattern, "\*" represents any sequence of characters, and "?" + represents any single character. Glob patterns match against the entire + string. + * \ **regexp:**\ \ *regexp*: + Match a string using a regular-expression. The full power of JavaScript + regular-expressions is available. + * \ **regexpi:**\ \ *regexpi*: + Match a string using a case-insensitive regular-expression. + * \ **exact:**\ \ *string*: + + Match a string exactly, verbatim, without any of that fancy wildcard + stuff. + + + + If no pattern prefix is specified, Selenium assumes that it's a "glob" + pattern. + + + + For commands that return multiple values (such as verifySelectOptions), + the string being matched is a comma-separated list of the return values, + where both commas and backslashes in the values are backslash-escaped. + When providing a pattern, the optional matching syntax (i.e. glob, + regexp, etc.) is specified once, as usual, at the beginning of the + pattern. + + + """ + + ### This part is hard-coded in the XSL + def __init__(self, host, port, browserStartCommand, browserURL): + self.host = host + self.port = port + self.browserStartCommand = browserStartCommand + self.browserURL = browserURL + self.sessionId = None + self.extensionJs = "" + + def setExtensionJs(self, extensionJs): + self.extensionJs = extensionJs + + def start(self, browserConfigurationOptions=None, driver=None): + start_args = [self.browserStartCommand, self.browserURL, self.extensionJs] + if browserConfigurationOptions: + start_args.append(browserConfigurationOptions) + if driver: + id = driver.desired_capabilities['webdriver.remote.sessionid'] + start_args.append('webdriver.remote.sessionid=%s' % id) + result = self.get_string("getNewBrowserSession", start_args) + try: + self.sessionId = result + except ValueError: + raise Exception, result + + def stop(self): + self.do_command("testComplete", []) + self.sessionId = None + + def do_command(self, verb, args): + conn = httplib.HTTPConnection(self.host, self.port) + try: + body = u'cmd=' + urllib.quote_plus(unicode(verb).encode('utf-8')) + for i in range(len(args)): + body += '&' + unicode(i+1) + '=' + \ + urllib.quote_plus(unicode(args[i]).encode('utf-8')) + if (None != self.sessionId): + body += "&sessionId=" + unicode(self.sessionId) + headers = { + "Content-Type": + "application/x-www-form-urlencoded; charset=utf-8" + } + conn.request("POST", "/selenium-server/driver/", body, headers) + + response = conn.getresponse() + data = unicode(response.read(), "UTF-8") + if (not data.startswith('OK')): + raise Exception, data + return data + finally: + conn.close() + + def get_string(self, verb, args): + result = self.do_command(verb, args) + return result[3:] + + def get_string_array(self, verb, args): + csv = self.get_string(verb, args) + if not csv: + return [] + token = "" + tokens = [] + escape = False + for i in range(len(csv)): + letter = csv[i] + if (escape): + token = token + letter + escape = False + continue + if (letter == '\\'): + escape = True + elif (letter == ','): + tokens.append(token) + token = "" + else: + token = token + letter + tokens.append(token) + return tokens + + def get_number(self, verb, args): + return int(self.get_string(verb, args)) + + def get_number_array(self, verb, args): + string_array = self.get_string_array(verb, args) + num_array = [] + for i in string_array: + num_array.append(int(i)) + + return num_array + + def get_boolean(self, verb, args): + boolstr = self.get_string(verb, args) + if ("true" == boolstr): + return True + if ("false" == boolstr): + return False + raise ValueError, "result is neither 'true' nor 'false': " + boolstr + + def get_boolean_array(self, verb, args): + boolarr = self.get_string_array(verb, args) + for i, boolstr in enumerate(boolarr): + if ("true" == boolstr): + boolarr[i] = True + continue + if ("false" == boolstr): + boolarr[i] = False + continue + raise ValueError, "result is neither 'true' nor 'false': " + boolarr[i] + return boolarr + + + + def click(self,locator): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("click", [locator,]) + + + def double_click(self,locator): + """ + Double clicks on a link, button, checkbox or radio button. If the double click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("doubleClick", [locator,]) + + + def context_menu(self,locator): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + """ + self.do_command("contextMenu", [locator,]) + + + def click_at(self,locator,coordString): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("clickAt", [locator,coordString,]) + + + def double_click_at(self,locator,coordString): + """ + Doubleclicks on a link, button, checkbox or radio button. If the action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("doubleClickAt", [locator,coordString,]) + + + def context_menu_at(self,locator,coordString): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("contextMenuAt", [locator,coordString,]) + + + def fire_event(self,locator,eventName): + """ + Explicitly simulate an event, to trigger the corresponding "on\ *event*" + handler. + + 'locator' is an element locator + 'eventName' is the event name, e.g. "focus" or "blur" + """ + self.do_command("fireEvent", [locator,eventName,]) + + + def focus(self,locator): + """ + Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field. + + 'locator' is an element locator + """ + self.do_command("focus", [locator,]) + + + def key_press(self,locator,keySequence): + """ + Simulates a user pressing and releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyPress", [locator,keySequence,]) + + + def shift_key_down(self): + """ + Press the shift key and hold it down until doShiftUp() is called or a new page is loaded. + + """ + self.do_command("shiftKeyDown", []) + + + def shift_key_up(self): + """ + Release the shift key. + + """ + self.do_command("shiftKeyUp", []) + + + def meta_key_down(self): + """ + Press the meta key and hold it down until doMetaUp() is called or a new page is loaded. + + """ + self.do_command("metaKeyDown", []) + + + def meta_key_up(self): + """ + Release the meta key. + + """ + self.do_command("metaKeyUp", []) + + + def alt_key_down(self): + """ + Press the alt key and hold it down until doAltUp() is called or a new page is loaded. + + """ + self.do_command("altKeyDown", []) + + + def alt_key_up(self): + """ + Release the alt key. + + """ + self.do_command("altKeyUp", []) + + + def control_key_down(self): + """ + Press the control key and hold it down until doControlUp() is called or a new page is loaded. + + """ + self.do_command("controlKeyDown", []) + + + def control_key_up(self): + """ + Release the control key. + + """ + self.do_command("controlKeyUp", []) + + + def key_down(self,locator,keySequence): + """ + Simulates a user pressing a key (without releasing it yet). + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyDown", [locator,keySequence,]) + + + def key_up(self,locator,keySequence): + """ + Simulates a user releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyUp", [locator,keySequence,]) + + + def mouse_over(self,locator): + """ + Simulates a user hovering a mouse over the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOver", [locator,]) + + + def mouse_out(self,locator): + """ + Simulates a user moving the mouse pointer away from the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOut", [locator,]) + + + def mouse_down(self,locator): + """ + Simulates a user pressing the left mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDown", [locator,]) + + + def mouse_down_right(self,locator): + """ + Simulates a user pressing the right mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDownRight", [locator,]) + + + def mouse_down_at(self,locator,coordString): + """ + Simulates a user pressing the left mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownAt", [locator,coordString,]) + + + def mouse_down_right_at(self,locator,coordString): + """ + Simulates a user pressing the right mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownRightAt", [locator,coordString,]) + + + def mouse_up(self,locator): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUp", [locator,]) + + + def mouse_up_right(self,locator): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUpRight", [locator,]) + + + def mouse_up_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpAt", [locator,coordString,]) + + + def mouse_up_right_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpRightAt", [locator,coordString,]) + + + def mouse_move(self,locator): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseMove", [locator,]) + + + def mouse_move_at(self,locator,coordString): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseMoveAt", [locator,coordString,]) + + + def type(self,locator,value): + """ + Sets the value of an input field, as though you typed it in. + + + Can also be used to set the value of combo boxes, check boxes, etc. In these cases, + value should be the value of the option selected, not the visible text. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("type", [locator,value,]) + + + def type_keys(self,locator,value): + """ + Simulates keystroke events on the specified element, as though you typed the value key-by-key. + + + This is a convenience method for calling keyDown, keyUp, keyPress for every character in the specified string; + this is useful for dynamic UI widgets (like auto-completing combo boxes) that require explicit key events. + + Unlike the simple "type" command, which forces the specified value into the page directly, this command + may or may not have any visible effect, even in cases where typing keys would normally have a visible effect. + For example, if you use "typeKeys" on a form element, you may or may not see the results of what you typed in + the field. + + In some cases, you may need to use the simple "type" command to set the value of the field and then the "typeKeys" command to + send the keystroke events corresponding to what you just typed. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("typeKeys", [locator,value,]) + + + def set_speed(self,value): + """ + Set execution speed (i.e., set the millisecond length of a delay which will follow each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + 'value' is the number of milliseconds to pause after operation + """ + self.do_command("setSpeed", [value,]) + + + def get_speed(self): + """ + Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + See also setSpeed. + + """ + return self.get_string("getSpeed", []) + + def get_log(self): + """ + Get RC logs associated with current session. + + """ + return self.get_string("getLog", []) + + + def check(self,locator): + """ + Check a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("check", [locator,]) + + + def uncheck(self,locator): + """ + Uncheck a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("uncheck", [locator,]) + + + def select(self,selectLocator,optionLocator): + """ + Select an option from a drop-down using an option locator. + + + + Option locators provide different ways of specifying options of an HTML + Select element (e.g. for selecting a specific option, or for asserting + that the selected option satisfies a specification). There are several + forms of Select Option Locator. + + + * \ **label**\ =\ *labelPattern*: + matches options based on their labels, i.e. the visible text. (This + is the default.) + + * label=regexp:^[Oo]ther + + + * \ **value**\ =\ *valuePattern*: + matches options based on their values. + + * value=other + + + * \ **id**\ =\ *id*: + + matches options based on their ids. + + * id=option1 + + + * \ **index**\ =\ *index*: + matches an option based on its index (offset from zero). + + * index=2 + + + + + + If no option locator prefix is provided, the default behaviour is to match on \ **label**\ . + + + + 'selectLocator' is an element locator identifying a drop-down menu + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("select", [selectLocator,optionLocator,]) + + + def add_selection(self,locator,optionLocator): + """ + Add a selection to the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("addSelection", [locator,optionLocator,]) + + + def remove_selection(self,locator,optionLocator): + """ + Remove a selection from the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("removeSelection", [locator,optionLocator,]) + + + def remove_all_selections(self,locator): + """ + Unselects all of the selected options in a multi-select element. + + 'locator' is an element locator identifying a multi-select box + """ + self.do_command("removeAllSelections", [locator,]) + + + def submit(self,formLocator): + """ + Submit the specified form. This is particularly useful for forms without + submit buttons, e.g. single-input "Search" forms. + + 'formLocator' is an element locator for the form you want to submit + """ + self.do_command("submit", [formLocator,]) + + def open(self,url,ignoreResponseCode=True): + """ + Opens an URL in the test frame. This accepts both relative and absolute + URLs. + + The "open" command waits for the page to load before proceeding, + ie. the "AndWait" suffix is implicit. + + \ *Note*: The URL must be on the same domain as the runner HTML + due to security restrictions in the browser (Same Origin Policy). If you + need to open an URL on another domain, use the Selenium Server to start a + new browser session on that domain. + + 'url' is the URL to open; may be relative or absolute + 'ignoreResponseCode' if set to true: doesnt send ajax HEAD/GET request; if set to false: sends ajax HEAD/GET request to the url and reports error code if any as response to open. + """ + self.do_command("open", [url,ignoreResponseCode]) + + + def open_window(self,url,windowID): + """ + Opens a popup window (if a window with that ID isn't already open). + After opening the window, you'll need to select it using the selectWindow + command. + + + This command can also be a useful workaround for bug SEL-339. In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'url' is the URL to open, which can be blank + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("openWindow", [url,windowID,]) + + + def select_window(self,windowID): + """ + Selects a popup window using a window locator; once a popup window has been selected, all + commands go to that window. To select the main window again, use null + as the target. + + + + + Window locators provide different ways of specifying the window object: + by title, by internal JavaScript "name," or by JavaScript variable. + + + * \ **title**\ =\ *My Special Window*: + Finds the window using the text that appears in the title bar. Be careful; + two windows can share the same title. If that happens, this locator will + just pick one. + + * \ **name**\ =\ *myWindow*: + Finds the window using its internal JavaScript "name" property. This is the second + parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag) + (which Selenium intercepts). + + * \ **var**\ =\ *variableName*: + Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current + application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using + "var=foo". + + + + + If no window locator prefix is provided, we'll try to guess what you mean like this: + + 1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser). + + 2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed + that this variable contains the return value from a call to the JavaScript window.open() method. + + 3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names". + + 4.) If \ *that* fails, we'll try looping over all of the known windows to try to find the appropriate "title". + Since "title" is not necessarily unique, this may have unexpected behavior. + + If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages + which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages + like the following for each window as it is opened: + + ``debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"`` + + In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + (This is bug SEL-339.) In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("selectWindow", [windowID,]) + + + def select_pop_up(self,windowID): + """ + Simplifies the process of selecting a popup window (and does not offer + functionality beyond what ``selectWindow()`` already provides). + + * If ``windowID`` is either not specified, or specified as + "null", the first non-top window is selected. The top window is the one + that would be selected by ``selectWindow()`` without providing a + ``windowID`` . This should not be used when more than one popup + window is in play. + * Otherwise, the window will be looked up considering + ``windowID`` as the following in order: 1) the "name" of the + window, as specified to ``window.open()``; 2) a javascript + variable which is a reference to a window; and 3) the title of the + window. This is the same ordered lookup performed by + ``selectWindow`` . + + + + 'windowID' is an identifier for the popup window, which can take on a number of different meanings + """ + self.do_command("selectPopUp", [windowID,]) + + + def deselect_pop_up(self): + """ + Selects the main window. Functionally equivalent to using + ``selectWindow()`` and specifying no value for + ``windowID``. + + """ + self.do_command("deselectPopUp", []) + + + def select_frame(self,locator): + """ + Selects a frame within the current window. (You may invoke this command + multiple times to select nested frames.) To select the parent frame, use + "relative=parent" as a locator; to select the top frame, use "relative=top". + You can also select a frame by its 0-based index number; select the first frame with + "index=0", or the third frame with "index=2". + + + You may also use a DOM expression to identify the frame you want directly, + like this: ``dom=frames["main"].frames["subframe"]`` + + + 'locator' is an element locator identifying a frame or iframe + """ + self.do_command("selectFrame", [locator,]) + + + def get_whether_this_frame_match_frame_expression(self,currentFrameString,target): + """ + Determine whether current/locator identify the frame containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" frame. In this case, when the test calls selectFrame, this + routine is called for each frame to figure out which one has been selected. + The selected frame will return true, while all others will return false. + + + 'currentFrameString' is starting frame + 'target' is new frame (which might be relative to the current one) + """ + return self.get_boolean("getWhetherThisFrameMatchFrameExpression", [currentFrameString,target,]) + + + def get_whether_this_window_match_window_expression(self,currentWindowString,target): + """ + Determine whether currentWindowString plus target identify the window containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" window. In this case, when the test calls selectWindow, this + routine is called for each window to figure out which one has been selected. + The selected window will return true, while all others will return false. + + + 'currentWindowString' is starting window + 'target' is new window (which might be relative to the current one, e.g., "_parent") + """ + return self.get_boolean("getWhetherThisWindowMatchWindowExpression", [currentWindowString,target,]) + + + def wait_for_pop_up(self,windowID,timeout): + """ + Waits for a popup window to appear and load up. + + 'windowID' is the JavaScript window "name" of the window that will appear (not the text of the title bar) If unspecified, or specified as "null", this command will wait for the first non-top window to appear (don't rely on this if you are working with multiple popups simultaneously). + 'timeout' is a timeout in milliseconds, after which the action will return with an error. If this value is not specified, the default Selenium timeout will be used. See the setTimeout() command. + """ + self.do_command("waitForPopUp", [windowID,timeout,]) + + + def choose_cancel_on_next_confirmation(self): + """ + + + By default, Selenium's overridden window.confirm() function will + return true, as if the user had manually clicked OK; after running + this command, the next call to confirm() will return false, as if + the user had clicked Cancel. Selenium will then resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call this command for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseCancelOnNextConfirmation", []) + + + def choose_ok_on_next_confirmation(self): + """ + + + Undo the effect of calling chooseCancelOnNextConfirmation. Note + that Selenium's overridden window.confirm() function will normally automatically + return true, as if the user had manually clicked OK, so you shouldn't + need to use this command unless for some reason you need to change + your mind prior to the next confirmation. After any confirmation, Selenium will resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseOkOnNextConfirmation", []) + + + def answer_on_next_prompt(self,answer): + """ + Instructs Selenium to return the specified answer string in response to + the next JavaScript prompt [window.prompt()]. + + 'answer' is the answer to give in response to the prompt pop-up + """ + self.do_command("answerOnNextPrompt", [answer,]) + + + def go_back(self): + """ + Simulates the user clicking the "back" button on their browser. + + """ + self.do_command("goBack", []) + + + def refresh(self): + """ + Simulates the user clicking the "Refresh" button on their browser. + + """ + self.do_command("refresh", []) + + + def close(self): + """ + Simulates the user clicking the "close" button in the titlebar of a popup + window or tab. + + """ + self.do_command("close", []) + + + def is_alert_present(self): + """ + Has an alert occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isAlertPresent", []) + + + def is_prompt_present(self): + """ + Has a prompt occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isPromptPresent", []) + + + def is_confirmation_present(self): + """ + Has confirm() been called? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isConfirmationPresent", []) + + + def get_alert(self): + """ + Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts. + + + Getting an alert has the same effect as manually clicking OK. If an + alert is generated but you do not consume it with getAlert, the next Selenium action + will fail. + + Under Selenium, JavaScript alerts will NOT pop up a visible alert + dialog. + + Selenium does NOT support JavaScript alerts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getAlert", []) + + + def get_confirmation(self): + """ + Retrieves the message of a JavaScript confirmation dialog generated during + the previous action. + + + + By default, the confirm function will return true, having the same effect + as manually clicking OK. This can be changed by prior execution of the + chooseCancelOnNextConfirmation command. + + + + If an confirmation is generated but you do not consume it with getConfirmation, + the next Selenium action will fail. + + + + NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible + dialog. + + + + NOTE: Selenium does NOT support JavaScript confirmations that are + generated in a page's onload() event handler. In this case a visible + dialog WILL be generated and Selenium will hang until you manually click + OK. + + + + """ + return self.get_string("getConfirmation", []) + + + def get_prompt(self): + """ + Retrieves the message of a JavaScript question prompt dialog generated during + the previous action. + + + Successful handling of the prompt requires prior execution of the + answerOnNextPrompt command. If a prompt is generated but you + do not get/verify it, the next Selenium action will fail. + + NOTE: under Selenium, JavaScript prompts will NOT pop up a visible + dialog. + + NOTE: Selenium does NOT support JavaScript prompts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getPrompt", []) + + + def get_location(self): + """ + Gets the absolute URL of the current page. + + """ + return self.get_string("getLocation", []) + + + def get_title(self): + """ + Gets the title of the current page. + + """ + return self.get_string("getTitle", []) + + + def get_body_text(self): + """ + Gets the entire text of the page. + + """ + return self.get_string("getBodyText", []) + + + def get_value(self,locator): + """ + Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter). + For checkbox/radio elements, the value will be "on" or "off" depending on + whether the element is checked or not. + + 'locator' is an element locator + """ + return self.get_string("getValue", [locator,]) + + + def get_text(self,locator): + """ + Gets the text of an element. This works for any element that contains + text. This command uses either the textContent (Mozilla-like browsers) or + the innerText (IE-like browsers) of the element, which is the rendered + text shown to the user. + + 'locator' is an element locator + """ + return self.get_string("getText", [locator,]) + + + def highlight(self,locator): + """ + Briefly changes the backgroundColor of the specified element yellow. Useful for debugging. + + 'locator' is an element locator + """ + self.do_command("highlight", [locator,]) + + + def get_eval(self,script): + """ + Gets the result of evaluating the specified JavaScript snippet. The snippet may + have multiple lines, but only the result of the last line will be returned. + + + Note that, by default, the snippet will run in the context of the "selenium" + object itself, so ``this`` will refer to the Selenium object. Use ``window`` to + refer to the window of your application, e.g. ``window.document.getElementById('foo')`` + + If you need to use + a locator to refer to a single element in your application page, you can + use ``this.browserbot.findElement("id=foo")`` where "id=foo" is your locator. + + + 'script' is the JavaScript snippet to run + """ + return self.get_string("getEval", [script,]) + + + def is_checked(self,locator): + """ + Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button. + + 'locator' is an element locator pointing to a checkbox or radio button + """ + return self.get_boolean("isChecked", [locator,]) + + + def get_table(self,tableCellAddress): + """ + Gets the text from a cell of a table. The cellAddress syntax + tableLocator.row.column, where row and column start at 0. + + 'tableCellAddress' is a cell address, e.g. "foo.1.4" + """ + return self.get_string("getTable", [tableCellAddress,]) + + + def get_selected_labels(self,selectLocator): + """ + Gets all option labels (visible text) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedLabels", [selectLocator,]) + + + def get_selected_label(self,selectLocator): + """ + Gets option label (visible text) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedLabel", [selectLocator,]) + + + def get_selected_values(self,selectLocator): + """ + Gets all option values (value attributes) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedValues", [selectLocator,]) + + + def get_selected_value(self,selectLocator): + """ + Gets option value (value attribute) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedValue", [selectLocator,]) + + + def get_selected_indexes(self,selectLocator): + """ + Gets all option indexes (option number, starting at 0) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIndexes", [selectLocator,]) + + + def get_selected_index(self,selectLocator): + """ + Gets option index (option number, starting at 0) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedIndex", [selectLocator,]) + + + def get_selected_ids(self,selectLocator): + """ + Gets all option element IDs for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIds", [selectLocator,]) + + + def get_selected_id(self,selectLocator): + """ + Gets option element ID for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedId", [selectLocator,]) + + + def is_something_selected(self,selectLocator): + """ + Determines whether some option in a drop-down menu is selected. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_boolean("isSomethingSelected", [selectLocator,]) + + + def get_select_options(self,selectLocator): + """ + Gets all option labels in the specified select drop-down. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectOptions", [selectLocator,]) + + + def get_attribute(self,attributeLocator): + """ + Gets the value of an element attribute. The value of the attribute may + differ across browsers (this is the case for the "style" attribute, for + example). + + 'attributeLocator' is an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar" + """ + return self.get_string("getAttribute", [attributeLocator,]) + + + def is_text_present(self,pattern): + """ + Verifies that the specified text pattern appears somewhere on the rendered page shown to the user. + + 'pattern' is a pattern to match with the text of the page + """ + return self.get_boolean("isTextPresent", [pattern,]) + + + def is_element_present(self,locator): + """ + Verifies that the specified element is somewhere on the page. + + 'locator' is an element locator + """ + return self.get_boolean("isElementPresent", [locator,]) + + + def is_visible(self,locator): + """ + Determines if the specified element is visible. An + element can be rendered invisible by setting the CSS "visibility" + property to "hidden", or the "display" property to "none", either for the + element itself or one if its ancestors. This method will fail if + the element is not present. + + 'locator' is an element locator + """ + return self.get_boolean("isVisible", [locator,]) + + + def is_editable(self,locator): + """ + Determines whether the specified input element is editable, ie hasn't been disabled. + This method will fail if the specified element isn't an input element. + + 'locator' is an element locator + """ + return self.get_boolean("isEditable", [locator,]) + + + def get_all_buttons(self): + """ + Returns the IDs of all buttons on the page. + + + If a given button has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllButtons", []) + + + def get_all_links(self): + """ + Returns the IDs of all links on the page. + + + If a given link has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllLinks", []) + + + def get_all_fields(self): + """ + Returns the IDs of all input fields on the page. + + + If a given field has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllFields", []) + + + def get_attribute_from_all_windows(self,attributeName): + """ + Returns every instance of some attribute from all known windows. + + 'attributeName' is name of an attribute on the windows + """ + return self.get_string_array("getAttributeFromAllWindows", [attributeName,]) + + + def dragdrop(self,locator,movementsString): + """ + deprecated - use dragAndDrop instead + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragdrop", [locator,movementsString,]) + + + def set_mouse_speed(self,pixels): + """ + Configure the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + Setting this value to 0 means that we'll send a "mousemove" event to every single pixel + in between the start location and the end location; that can be very slow, and may + cause some browsers to force the JavaScript to timeout. + + If the mouse speed is greater than the distance between the two dragged objects, we'll + just send one "mousemove" at the start location and then one final one at the end location. + + + 'pixels' is the number of pixels between "mousemove" events + """ + self.do_command("setMouseSpeed", [pixels,]) + + + def get_mouse_speed(self): + """ + Returns the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + """ + return self.get_number("getMouseSpeed", []) + + + def drag_and_drop(self,locator,movementsString): + """ + Drags an element a certain distance and then drops it + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragAndDrop", [locator,movementsString,]) + + + def drag_and_drop_to_object(self,locatorOfObjectToBeDragged,locatorOfDragDestinationObject): + """ + Drags an element and drops it on another element + + 'locatorOfObjectToBeDragged' is an element to be dragged + 'locatorOfDragDestinationObject' is an element whose location (i.e., whose center-most pixel) will be the point where locatorOfObjectToBeDragged is dropped + """ + self.do_command("dragAndDropToObject", [locatorOfObjectToBeDragged,locatorOfDragDestinationObject,]) + + + def window_focus(self): + """ + Gives focus to the currently selected window + + """ + self.do_command("windowFocus", []) + + + def window_maximize(self): + """ + Resize currently selected window to take up the entire screen + + """ + self.do_command("windowMaximize", []) + + + def get_all_window_ids(self): + """ + Returns the IDs of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowIds", []) + + + def get_all_window_names(self): + """ + Returns the names of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowNames", []) + + + def get_all_window_titles(self): + """ + Returns the titles of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowTitles", []) + + + def get_html_source(self): + """ + Returns the entire HTML source between the opening and + closing "html" tags. + + """ + return self.get_string("getHtmlSource", []) + + + def set_cursor_position(self,locator,position): + """ + Moves the text cursor to the specified position in the given input element or textarea. + This method will fail if the specified element isn't an input element or textarea. + + 'locator' is an element locator pointing to an input element or textarea + 'position' is the numerical position of the cursor in the field; position should be 0 to move the position to the beginning of the field. You can also set the cursor to -1 to move it to the end of the field. + """ + self.do_command("setCursorPosition", [locator,position,]) + + + def get_element_index(self,locator): + """ + Get the relative index of an element to its parent (starting from 0). The comment node and empty text node + will be ignored. + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementIndex", [locator,]) + + + def is_ordered(self,locator1,locator2): + """ + Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will + not be considered ordered. + + 'locator1' is an element locator pointing to the first element + 'locator2' is an element locator pointing to the second element + """ + return self.get_boolean("isOrdered", [locator1,locator2,]) + + + def get_element_position_left(self,locator): + """ + Retrieves the horizontal position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionLeft", [locator,]) + + + def get_element_position_top(self,locator): + """ + Retrieves the vertical position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionTop", [locator,]) + + + def get_element_width(self,locator): + """ + Retrieves the width of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementWidth", [locator,]) + + + def get_element_height(self,locator): + """ + Retrieves the height of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementHeight", [locator,]) + + + def get_cursor_position(self,locator): + """ + Retrieves the text cursor position in the given input element or textarea; beware, this may not work perfectly on all browsers. + + + Specifically, if the cursor/selection has been cleared by JavaScript, this command will tend to + return the position of the last location of the cursor, even though the cursor is now gone from the page. This is filed as SEL-243. + + This method will fail if the specified element isn't an input element or textarea, or there is no cursor in the element. + + 'locator' is an element locator pointing to an input element or textarea + """ + return self.get_number("getCursorPosition", [locator,]) + + + def get_expression(self,expression): + """ + Returns the specified expression. + + + This is useful because of JavaScript preprocessing. + It is used to generate commands like assertExpression and waitForExpression. + + + 'expression' is the value to return + """ + return self.get_string("getExpression", [expression,]) + + + def get_xpath_count(self,xpath): + """ + Returns the number of nodes that match the specified xpath, eg. "//table" would give + the number of tables. + + 'xpath' is the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you. + """ + return self.get_number("getXpathCount", [xpath,]) + + def get_css_count(self,css): + """ + Returns the number of nodes that match the specified css selector, eg. "css=table" would give + the number of tables. + + 'css' is the css selector to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you. + """ + return self.get_number("getCssCount", [css,]) + + def assign_id(self,locator,identifier): + """ + Temporarily sets the "id" attribute of the specified element, so you can locate it in the future + using its ID rather than a slow/complicated XPath. This ID will disappear once the page is + reloaded. + + 'locator' is an element locator pointing to an element + 'identifier' is a string to be used as the ID of the specified element + """ + self.do_command("assignId", [locator,identifier,]) + + + def allow_native_xpath(self,allow): + """ + Specifies whether Selenium should use the native in-browser implementation + of XPath (if any native version is available); if you pass "false" to + this function, we will always use our pure-JavaScript xpath library. + Using the pure-JS xpath library can improve the consistency of xpath + element locators between different browser vendors, but the pure-JS + version is much slower than the native implementations. + + 'allow' is boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath + """ + self.do_command("allowNativeXpath", [allow,]) + + + def ignore_attributes_without_value(self,ignore): + """ + Specifies whether Selenium will ignore xpath attributes that have no + value, i.e. are the empty string, when using the non-native xpath + evaluation engine. You'd want to do this for performance reasons in IE. + However, this could break certain xpaths, for example an xpath that looks + for an attribute whose value is NOT the empty string. + + The hope is that such xpaths are relatively rare, but the user should + have the option of using them. Note that this only influences xpath + evaluation when using the ajaxslt engine (i.e. not "javascript-xpath"). + + 'ignore' is boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness. + """ + self.do_command("ignoreAttributesWithoutValue", [ignore,]) + + + def wait_for_condition(self,script,timeout): + """ + Runs the specified JavaScript snippet repeatedly until it evaluates to "true". + The snippet may have multiple lines, but only the result of the last line + will be considered. + + + Note that, by default, the snippet will be run in the runner's test window, not in the window + of your application. To get the window of your application, you can use + the JavaScript snippet ``selenium.browserbot.getCurrentWindow()``, and then + run your JavaScript in there + + + 'script' is the JavaScript snippet to run + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForCondition", [script,timeout,]) + + + def set_timeout(self,timeout): + """ + Specifies the amount of time that Selenium will wait for actions to complete. + + + Actions that require waiting include "open" and the "waitFor\*" actions. + + The default timeout is 30 seconds. + + 'timeout' is a timeout in milliseconds, after which the action will return with an error + """ + self.do_command("setTimeout", [timeout,]) + + + def wait_for_page_to_load(self,timeout): + """ + Waits for a new page to load. + + + You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc. + (which are only available in the JS API). + + Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded" + flag when it first notices a page load. Running any other Selenium command after + turns the flag to false. Hence, if you want to wait for a page to load, you must + wait immediately after a Selenium command that caused a page-load. + + + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForPageToLoad", [timeout,]) + + + def wait_for_frame_to_load(self,frameAddress,timeout): + """ + Waits for a new frame to load. + + + Selenium constantly keeps track of new pages and frames loading, + and sets a "newPageLoaded" flag when it first notices a page load. + + + See waitForPageToLoad for more information. + + 'frameAddress' is FrameAddress from the server side + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForFrameToLoad", [frameAddress,timeout,]) + + + def get_cookie(self): + """ + Return all cookies of the current page under test. + + """ + return self.get_string("getCookie", []) + + + def get_cookie_by_name(self,name): + """ + Returns the value of the cookie with the specified name, or throws an error if the cookie is not present. + + 'name' is the name of the cookie + """ + return self.get_string("getCookieByName", [name,]) + + + def is_cookie_present(self,name): + """ + Returns true if a cookie with the specified name is present, or false otherwise. + + 'name' is the name of the cookie + """ + return self.get_boolean("isCookiePresent", [name,]) + + + def create_cookie(self,nameValuePair,optionsString): + """ + Create a new cookie whose path and domain are same with those of current page + under test, unless you specified a path for this cookie explicitly. + + 'nameValuePair' is name and value of the cookie in a format "name=value" + 'optionsString' is options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("createCookie", [nameValuePair,optionsString,]) + + + def delete_cookie(self,name,optionsString): + """ + Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you + need to delete it using the exact same path and domain that were used to create the cookie. + If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also + note that specifying a domain that isn't a subset of the current domain will usually fail. + + Since there's no way to discover at runtime the original path and domain of a given cookie, + we've added an option called 'recurse' to try all sub-domains of the current domain with + all paths that are a subset of the current path. Beware; this option can be slow. In + big-O notation, it operates in O(n\*m) time, where n is the number of dots in the domain + name and m is the number of slashes in the path. + + 'name' is the name of the cookie to be deleted + 'optionsString' is options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("deleteCookie", [name,optionsString,]) + + + def delete_all_visible_cookies(self): + """ + Calls deleteCookie with recurse=true on all cookies visible to the current page. + As noted on the documentation for deleteCookie, recurse=true can be much slower + than simply deleting the cookies using a known domain/path. + + """ + self.do_command("deleteAllVisibleCookies", []) + + + def set_browser_log_level(self,logLevel): + """ + Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded. + Valid logLevel strings are: "debug", "info", "warn", "error" or "off". + To see the browser logs, you need to + either show the log window in GUI mode, or enable browser-side logging in Selenium RC. + + 'logLevel' is one of the following: "debug", "info", "warn", "error" or "off" + """ + self.do_command("setBrowserLogLevel", [logLevel,]) + + + def run_script(self,script): + """ + Creates a new "script" tag in the body of the current test window, and + adds the specified text into the body of the command. Scripts run in + this way can often be debugged more easily than scripts executed using + Selenium's "getEval" command. Beware that JS exceptions thrown in these script + tags aren't managed by Selenium, so you should probably wrap your script + in try/catch blocks if there is any chance that the script will throw + an exception. + + 'script' is the JavaScript snippet to run + """ + self.do_command("runScript", [script,]) + + + def add_location_strategy(self,strategyName,functionDefinition): + """ + Defines a new function for Selenium to locate elements on the page. + For example, + if you define the strategy "foo", and someone runs click("foo=blah"), we'll + run your function, passing you the string "blah", and click on the element + that your function + returns, or throw an "Element not found" error if your function returns null. + + We'll pass three arguments to your function: + + * locator: the string the user passed in + * inWindow: the currently selected window + * inDocument: the currently selected document + + + The function must return null if the element can't be found. + + 'strategyName' is the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation. + 'functionDefinition' is a string defining the body of a function in JavaScript. For example: ``return inDocument.getElementById(locator);`` + """ + self.do_command("addLocationStrategy", [strategyName,functionDefinition,]) + + + def capture_entire_page_screenshot(self,filename,kwargs): + """ + Saves the entire contents of the current window canvas to a PNG file. + Contrast this with the captureScreenshot command, which captures the + contents of the OS viewport (i.e. whatever is currently being displayed + on the monitor), and is implemented in the RC only. Currently this only + works in Firefox when running in chrome mode, and in IE non-HTA using + the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly + borrowed from the Screengrab! Firefox extension. Please see + http://www.screengrab.org and http://snapsie.sourceforge.net/ for + details. + + 'filename' is the path to the file to persist the screenshot as. No + filename extension will be appended by default. Directories will not be + created if they do not exist, and an exception will be thrown, possibly + by native code. + + 'kwargs' is a kwargs string that modifies the way the + screenshot is captured. + + Example: "background=#CCFFDD" + + Currently valid options: + + * background + + the background CSS for the HTML document. + This may be useful to set for capturing screenshots of + less-than-ideal layouts, for example where absolute positioning + causes the calculation of the canvas dimension to fail and a black + background is exposed (possibly obscuring black text). + + """ + self.do_command("captureEntirePageScreenshot", [filename,kwargs,]) + + + def rollup(self,rollupName,kwargs): + """ + Executes a command rollup, which is a series of commands with a unique + name, and optionally arguments that control the generation of the set of + commands. If any one of the rolled-up commands fails, the rollup is + considered to have failed. Rollups may also contain nested rollups. + + 'rollupName' is the name of the rollup command + 'kwargs' is keyword arguments string that influences how the rollup expands into commands + """ + self.do_command("rollup", [rollupName,kwargs,]) + + + def add_script(self,scriptContent,scriptTagId): + """ + Loads script content into a new script tag in the Selenium document. This + differs from the runScript command in that runScript adds the script tag + to the document of the AUT, not the Selenium document. The following + entities in the script content are replaced by the characters they + represent: + + < + > + & + + The corresponding remove command is removeScript. + + 'scriptContent' is the Javascript content of the script to add + 'scriptTagId' is (optional) the id of the new script tag. If specified, and an element with this id already exists, this operation will fail. + """ + self.do_command("addScript", [scriptContent,scriptTagId,]) + + + def remove_script(self,scriptTagId): + """ + Removes a script tag from the Selenium document identified by the given + id. Does nothing if the referenced tag doesn't exist. + + 'scriptTagId' is the id of the script element to remove. + """ + self.do_command("removeScript", [scriptTagId,]) + + + def use_xpath_library(self,libraryName): + """ + Allows choice of one of the available libraries. + + 'libraryName' is name of the desired library Only the following three can be chosen: + * "ajaxslt" - Google's library + * "javascript-xpath" - Cybozu Labs' faster library + * "default" - The default library. Currently the default library is "ajaxslt" . + + If libraryName isn't one of these three, then no change will be made. + """ + self.do_command("useXpathLibrary", [libraryName,]) + + + def set_context(self,context): + """ + Writes a message to the status bar and adds a note to the browser-side + log. + + 'context' is the message to be sent to the browser + """ + self.do_command("setContext", [context,]) + + + def attach_file(self,fieldLocator,fileLocator): + """ + Sets a file input (upload) field to the file listed in fileLocator + + 'fieldLocator' is an element locator + 'fileLocator' is a URL pointing to the specified file. Before the file can be set in the input field (fieldLocator), Selenium RC may need to transfer the file to the local machine before attaching the file in a web page form. This is common in selenium grid configurations where the RC server driving the browser is not the same machine that started the test. Supported Browsers: Firefox ("\*chrome") only. + """ + self.do_command("attachFile", [fieldLocator,fileLocator,]) + + + def capture_screenshot(self,filename): + """ + Captures a PNG screenshot to the specified file. + + 'filename' is the absolute path to the file to be written, e.g. "c:\blah\screenshot.png" + """ + self.do_command("captureScreenshot", [filename,]) + + + def capture_screenshot_to_string(self): + """ + Capture a PNG screenshot. It then returns the file as a base 64 encoded string. + + """ + return self.get_string("captureScreenshotToString", []) + + + def captureNetworkTraffic(self, type): + """ + Returns the network traffic seen by the browser, including headers, AJAX requests, status codes, and timings. When this function is called, the traffic log is cleared, so the returned content is only the traffic seen since the last call. + + 'type' is The type of data to return the network traffic as. Valid values are: json, xml, or plain. + """ + return self.get_string("captureNetworkTraffic", [type,]) + + def capture_network_traffic(self, type): + return self.captureNetworkTraffic(type) + + def addCustomRequestHeader(self, key, value): + """ + Tells the Selenium server to add the specificed key and value as a custom outgoing request header. This only works if the browser is configured to use the built in Selenium proxy. + + 'key' the header name. + 'value' the header value. + """ + return self.do_command("addCustomRequestHeader", [key,value,]) + + def add_custom_request_header(self, key, value): + return self.addCustomRequestHeader(key, value) + + def capture_entire_page_screenshot_to_string(self,kwargs): + """ + Downloads a screenshot of the browser current window canvas to a + based 64 encoded PNG file. The \ *entire* windows canvas is captured, + including parts rendered outside of the current view port. + + Currently this only works in Mozilla and when running in chrome mode. + + 'kwargs' is A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text). + """ + return self.get_string("captureEntirePageScreenshotToString", [kwargs,]) + + + def shut_down_selenium_server(self): + """ + Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send + commands to the server; you can't remotely start the server once it has been stopped. Normally + you should prefer to run the "stop" command, which terminates the current browser session, rather than + shutting down the entire server. + + """ + self.do_command("shutDownSeleniumServer", []) + + + def retrieve_last_remote_control_logs(self): + """ + Retrieve the last messages logged on a specific remote control. Useful for error reports, especially + when running multiple remote controls in a distributed environment. The maximum number of log messages + that can be retrieve is configured on remote control startup. + + """ + return self.get_string("retrieveLastRemoteControlLogs", []) + + + def key_down_native(self,keycode): + """ + Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyDownNative", [keycode,]) + + + def key_up_native(self,keycode): + """ + Simulates a user releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyUpNative", [keycode,]) + + + def key_press_native(self,keycode): + """ + Simulates a user pressing and releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyPressNative", [keycode,]) diff --git a/selenium/webdriver/__init__.py b/selenium/webdriver/__init__.py new file mode 100644 index 0000000..5577ae2 --- /dev/null +++ b/selenium/webdriver/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# +# Copyright 2008-2010 Webdriver_name committers +# Copyright 2008-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 firefox.webdriver import WebDriver as Firefox +from firefox.firefox_profile import FirefoxProfile +from chrome.webdriver import WebDriver as Chrome +from chrome.options import Options as ChromeOptions +from ie.webdriver import WebDriver as Ie +from opera.webdriver import WebDriver as Opera +from phantomjs.webdriver import WebDriver as PhantomJS +from remote.webdriver import WebDriver as Remote +from common.desired_capabilities import DesiredCapabilities +from common.action_chains import ActionChains +from common.touch_actions import TouchActions +from common.proxy import Proxy + +__version__ = '2.28.0' diff --git a/selenium/webdriver/chrome/__init__.py b/selenium/webdriver/chrome/__init__.py new file mode 100644 index 0000000..0266e8a --- /dev/null +++ b/selenium/webdriver/chrome/__init__.py @@ -0,0 +1,14 @@ +# 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/selenium/webdriver/chrome/options.py b/selenium/webdriver/chrome/options.py new file mode 100644 index 0000000..102ecb8 --- /dev/null +++ b/selenium/webdriver/chrome/options.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# +# Copyright 2012 Webdriver_name committers +# Copyright 2012 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 os +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +import base64 + + +class Options(object): + + def __init__(self): + self._binary_location = '' + self._arguments = [] + self._extension_files = [] + self._experimental_options = {} + + @property + def binary_location(self): + """ + Returns the location of the binary otherwise an empty string + """ + return self._binary_location + + @binary_location.setter + def binary_location(self, value): + """ + Allows you to set where the chromium binary lives + + :Args: + - value: path to the Chromium binary + """ + self._binary_location = value + + @property + def arguments(self): + """ + Returns a list of arguments needed for the browser + """ + return self._arguments + + def add_argument(self, argument): + """ + Adds an argument to the list + + :Args: + - Sets the arguments + """ + if argument: + self._arguments.append(argument) + else: + raise ValueError("argument can not be null") + + @property + def extensions(self): + """ + Returns a list of encoded extensions that will be loaded into chrome + + """ + encoded_extensions = [] + for ext in self._extension_files: + file_ = open(ext) + # Should not use base64.encodestring() which inserts newlines every + # 76 characters (per RFC 1521). Chromedriver has to remove those + # unnecessary newlines before decoding, causing performance hit. + encoded_extensions.append(base64.b64encode(file_.read())) + + file_.close() + return encoded_extensions + + def add_extension(self, extension): + """ + Adds the path to the extension to a list that will be used to extract it + to the ChromeDriver + + :Args: + - extension: path to the *.crx file + """ + if extension: + if os.path.exists(extension): + self._extension_files.append(extension) + else: + raise IOError("Path to the extension doesn't exist") + else: + raise ValueError("argument can not be null") + + @property + def experimental_options(self): + """ + Returns a dictionary of experimental options for chrome. + """ + return self._experimental_options + + def add_experimental_option(self, name, value): + """ + Adds an experimental option which is passed to chrome. + + Args: + name: The experimental option name. + value: The option value. + """ + self._experimental_options[name] = value + + def to_capabilities(self): + """ + Creates a capabilities with all the options that have been set and + + returns a dictionary with everything + """ + chrome = DesiredCapabilities.CHROME + + chrome_options = self.experimental_options.copy() + chrome_options["extensions"] = self.extensions + chrome_options["binary"] = self.binary_location + chrome_options["args"] = self.arguments + + chrome["chromeOptions"] = chrome_options + + return chrome diff --git a/selenium/webdriver/chrome/service.py b/selenium/webdriver/chrome/service.py new file mode 100644 index 0000000..a611b8d --- /dev/null +++ b/selenium/webdriver/chrome/service.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 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 subprocess +from subprocess import PIPE +import time + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the ChromeDriver + """ + + def __init__(self, executable_path, port=0, service_args=None, log_path=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the ChromeDriver + - port : Port the service is running on + - service_args : List of args to pass to the chromedriver service + - log_path : Path for the chromedriver service to log to""" + + self.port = port + self.path = executable_path + self.service_args = service_args or [] + if log_path: + self.service_args.append('--log-path=%s' % log_path) + if self.port == 0: + self.port = utils.free_port() + + def start(self): + """ + Starts the ChromeDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen([ + self.path, + "--port=%d" % self.port] + + self.service_args, stdout=PIPE, stderr=PIPE) + except: + raise WebDriverException( + "ChromeDriver executable needs to be available in the path. \ + Please download from http://code.google.com/p/selenium/downloads/list\ + and read up at http://code.google.com/p/selenium/wiki/ChromeDriver") + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the ChromeDriver") + + @property + def service_url(self): + """ + Gets the url of the ChromeDriver Service + """ + return "http://localhost:%d" % self.port + + def stop(self): + """ + Tells the ChromeDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to die! + import urllib2 + urllib2.urlopen("http://127.0.0.1:%d/shutdown" % self.port) + count = 0 + while utils.is_connectable(self.port): + if count == 30: + break + count += 1 + time.sleep(1) + + #Tell the Server to properly die in case + try: + if self.process: + self.process.kill() + self.process.wait() + except WindowsError: + # kill may not be available under windows environment + pass diff --git a/selenium/webdriver/chrome/webdriver.py b/selenium/webdriver/chrome/webdriver.py new file mode 100644 index 0000000..fdb5b6a --- /dev/null +++ b/selenium/webdriver/chrome/webdriver.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 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 base64 +import httplib +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.common.exceptions import WebDriverException +from service import Service +from options import Options + +class WebDriver(RemoteWebDriver): + """ + Controls the ChromeDriver and allows you to drive the browser. + + You will need to download the ChromeDriver executable from + http://code.google.com/p/chromedriver/downloads/list + """ + + def __init__(self, executable_path="chromedriver", port=0, + chrome_options=None, service_args=None, + desired_capabilities=None, service_log_path=None): + """ + Creates a new instance of the chrome driver. + + Starts the service and then creates new instance of chrome driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with non-browser specific + capabilities only, such as "proxy" or "loggingPref". + - chrome_options: this takes an instance of ChromeOptions + """ + if chrome_options is None: + options = Options() + else: + options = chrome_options + + if desired_capabilities is not None: + desired_capabilities.update(options.to_capabilities()) + else: + desired_capabilities = options.to_capabilities() + + self.service = Service(executable_path, port=port, + service_args=service_args, log_path=service_log_path) + self.service.start() + + try: + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + except: + self.quit() + raise + + def quit(self): + """ + Closes the browser and shuts down the ChromeDriver executable + that is started when starting the ChromeDriver + """ + try: + RemoteWebDriver.quit(self) + except: + # We don't care about the message because something probably has gone wrong + pass + finally: + self.service.stop() diff --git a/selenium/webdriver/common/__init__.py b/selenium/webdriver/common/__init__.py new file mode 100644 index 0000000..f042f9d --- /dev/null +++ b/selenium/webdriver/common/__init__.py @@ -0,0 +1,14 @@ +# 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/selenium/webdriver/common/action_chains.py b/selenium/webdriver/common/action_chains.py new file mode 100644 index 0000000..acea8ac --- /dev/null +++ b/selenium/webdriver/common/action_chains.py @@ -0,0 +1,253 @@ +# Copyright 2011 WebDriver committers +# Copyright 2011 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 ActionChains implementation +""" +from selenium.webdriver.remote.command import Command +from selenium.webdriver.common.keys import Keys + +class ActionChains(object): + """ + Generate user actions. + All actions are stored in the ActionChains object. Call perform() to fire + stored actions. + """ + + def __init__(self, driver): + """ + Creates a new ActionChains. + + :Args: + - driver: The WebDriver instance which performs user actions. + """ + self._driver = driver + self._actions = [] + + def perform(self): + """ + Performs all stored actions. + """ + for action in self._actions: + action() + + def click(self, on_element=None): + """ + Clicks an element. + + :Args: + - on_element: The element to click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.CLICK, {'button': 0})) + return self + + def click_and_hold(self, on_element=None): + """ + Holds down the left mouse button on an element. + + :Args: + - on_element: The element to mouse down. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.MOUSE_DOWN, {})) + return self + + def context_click(self, on_element=None): + """ + Performs a context-click (right click) on an element. + + :Args: + - on_element: The element to context-click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.CLICK, {'button': 2})) + return self + + def double_click(self, on_element=None): + """ + Double-clicks an element. + + :Args: + - on_element: The element to double-click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.DOUBLE_CLICK, {})) + return self + + def drag_and_drop(self, source, target): + """Holds down the left mouse button on the source element, + then moves to the target element and releases the mouse button. + + :Args: + - source: The element to mouse down. + - target: The element to mouse up. + """ + self.click_and_hold(source) + self.release(target) + return self + + def drag_and_drop_by_offset(self, source, xoffset, yoffset): + """ + Holds down the left mouse button on the source element, + then moves to the target element and releases the mouse button. + + :Args: + - source: The element to mouse down. + - xoffset: X offset to move to. + - yoffset: Y offset to move to. + """ + self.click_and_hold(source) + self.move_by_offset(xoffset, yoffset) + self.release(source) + return self + + def key_down(self, value, element=None): + """Sends a key press only, without releasing it. + Should only be used with modifier keys (Control, Alt and Shift). + + :Args: + - key: The modifier key to send. Values are defined in Keys class. + - target: The element to send keys. + If None, sends a key to current focused element. + """ + typing = [] + for val in value: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = str(val) + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + + if element: self.click(element) + self._actions.append(lambda: + self._driver.execute(Command.SEND_KEYS_TO_ACTIVE_ELEMENT, { + "value": typing })) + return self + + def key_up(self, value, element=None): + """ + Releases a modifier key. + + :Args: + - key: The modifier key to send. Values are defined in Keys class. + - target: The element to send keys. + If None, sends a key to current focused element. + """ + typing = [] + for val in value: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = str(val) + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + + if element: self.click(element) + self._actions.append(lambda: + self._driver.execute(Command.SEND_KEYS_TO_ACTIVE_ELEMENT, { + "value": typing })) + return self + + def move_by_offset(self, xoffset, yoffset): + """ + Moving the mouse to an offset from current mouse position. + + :Args: + - xoffset: X offset to move to. + - yoffset: Y offset to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, { + 'xoffset': xoffset, + 'yoffset': yoffset})) + return self + + def move_to_element(self, to_element): + """ + Moving the mouse to the middle of an element. + + :Args: + - to_element: The element to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, {'element': to_element.id})) + return self + + def move_to_element_with_offset(self, to_element, xoffset, yoffset): + """ + Move the mouse by an offset of the specificed element. + Offsets are relative to the top-left corner of the element. + + :Args: + - to_element: The element to move to. + - xoffset: X offset to move to. + - yoffset: Y offset to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, { + 'element': to_element.id, + 'xoffset': xoffset, + 'yoffset': yoffset})) + return self + + def release(self, on_element=None): + """ + Releasing a held mouse button. + + :Args: + - on_element: The element to mouse up. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.MOUSE_UP, {})) + return self + + def send_keys(self, *keys_to_send): + """Sends keys to current focused element. + + :Args: + - keys_to_send: The keys to send. + """ + self._actions.append(lambda: + self._driver.switch_to_active_element().send_keys(*keys_to_send)) + return self + + def send_keys_to_element(self, element, *keys_to_send): + """ + Sends keys to an element. + + :Args: + - element: The element to send keys. + - keys_to_send: The keys to send. + """ + self._actions.append(lambda: + element.send_keys(*keys_to_send)) + return self diff --git a/selenium/webdriver/common/alert.py b/selenium/webdriver/common/alert.py new file mode 100644 index 0000000..711f09c --- /dev/null +++ b/selenium/webdriver/common/alert.py @@ -0,0 +1,39 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-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. + +from selenium.webdriver.remote.command import Command + + +class Alert(object): + + def __init__(self, driver): + self.driver = driver + + @property + def text(self): + """ Gets the text of the Alert """ + return self.driver.execute(Command.GET_ALERT_TEXT)["value"] + + def dismiss(self): + """ Dismisses the alert available """ + self.driver.execute(Command.DISMISS_ALERT) + + def accept(self): + """ Accepts the alert available """ + self.driver.execute(Command.ACCEPT_ALERT) + + def send_keys(self, keysToSend): + """ Send Keys to the Alert """ + self.driver.execute(Command.SET_ALERT_VALUE, {'text': keysToSend}) diff --git a/selenium/webdriver/common/by.py b/selenium/webdriver/common/by.py new file mode 100644 index 0000000..b54ca72 --- /dev/null +++ b/selenium/webdriver/common/by.py @@ -0,0 +1,25 @@ +# 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. + + +class By(object): + ID = "id" + XPATH = "xpath" + LINK_TEXT = "link text" + PARTIAL_LINK_TEXT = "partial link text" + NAME = "name" + TAG_NAME = "tag name" + CLASS_NAME = "class name" + CSS_SELECTOR = "css selector" diff --git a/selenium/webdriver/common/desired_capabilities.py b/selenium/webdriver/common/desired_capabilities.py new file mode 100644 index 0000000..8b3ffc6 --- /dev/null +++ b/selenium/webdriver/common/desired_capabilities.py @@ -0,0 +1,93 @@ +# 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. + +class DesiredCapabilities(object): + + FIREFOX = { + "browserName": "firefox", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + INTERNETEXPLORER = { + "browserName": "internet explorer", + "version": "", + "platform": "WINDOWS", + "javascriptEnabled": True, + } + + CHROME = { + "browserName": "chrome", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + OPERA = { + "browserName": "opera", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + SAFARI = { + "browserName": "safari", + "version": "5", + "platform": "MAC", + "javascriptEnabled": True, + } + + HTMLUNIT = { + "browserName": "htmlunit", + "version": "", + "platform": "ANY", + } + + HTMLUNITWITHJS = { + "browserName": "htmlunit", + "version": "firefox", + "platform": "ANY", + "javascriptEnabled": True, + } + + IPHONE = { + "browserName": "iPhone", + "version": "", + "platform": "MAC", + "javascriptEnabled": True, + } + + IPAD = { + "browserName": "iPad", + "version": "", + "platform": "MAC", + "javascriptEnabled": True, + } + + ANDROID = { + "browserName": "android", + "version": "", + "platform": "ANDROID", + "javascriptEnabled": True, + } + + PHANTOMJS = { + "browserName":"phantomjs", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + diff --git a/selenium/webdriver/common/html5/__init__.py b/selenium/webdriver/common/html5/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/selenium/webdriver/common/html5/application_cache.py b/selenium/webdriver/common/html5/application_cache.py new file mode 100644 index 0000000..4c5986e --- /dev/null +++ b/selenium/webdriver/common/html5/application_cache.py @@ -0,0 +1,17 @@ +from selenium.webdriver.remote.command import Command + +class ApplicationCache(object): + + UNCACHED = 0 + IDLE = 1 + CHECKING = 2 + DOWNLOADING = 3 + UPDATE_READY = 4 + OBSOLETE = 5 + + def __init__(self, driver): + self.driver = driver + + @property + def status(self): + return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value'] diff --git a/selenium/webdriver/common/keys.py b/selenium/webdriver/common/keys.py new file mode 100644 index 0000000..c136813 --- /dev/null +++ b/selenium/webdriver/common/keys.py @@ -0,0 +1,84 @@ +# copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License Version 2.0 = uthe "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 Keys(object): + + NULL = u'\ue000' + CANCEL = u'\ue001' # ^break + HELP = u'\ue002' + BACK_SPACE = u'\ue003' + TAB = u'\ue004' + CLEAR = u'\ue005' + RETURN = u'\ue006' + ENTER = u'\ue007' + SHIFT = u'\ue008' + LEFT_SHIFT = u'\ue008' # alias + CONTROL = u'\ue009' + LEFT_CONTROL = u'\ue009' # alias + ALT = u'\ue00a' + LEFT_ALT = u'\ue00a' # alias + PAUSE = u'\ue00b' + ESCAPE = u'\ue00c' + SPACE = u'\ue00d' + PAGE_UP = u'\ue00e' + PAGE_DOWN = u'\ue00f' + END = u'\ue010' + HOME = u'\ue011' + LEFT = u'\ue012' + ARROW_LEFT = u'\ue012' # alias + UP = u'\ue013' + ARROW_UP = u'\ue013' # alias + RIGHT = u'\ue014' + ARROW_RIGHT = u'\ue014' # alias + DOWN = u'\ue015' + ARROW_DOWN = u'\ue015' # alias + INSERT = u'\ue016' + DELETE = u'\ue017' + SEMICOLON = u'\ue018' + EQUALS = u'\ue019' + + NUMPAD0 = u'\ue01a' # numbe pad keys + NUMPAD1 = u'\ue01b' + NUMPAD2 = u'\ue01c' + NUMPAD3 = u'\ue01d' + NUMPAD4 = u'\ue01e' + NUMPAD5 = u'\ue01f' + NUMPAD6 = u'\ue020' + NUMPAD7 = u'\ue021' + NUMPAD8 = u'\ue022' + NUMPAD9 = u'\ue023' + MULTIPLY = u'\ue024' + ADD = u'\ue025' + SEPARATOR = u'\ue026' + SUBTRACT = u'\ue027' + DECIMAL = u'\ue028' + DIVIDE = u'\ue029' + + F1 = u'\ue031' # function keys + F2 = u'\ue032' + F3 = u'\ue033' + F4 = u'\ue034' + F5 = u'\ue035' + F6 = u'\ue036' + F7 = u'\ue037' + F8 = u'\ue038' + F9 = u'\ue039' + F10 = u'\ue03a' + F11 = u'\ue03b' + F12 = u'\ue03c' + + META = u'\ue03d' + COMMAND = u'\ue03d' diff --git a/selenium/webdriver/common/proxy.py b/selenium/webdriver/common/proxy.py new file mode 100644 index 0000000..6b8a0d9 --- /dev/null +++ b/selenium/webdriver/common/proxy.py @@ -0,0 +1,136 @@ +class ProxyTypeFactory: + @staticmethod + def make(ff_value, string): + return {'ff_value': ff_value, 'string': string} + +class ProxyType: + + DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows). + MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy). + PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL. + RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used. + AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD). + SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux). + UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') + + +class Proxy(object): + + proxyType = ProxyType.UNSPECIFIED + autodetect = False + ftpProxy = '' + httpProxy = '' + noProxy = '' + proxyAutoconfigUrl = '' + sslProxy = '' + + def __init__(self, raw=None): + if raw is not None: + if raw.has_key('proxyType') and raw['proxyType'] is not None: + self.proxy_type = raw['proxyType'] + if raw.has_key('ftpProxy') and raw['ftpProxy'] is not None: + self.ftp_proxy = raw['ftpProxy'] + if raw.has_key('httpProxy') and raw['httpProxy'] is not None: + self.http_proxy = raw['httpProxy'] + if raw.has_key('noProxy') and raw['noProxy'] is not None: + self.no_proxy = raw['noProxy'] + if raw.has_key('proxyAutoconfigUrl') and raw['proxyAutoconfigUrl'] is not None: + self.proxy_autoconfig_url = raw['proxyAutoconfigUrl'] + if raw.has_key('sslProxy') and raw['sslProxy'] is not None: + self.sslProxy = raw['sslProxy'] + if raw.has_key('autodetect') and raw['autodetect'] is not None: + self.auto_detect = raw['autodetect'] + + @property + def proxy_type(self): + return self.proxyType + + @proxy_type.setter + def proxy_type(self, value): + self._verify_proxy_type_compatilibily(ProxyType.AUTODETECT) + self.proxyType = value + + @property + def auto_detect(self): + return self.autodetect + + @auto_detect.setter + def auto_detect(self, value): + if isinstance(value, bool): + if self.autodetect is not value: + self._verify_proxy_type_compatilibily(ProxyType.AUTODETECT) + self.proxyType = ProxyType.AUTODETECT + self.autodetect = value + else: + raise ValueError("value needs to be a boolean") + + @property + def ftp_proxy(self): + return self.ftpProxy + + @ftp_proxy.setter + def ftp_proxy(self, value): + self._verify_proxy_type_compatilibily(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.ftpProxy = value + + @property + def http_proxy(self): + return self.httpProxy + + @http_proxy.setter + def http_proxy(self, value): + self._verify_proxy_type_compatilibily(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.httpProxy = value + + @property + def no_proxy(self): + return self.noProxy + + @no_proxy.setter + def no_proxy(self, value): + self._verify_proxy_type_compatilibily(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.noProxy = value + + @property + def proxy_autoconfig_url(self): + return self.proxyAutoconfigUrl + + @proxy_autoconfig_url.setter + def proxy_autoconfig_url(self, value): + self._verify_proxy_type_compatilibily(ProxyType.PAC) + self.proxyType = ProxyType.PAC + self.proxyAutoconfigUrl = value + + @property + def ssl_proxy(self): + return self.sslProxy + + @ssl_proxy.setter + def ssl_proxy(self, value): + self._verify_proxy_type_compatilibily(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.sslProxy = value + + def _verify_proxy_type_compatilibily(self, compatibleProxy): + if self.proxyType != ProxyType.UNSPECIFIED and self.proxyType != compatibleProxy: + raise Exception(" Specified proxy type (%s) not compatible with current setting (%s)" % \ + (compatibleProxy, self.proxyType)) + + + def add_to_capabilities(self, capabilities): + proxy_caps = {} + proxy_caps['proxyType'] = self.proxyType['string'] + if self.autodetect: + proxy_caps['autodetect'] = self.autodetect + if self.ftpProxy: + proxy_caps['ftpProxy'] = self.ftpProxy + if self.httpProxy: + proxy_caps['httpProxy'] = self.httpProxy + if self.proxyAutoconfigUrl: + proxy_caps['proxyAutoconfigUrl'] = self.proxyAutoconfigUrl + if self.sslProxy: + proxy_caps['sslProxy'] = self.sslProxy + capabilities['proxy'] = proxy_caps diff --git a/selenium/webdriver/common/touch_actions.py b/selenium/webdriver/common/touch_actions.py new file mode 100644 index 0000000..257e050 --- /dev/null +++ b/selenium/webdriver/common/touch_actions.py @@ -0,0 +1,165 @@ +"""" +Touch Actions implementation +""" + +from selenium.webdriver.remote.command import Command + +class TouchActions(object): + """ + Generate touch actions. Works like ActionChains; actions are stored in the + TouchActions object and are fired with perform(). + """ + + def __init__(self, driver): + """ + Creates a new TouchActions object. + + Args: + -driver: The WebDriver instance, which must be touchscreen enabled. + """ + self._driver = driver + self._actions = [] + + def perform(self): + """ + Performs all stored actions. + """ + for action in self._actions: + action() + + def tap(self, on_element): + """ + Taps on a given element. + + Args: + -element: The element to tap. + """ + self._actions.append(lambda: + self._driver.execute(Command.SINGLE_TAP, {'element': on_element.id})) + return self + + def double_tap(self, on_element): + """ + Double taps on a given element. + + Args: + -element: The element to tap. + """ + self._actions.append(lambda: + self._driver.execute(Command.DOUBLE_TAP, {'element': on_element.id})) + return self + + def tap_and_hold(self, xcoord, ycoord): + """ + Tap and hold a given element. + + Args: + -xcoord: X Coordinates. + -ycoord: Y Coordinates. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_DOWN, { + 'x': xcoord, + 'y': ycoord})) + return self + + def move(self, xcoord, ycoord): + """ + Move held tap to specified location. + + Args: + -xcoord: X Coordinates. + -ycoord: Y Coordinates. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_MOVE, { + 'x': xcoord, + 'y': ycoord})) + return self + + def release(self, xcoord, ycoord): + """ + Release previously issued tap and hold command, at specified location. + + Args: + -xcoord: X Coordinates. + -ycoord: Y Coordinates. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_UP, { + 'x': xcoord, + 'y': ycoord})) + return self + + def scroll(self, xoffset, yoffset): + """ + Touch and scroll, moving by xoffset and yoffset. + + Args: + -xoffset: X offset to move to. + -yoffset: Y offset to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_SCROLL, { + 'xoffset': xoffset, + 'yoffset': yoffset})) + return self + + def scroll_from_element(self, on_element, xoffset, yoffset): + """ + Touch and scroll starting at on_element, moving by xoffset and yoffset. + + Args: + -on_element: Element where scroll starts. + -xoffset: X offset to move to. + -yoffset: Y offset to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_SCROLL, { + 'element': on_element.id, + 'xoffset': xoffset, + 'yoffset': yoffset})) + return self + + def long_press(self, on_element): + """ + Long press on an element. + + Args: + -on_element: The element to long press. + """ + self._actions.append(lambda: + self._driver.execute(Command.LONG_PRESS, {'element': on_element.id})) + return self + + def flick(self, xspeed, yspeed): + """ + Flicks, starting anywhere on the screen. + + Args: + -xspeed: The X speed in pixels per second. + -yspeed: The Y speed in pixels per second. + """ + self._actions.append(lambda: + self._driver.execute(Command.FLICK, { + 'xSpeed': xspeed, + 'ySpeed': yspeed})) + return self + + def flick_element(self, on_element, xoffset, yoffset, speed): + """ + Flick starting at on_element, and moving by the xoffset and yoffset. + + Args: + -on_element: Flick will start at center of element. + -xoffset: X offset to flick to. + -yoffset: Y offset to flick to. + -speed: Pixels per second to flick. + """ + self._actions.append(lambda: + self._driver.execute(Command.FLICK, { + 'element': on_element.id, + 'xoffset': xoffset, + 'yoffset': yoffset, + 'speed': speed})) + return self diff --git a/selenium/webdriver/common/utils.py b/selenium/webdriver/common/utils.py new file mode 100644 index 0000000..71bf8b2 --- /dev/null +++ b/selenium/webdriver/common/utils.py @@ -0,0 +1,46 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 socket + + +def free_port(): + free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + free_socket.bind(('127.0.0.1', 0)) + free_socket.listen(5) + port = free_socket.getsockname()[1] + free_socket.close() + return port + +def is_connectable(port): + """Trys to connect to the server to see if it is running.""" + try: + socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_.settimeout(1) + socket_.connect(("localhost", port)) + socket_.close() + return True + except socket.error: + return False + +def is_url_connectable(port): + import urllib2 + try: + res = urllib2.urlopen("http://localhost:%s/status" % port) + if res.getcode() == 200: + return True + else: + return False + except: + return False diff --git a/selenium/webdriver/firefox/__init__.py b/selenium/webdriver/firefox/__init__.py new file mode 100644 index 0000000..a8bb36b --- /dev/null +++ b/selenium/webdriver/firefox/__init__.py @@ -0,0 +1,14 @@ +# 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. diff --git a/selenium/webdriver/firefox/extension_connection.py b/selenium/webdriver/firefox/extension_connection.py new file mode 100644 index 0000000..76f7382 --- /dev/null +++ b/selenium/webdriver/firefox/extension_connection.py @@ -0,0 +1,79 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 time + +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common import utils +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.remote_connection import RemoteConnection +from selenium.webdriver.firefox.firefox_binary import FirefoxBinary + + +LOGGER = logging.getLogger(__name__) +PORT = 0 # +HOST = None +_URL = "" +class ExtensionConnection(RemoteConnection): + def __init__(self, host, firefox_profile, firefox_binary=None, timeout=30): + self.profile = firefox_profile + self.binary = firefox_binary + HOST = host + if self.binary is None: + self.binary = FirefoxBinary() + + if HOST is None: + HOST = "127.0.0.1" + + PORT = utils.free_port() + self.profile.port = PORT + self.profile.update_preferences() + + self.profile.add_extension() + + self.binary.launch_browser(self.profile) + _URL = "http://%s:%d/hub" % (HOST, PORT) + RemoteConnection.__init__( + self, _URL) + + def quit(self, sessionId=None): + self.execute(Command.QUIT, {'sessionId':sessionId}) + while self.is_connectable(): + LOGGER.info("waiting to quit") + time.sleep(1) + + def connect(self): + """Connects to the extension and retrieves the session id.""" + return self.execute(Command.NEW_SESSION, {'desiredCapabilities': DesiredCapabilities.FIREFOX}) + + @classmethod + def connect_and_quit(self): + """Connects to an running browser and quit immediately.""" + self._request('%s/extensions/firefox/quit' % _URL) + + @classmethod + def is_connectable(self): + """Trys to connect to the extension but do not retrieve context.""" + utils.is_connectable(self.port) + +class ExtensionConnectionError(Exception): + """An internal error occurred int the extension. + + Might be caused by bad input or bugs in webdriver + """ + pass + + diff --git a/selenium/webdriver/firefox/firefox_binary.py b/selenium/webdriver/firefox/firefox_binary.py new file mode 100644 index 0000000..1ac40ee --- /dev/null +++ b/selenium/webdriver/firefox/firefox_binary.py @@ -0,0 +1,175 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 os +import platform +from subprocess import Popen, PIPE, STDOUT +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils +import time + + +class FirefoxBinary(object): + + NO_FOCUS_LIBRARY_NAME = "x_ignore_nofocus.so" + + def __init__(self, firefox_path=None): + self._start_cmd = firefox_path + self.command_line = None + if self._start_cmd is None: + self._start_cmd = self._get_firefox_start_cmd() + # Rather than modifying the environment of the calling Python process + # copy it and modify as needed. + self._firefox_env = os.environ.copy() + + def add_command_line_options(self, *args): + self.command_line = args + + def launch_browser(self, profile): + """Launches the browser for the given profile name. + It is assumed the profile already exists. + """ + self.profile = profile + + self._start_from_profile_path(self.profile.path) + self._wait_until_connectable() + + def kill(self): + """Kill the browser. + + This is useful when the browser is stuck. + """ + if self.process: + self.process.kill() + self.process.wait() + + def _start_from_profile_path(self, path): + self._firefox_env["XRE_PROFILE_PATH"] = path + self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1" + self._firefox_env["MOZ_NO_REMOTE"] = "1" + self._firefox_env["NO_EM_RESTART"] = "1" + + if platform.system().lower() == 'linux': + self._modify_link_library_path() + command = [self._start_cmd, "-silent"] + if self.command_line is not None: + for cli in self.command_line: + command.append(cli) + + Popen(command, stdout=PIPE, stderr=STDOUT, + env=self._firefox_env).communicate() + command[1] = '-foreground' + self.process = Popen( + command, stdout=PIPE, stderr=STDOUT, + env=self._firefox_env) + + def _get_firefox_output(self): + return self.process.communicate()[0] + + def _wait_until_connectable(self): + """Blocks until the extension is connectable in the firefox.""" + count = 0 + while not utils.is_connectable(self.profile.port): + if self.process.poll() is not None: + # Browser has exited + raise WebDriverException("The browser appears to have exited " + "before we could connect. The output was: %s" % + self._get_firefox_output()) + if count == 30: + self.kill() + raise WebDriverException("Can't load the profile. Profile " + "Dir: %s Firefox output: %s" % ( + self.profile.path, self._get_firefox_output())) + count += 1 + time.sleep(1) + return True + + def _find_exe_in_registry(self): + from _winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE + import shlex + keys = ( + r"SOFTWARE\Classes\FirefoxHTML\shell\open\command", + r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command" + ) + command = "" + for path in keys: + try: + key = OpenKey(HKEY_LOCAL_MACHINE, path) + command = QueryValue(key, "") + break + except WindowsError: + pass + else: + return "" + + return shlex.split(command)[0] + + def _get_firefox_start_cmd(self): + """Return the command to start firefox.""" + start_cmd = "" + if platform.system() == "Darwin": + start_cmd = ("/Applications/Firefox.app/Contents/MacOS/firefox-bin") + elif platform.system() == "Windows": + start_cmd = (self._find_exe_in_registry() or + self._default_windows_location()) + elif platform.system() == 'Java' and os._name == 'nt': + start_cmd = self._default_windows_location() + else: + # Maybe iceweasel (Debian) is another candidate... + for ffname in ["firefox2", "firefox", "firefox-3.0", "firefox-4.0"]: + start_cmd = self.which(ffname) + if start_cmd is not None: + break + return start_cmd + + def _default_windows_location(self): + program_files = os.getenv("PROGRAMFILES", r"\Program Files") + return os.path.join(program_files, "Mozilla Firefox\\firefox.exe") + + def _modify_link_library_path(self): + existing_ld_lib_path = os.environ.get('LD_LIBRARY_PATH', '') + + new_ld_lib_path = self._extract_and_check( + self.profile, self.NO_FOCUS_LIBRARY_NAME, "x86", "amd64") + + new_ld_lib_path += existing_ld_lib_path + + self._firefox_env["LD_LIBRARY_PATH"] = new_ld_lib_path + self._firefox_env['LD_PRELOAD'] = self.NO_FOCUS_LIBRARY_NAME + + def _extract_and_check(self, profile, no_focus_so_name, x86, amd64): + + paths = [x86, amd64] + built_path = "" + for path in paths: + library_path = os.path.join(profile.path, path) + os.makedirs(library_path) + import shutil + shutil.copy(os.path.join(os.path.dirname(__file__), path, + self.NO_FOCUS_LIBRARY_NAME), + library_path) + built_path += library_path + ":" + + return built_path + + def which(self, fname): + """Returns the fully qualified path by searching Path of the given + name""" + for pe in os.environ['PATH'].split(os.pathsep): + checkname = os.path.join(pe, fname) + if os.access(checkname, os.X_OK) and not os.path.isdir(checkname): + return checkname + return None diff --git a/selenium/webdriver/firefox/firefox_profile.py b/selenium/webdriver/firefox/firefox_profile.py new file mode 100644 index 0000000..0102c43 --- /dev/null +++ b/selenium/webdriver/firefox/firefox_profile.py @@ -0,0 +1,376 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 __future__ import with_statement + +import base64 +import copy +import os +import re +import shutil +import tempfile +import zipfile +from cStringIO import StringIO +from xml.dom import minidom +from distutils import dir_util +from selenium.webdriver.common.proxy import ProxyType +from selenium.common.exceptions import WebDriverException + + +WEBDRIVER_EXT = "webdriver.xpi" +EXTENSION_NAME = "fxdriver@googlecode.com" + +class FirefoxProfile(object): + + ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE" + DEFAULT_PREFERENCES = { + "app.update.auto": "false", + "app.update.enabled": "false", + "browser.download.manager.showWhenStarting": "false", + "browser.EULA.override": "true", + "browser.EULA.3.accepted": "true", + "browser.link.open_external": "2", + "browser.link.open_newwindow": "2", + "browser.offline": "false", + "browser.safebrowsing.enabled": "false", + "browser.search.update": "false", + "extensions.blocklist.enabled": "false", + "browser.sessionstore.resume_from_crash": "false", + "browser.shell.checkDefaultBrowser": "false", + "browser.tabs.warnOnClose": "false", + "browser.tabs.warnOnOpen": "false", + "browser.startup.page": "0", + "browser.safebrowsing.malware.enabled": "false", + "startup.homepage_welcome_url": "\"about:blank\"", + "devtools.errorconsole.enabled": "true", + "dom.disable_open_during_load": "false", + "extensions.autoDisableScopes" : 10, + "extensions.logging.enabled": "true", + "extensions.update.enabled": "false", + "extensions.update.notifyUser": "false", + "network.manage-offline-status": "false", + "network.http.max-connections-per-server": "10", + "network.http.phishy-userpass-length": "255", + "offline-apps.allow_by_default": "true", + "prompts.tab_modal.enabled": "false", + "security.fileuri.origin_policy": "3", + "security.fileuri.strict_origin_policy": "false", + "security.warn_entering_secure": "false", + "security.warn_entering_secure.show_once": "false", + "security.warn_entering_weak": "false", + "security.warn_entering_weak.show_once": "false", + "security.warn_leaving_secure": "false", + "security.warn_leaving_secure.show_once": "false", + "security.warn_submit_insecure": "false", + "security.warn_viewing_mixed": "false", + "security.warn_viewing_mixed.show_once": "false", + "signon.rememberSignons": "false", + "toolkit.networkmanager.disable": "true", + "toolkit.telemetry.enabled": "false", + "toolkit.telemetry.prompted": "2", + "toolkit.telemetry.rejected": "true", + "javascript.options.showInConsole": "true", + "browser.dom.window.dump.enabled": "true", + "webdriver_accept_untrusted_certs": "true", + "webdriver_enable_native_events": "true", + "webdriver_assume_untrusted_issuer": "true", + "dom.max_script_run_time": "30", + } + + def __init__(self,profile_directory=None): + """ + Initialises a new instance of a Firefox Profile + + :args: + - profile_directory: Directory of profile that you want to use. + This defaults to None and will create a new + directory when object is created. + """ + self.default_preferences = copy.deepcopy( + FirefoxProfile.DEFAULT_PREFERENCES) + self.profile_dir = profile_directory + self.tempfolder = None + if self.profile_dir is None: + self.profile_dir = self._create_tempfolder() + else: + self.tempfolder = tempfile.mkdtemp() + newprof = os.path.join(self.tempfolder, + "webdriver-py-profilecopy") + shutil.copytree(self.profile_dir, newprof, + ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock")) + self.profile_dir = newprof + self._read_existing_userjs() + self.extensionsDir = os.path.join(self.profile_dir, "extensions") + self.userPrefs = os.path.join(self.profile_dir, "user.js") + + #Public Methods + def set_preference(self, key, value): + """ + sets the preference that we want in the profile. + """ + clean_value = '' + if value is True: + clean_value = 'true' + elif value is False: + clean_value = 'false' + elif isinstance(value, str): + clean_value = '"%s"' % value + elif isinstance(value, unicode): + clean_value = '"%s"' % value + else: + clean_value = str(int(value)) + + self.default_preferences[key] = clean_value + + def add_extension(self, extension=WEBDRIVER_EXT): + self._install_extension(extension) + + def update_preferences(self): + self._write_user_prefs(self.default_preferences) + + #Properties + + @property + def path(self): + """ + Gets the profile directory that is currently being used + """ + return self.profile_dir + + @property + def port(self): + """ + Gets the port that WebDriver is working on + """ + return self._port + + @port.setter + def port(self, port): + """ + Sets the port that WebDriver will be running on + """ + if not isinstance(port, int): + raise WebDriverException("Port needs to be an integer") + self._port = port + self.set_preference("webdriver_firefox_port", self._port) + + @property + def accept_untrusted_certs(self): + return self._santise_pref( + self.default_preferences["webdriver_accept_untrusted_certs"]) + + @accept_untrusted_certs.setter + def accept_untrusted_certs(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + self.set_preference("webdriver_accept_untrusted_certs", value) + + @property + def assume_untrusted_cert_issuer(self): + return self._santise_pref(self.default_preferences["webdriver_assume_untrusted_issuer"]) + + @assume_untrusted_cert_issuer.setter + def assume_untrusted_cert_issuer(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + + self.set_preference("webdriver_assume_untrusted_issuer", value) + + @property + def native_events_enabled(self): + return self._santise_pref(self.default_preferences['webdriver_enable_native_events']) + + @native_events_enabled.setter + def native_events_enabled(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + self.set_preference("webdriver_enable_native_events", value) + + @property + def encoded(self): + """ + A zipped, base64 encoded string of profile directory + for use with remote WebDriver JSON wire protocol + """ + fp = StringIO() + zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) + path_root = len(self.path) + 1 # account for trailing slash + for base, dirs, files in os.walk(self.path): + for fyle in files: + filename = os.path.join(base, fyle) + zipped.write(filename, filename[path_root:]) + zipped.close() + return base64.encodestring(fp.getvalue()) + + def set_proxy(self, proxy): + import warnings + warnings.warn("This method has been deprecated. Please pass in the proxy object to the Driver Object", + DeprecationWarning) + if proxy is None: + raise ValueError("proxy can not be None") + + if proxy.proxy_type is ProxyType.UNSPECIFIED: + return + + self.set_preference("network.proxy.type", proxy.proxy_type['ff_value']) + + if proxy.proxy_type is ProxyType.MANUAL: + self.set_preference("network.proxy.no_proxies_on", proxy.no_proxy) + self._set_manual_proxy_preference("ftp", proxy.ftp_proxy) + self._set_manual_proxy_preference("http", proxy.http_proxy) + self._set_manual_proxy_preference("ssl", proxy.ssl_proxy) + elif proxy.proxy_type is ProxyType.AUTODETECT: + self.set_preference("network.proxy.autoconfig_url", proxy.proxy_autoconfig_url) + + #Private Methods + def _santise_pref(self, item): + if item == 'true': + return True + elif item == 'false': + return False + else: + return item + def _set_manual_proxy_preference(self, key, setting): + if setting is None or setting is '': + return + + host_details = setting.split(":") + self.set_preference("network.proxy.%s" % key, host_details[1][2:]) + if len(host_details) > 1: + self.set_preference("network.proxy.%s_port" % key, int(host_details[2])) + + def _create_tempfolder(self): + """ + Creates a temp folder to store User.js and the extension + """ + return tempfile.mkdtemp() + + def _write_user_prefs(self, user_prefs): + """ + writes the current user prefs dictionary to disk + """ + with open(self.userPrefs, "w") as f: + for key, value in user_prefs.items(): + f.write('user_pref("%s", %s);\n' % (key, value)) + + def _read_existing_userjs(self): + userjs_path = os.path.join(self.profile_dir, 'user.js') + PREF_RE = re.compile(r'user_pref\("(.*)",\s(.*)\)') + try: + with open(userjs_path) as f: + for usr in f: + matches = re.search(PREF_RE, usr) + self.default_preferences[matches.group(1)] = matches.group(2) + except: + # The profile given hasn't had any changes made, i.e no users.js + pass + + def _install_extension(self, addon, unpack=True): + """ + Installs addon from a filepath, url + or directory of addons in the profile. + - path: url, path to .xpi, or directory of addons + - unpack: whether to unpack unless specified otherwise in the install.rdf + """ + if addon == WEBDRIVER_EXT: + addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT) + + tmpdir = None + xpifile = None + if addon.endswith('.xpi'): + tmpdir = tempfile.mkdtemp(suffix = '.' + os.path.split(addon)[-1]) + compressed_file = zipfile.ZipFile(addon, 'r') + for name in compressed_file.namelist(): + if name.endswith('/'): + os.makedirs(os.path.join(tmpdir, name)) + else: + if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))): + os.makedirs(os.path.dirname(os.path.join(tmpdir, name))) + data = compressed_file.read(name) + with open(os.path.join(tmpdir, name), 'wb') as f: + f.write(data) + xpifile = addon + addon = tmpdir + + # determine the addon id + addon_details = self._addon_details(addon) + addon_id = addon_details.get('id') + assert addon_id, 'The addon id could not be found: %s' % addon + + # copy the addon to the profile + extensions_path = os.path.join(self.profile_dir, 'extensions') + addon_path = os.path.join(extensions_path, addon_id) + if not unpack and not addon_details['unpack'] and xpifile: + if not os.path.exists(extensions_path): + os.makedirs(extensions_path) + shutil.copy(xpifile, addon_path + '.xpi') + else: + dir_util.copy_tree(addon, addon_path, preserve_symlinks=1) + + # remove the temporary directory, if any + if tmpdir: + dir_util.remove_tree(tmpdir) + + def _addon_details(self, addon_path): + """ + returns a dictionary of details about the addon + - addon_path : path to the addon directory + Returns: + {'id': u'rainbow@colors.org', # id of the addon + 'version': u'1.4', # version of the addon + 'name': u'Rainbow', # name of the addon + 'unpack': False } # whether to unpack the addon + """ + + # TODO: We don't use the unpack variable yet, but we should: bug 662683 + details = { + 'id': None, + 'name': None, + 'unpack': True, + 'version': None + } + + def get_namespace_id(doc, url): + attributes = doc.documentElement.attributes + namespace = "" + for i in range(attributes.length): + if attributes.item(i).value == url: + if ":" in attributes.item(i).name: + # If the namespace is not the default one remove 'xlmns:' + namespace = attributes.item(i).name.split(':')[1] + ":" + break + return namespace + + def get_text(element): + """Retrieve the text value of a given node""" + rc = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc).strip() + + doc = minidom.parse(os.path.join(addon_path, 'install.rdf')) + + # Get the namespaces abbreviations + em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#") + rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + + description = doc.getElementsByTagName(rdf + "Description").item(0) + for node in description.childNodes: + # Remove the namespace prefix from the tag for comparison + entry = node.nodeName.replace(em, "") + if entry in details.keys(): + details.update({ entry: get_text(node) }) + + return details diff --git a/selenium/webdriver/firefox/webdriver.py b/selenium/webdriver/firefox/webdriver.py new file mode 100644 index 0000000..0dbe5db --- /dev/null +++ b/selenium/webdriver/firefox/webdriver.py @@ -0,0 +1,84 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 base64 +import httplib +import shutil +import sys +import urllib2 +from firefox_binary import FirefoxBinary +from selenium.common.exceptions import ErrorInResponseException +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.firefox.extension_connection import ExtensionConnection +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.remote.webelement import WebElement + +class WebDriver(RemoteWebDriver): + + # There is no native event support on Mac + NATIVE_EVENTS_ALLOWED = sys.platform != "darwin" + + def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30, + capabilities=None, proxy=None): + + self.binary = firefox_binary + self.profile = firefox_profile + + if self.profile is None: + self.profile = FirefoxProfile() + + self.profile.native_events_enabled = self.NATIVE_EVENTS_ALLOWED and self.profile.native_events_enabled + + if self.binary is None: + self.binary = FirefoxBinary() + + if capabilities is None: + capabilities = DesiredCapabilities.FIREFOX + + if proxy is not None: + proxy.add_to_capabilities(capabilities) + + + RemoteWebDriver.__init__(self, + command_executor=ExtensionConnection("127.0.0.1", self.profile, + self.binary, timeout), + desired_capabilities=capabilities) + + def create_web_element(self, element_id): + """Override from RemoteWebDriver to use firefox.WebElement.""" + return WebElement(self, element_id) + + def quit(self): + """Quits the driver and close every associated window.""" + try: + RemoteWebDriver.quit(self) + except httplib.BadStatusLine: + # Happens if Firefox shutsdown before we've read the response from + # the socket. + pass + self.binary.kill() + try: + shutil.rmtree(self.profile.path) + if self.profile.tempfolder is not None: + shutil.rmtree(self.profile.tempfolder) + except Exception, e: + print str(e) + + @property + def firefox_profile(self): + return self.profile diff --git a/selenium/webdriver/ie/__init__.py b/selenium/webdriver/ie/__init__.py new file mode 100644 index 0000000..edbfeeb --- /dev/null +++ b/selenium/webdriver/ie/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-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. diff --git a/selenium/webdriver/ie/service.py b/selenium/webdriver/ie/service.py new file mode 100644 index 0000000..7417781 --- /dev/null +++ b/selenium/webdriver/ie/service.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# +# Copyright 2012 Webdriver_name committers +# Copyright 2012 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 subprocess +from subprocess import PIPE +import time +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the IEDriver + """ + + def __init__(self, executable_path, port=0, host=None, log_level=None, log_file=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the IEDriver + - port : Port the service is running on + - host : IP address the service port is bound + - log_level : Level of logging of service, may be "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE". + Default is "FATAL". + - log_file : Target of logging of service, may be "stdout", "stderr" or file path. + Default is "stdout".""" + + self.port = port + self.path = executable_path + if self.port == 0: + self.port = utils.free_port() + self.host = host + self.log_level = log_level + self.log_file = log_file + + def start(self): + """ + Starts the IEDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + cmd = [self.path, "--port=%d" % self.port] + if self.host is not None: + cmd.append("--host=%s" % self.host) + if self.log_level is not None: + cmd.append("--log-level=%s" % self.log_level) + if self.log_file is not None: + cmd.append("--log-file=%s" % self.log_file) + self.process = subprocess.Popen(cmd, + stdout=PIPE, stderr=PIPE) + except TypeError: + raise + except: + raise WebDriverException( + "IEDriver executable needs to be available in the path. \ + Please download from http://code.google.com/p/selenium/downloads/list\ + and read up at http://code.google.com/p/selenium/wiki/InternetExplorerDriver") + count = 0 + while not utils.is_url_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the IEDriver") + + def stop(self): + """ + Tells the IEDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to die! + import urllib2 + urllib2.urlopen("http://127.0.0.1:%d/shutdown" % self.port) + count = 0 + while utils.is_connectable(self.port): + if count == 30: + break + count += 1 + time.sleep(1) + + #Tell the Server to properly die in case + try: + if self.process: + self.process.kill() + self.process.wait() + except WindowsError: + # kill may not be available under windows environment + pass diff --git a/selenium/webdriver/ie/webdriver.py b/selenium/webdriver/ie/webdriver.py new file mode 100644 index 0000000..f5ffaff --- /dev/null +++ b/selenium/webdriver/ie/webdriver.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-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.webdriver.common import utils +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.command import Command +from selenium.common.exceptions import WebDriverException +import base64 +from service import Service + +DEFAULT_TIMEOUT = 30 +DEFAULT_PORT = 0 +DEFAULT_HOST = None +DEFAULT_LOG_LEVEL = None +DEFAULT_LOG_FILE = None + +class WebDriver(RemoteWebDriver): + + def __init__(self, executable_path='IEDriverServer.exe', + port=DEFAULT_PORT, timeout=DEFAULT_TIMEOUT, host=DEFAULT_HOST, + log_level=DEFAULT_LOG_LEVEL, log_file=DEFAULT_LOG_FILE): + self.port = port + if self.port == 0: + self.port = utils.free_port() + self.host = host + self.log_level = log_level + self.log_file = log_file + + self.iedriver = Service(executable_path, port=self.port, + host=self.host, log_level=self.log_level, log_file=self.log_file) + + self.iedriver.start() + + RemoteWebDriver.__init__( + self, + command_executor='http://localhost:%d' % self.port, + desired_capabilities=DesiredCapabilities.INTERNETEXPLORER) + + def quit(self): + RemoteWebDriver.quit(self) + self.iedriver.stop() diff --git a/selenium/webdriver/opera/__init__.py b/selenium/webdriver/opera/__init__.py new file mode 100644 index 0000000..a8bb36b --- /dev/null +++ b/selenium/webdriver/opera/__init__.py @@ -0,0 +1,14 @@ +# 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. diff --git a/selenium/webdriver/opera/service.py b/selenium/webdriver/opera/service.py new file mode 100644 index 0000000..688b8d8 --- /dev/null +++ b/selenium/webdriver/opera/service.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 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 subprocess +from subprocess import PIPE +import time +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the OperaDriver + """ + + def __init__(self, executable_path, port=0): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the OperaDriver + - port : Port the service is running on """ + + self.port = port + self.path = executable_path + if self.port == 0: + self.port = utils.free_port() + + def start(self): + """ + Starts the OperaDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen(["java", "-jar", self.path, "-port", "%s" % self.port]) + except: + raise WebDriverException( + "OperaDriver executable needs to be available in the path. \ + ") + time.sleep(10) + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the OperaDriver") + + @property + def service_url(self): + """ + Gets the url of the OperaDriver Service + """ + return "http://localhost:%d/wd/hub" % self.port + + def stop(self): + """ + Tells the OperaDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + self.process.kill() + self.process.wait() + diff --git a/selenium/webdriver/opera/webdriver.py b/selenium/webdriver/opera/webdriver.py new file mode 100644 index 0000000..af4883c --- /dev/null +++ b/selenium/webdriver/opera/webdriver.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 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 base64 +import httplib +import os +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from service import Service + +class WebDriver(RemoteWebDriver): + """ + Controls the OperaDriver and allows you to drive the browser. + + """ + + def __init__(self, executable_path=None, port=0, + desired_capabilities=DesiredCapabilities.OPERA): + """ + Creates a new instance of the Opera driver. + + Starts the service and then creates new instance of Opera Driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the + Environment Variable SELENIUM_SERVER_JAR + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Opera switches). + """ + if executable_path is None: + try: + executable_path = os.environ["SELENIUM_SERVER_JAR"] + except: + raise Exception("No executable path given, please add one to Environment Variable \ + 'SELENIUM_SERVER_JAR'") + self.service = Service(executable_path, port=port) + self.service.start() + + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + + def quit(self): + """ + Closes the browser and shuts down the OperaDriver executable + that is started when starting the OperaDriver + """ + try: + RemoteWebDriver.quit(self) + except httplib.BadStatusLine: + pass + finally: + self.service.stop() diff --git a/selenium/webdriver/phantomjs/__init__.py b/selenium/webdriver/phantomjs/__init__.py new file mode 100644 index 0000000..ce6fdad --- /dev/null +++ b/selenium/webdriver/phantomjs/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2012 Software Freedom Conservancy +# +# 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/selenium/webdriver/phantomjs/service.py b/selenium/webdriver/phantomjs/service.py new file mode 100644 index 0000000..8d969c7 --- /dev/null +++ b/selenium/webdriver/phantomjs/service.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# +# Copyright 2012 Software Freedom Conservancy +# +# 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 subprocess +import time + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of PhantomJS / Ghostdriver + """ + + def __init__(self, executable_path, port=0, service_args=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to PhantomJS binary + - port : Port the service is running on + - service_args : A List of other command line options to pass to PhantomJS + """ + + self.port = port + self.path = executable_path + self.service_args= service_args + if self.port == 0: + self.port = utils.free_port() + if self.service_args is None: + self.service_args = [] + self.service_args.insert(0, self.path) + self.service_args.append("--webdriver=%d" % self.port) + self._log = open("ghostdriver.log", 'w') + + def start(self): + """ + Starts PhantomJS with GhostDriver. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen(self.service_args, + stdout=self._log, stderr=self._log) + except Exception, e: + raise WebDriverException("Unable to start phantomjs with ghostdriver.", e) + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to GhostDriver") + + @property + def service_url(self): + """ + Gets the url of the GhostDriver Service + """ + return "http://localhost:%d/wd/hub" % self.port + + def stop(self): + """ + Cleans up the process + """ + if self._log: + self._log.close() + self._log = None + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to properly die in case + try: + if self.process: + self.process.kill() + self.process.wait() + except WindowsError: + # kill may not be available under windows environment + pass diff --git a/selenium/webdriver/phantomjs/webdriver.py b/selenium/webdriver/phantomjs/webdriver.py new file mode 100644 index 0000000..25eca5a --- /dev/null +++ b/selenium/webdriver/phantomjs/webdriver.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# +# Copyright 2012 Software freedom conservancy +# +# 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 base64 +import httplib +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.common.exceptions import WebDriverException +from service import Service + +class WebDriver(RemoteWebDriver): + """ + Wrapper to communicate with PhantomJS through Ghostdriver. + + You will need to follow all the directions here: + https://github.com/detro/ghostdriver + """ + + def __init__(self, executable_path="phantomjs", + port=0, desired_capabilities=DesiredCapabilities.PHANTOMJS): + """ + Creates a new instance of the PhantomJS / Ghostdriver. + + Starts the service and then creates new instance of the driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with non-browser specific + capabilities only, such as "proxy" or "loggingPref". + """ + self.service = Service(executable_path, port=port) + self.service.start() + + try: + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + except: + self.quit() + raise + + def quit(self): + """ + Closes the browser and shuts down the PhantomJS executable + that is started when starting the PhantomJS + """ + try: + RemoteWebDriver.quit(self) + except: + # We don't care about the message because something probably has gone wrong + pass + finally: + self.service.stop() + + def __del__(self): + try: + self.service.stop() + except: + pass diff --git a/selenium/webdriver/remote/__init__.py b/selenium/webdriver/remote/__init__.py new file mode 100644 index 0000000..f042f9d --- /dev/null +++ b/selenium/webdriver/remote/__init__.py @@ -0,0 +1,14 @@ +# 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/selenium/webdriver/remote/command.py b/selenium/webdriver/remote/command.py new file mode 100644 index 0000000..43fe28d --- /dev/null +++ b/selenium/webdriver/remote/command.py @@ -0,0 +1,144 @@ +# 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" + 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" + SEND_KEYS_TO_ACTIVE_ELEMENT = "sendKeysToActiveElement" + SUBMIT_ELEMENT = "submitElement" + UPLOAD_FILE = "uploadFile" + TOGGLE_ELEMENT = "toggleElement" + GET_CURRENT_WINDOW_HANDLE = "getCurrentWindowHandle" + GET_WINDOW_HANDLES = "getWindowHandles" + GET_WINDOW_SIZE = "getWindowSize" + GET_WINDOW_POSITION = "getWindowPosition" + SET_WINDOW_SIZE = "setWindowSize" + SET_WINDOW_POSITION = "setWindowPosition" + 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" + IMPLICIT_WAIT = "implicitlyWait" + EXECUTE_ASYNC_SCRIPT = "executeAsyncScript" + SET_SCRIPT_TIMEOUT = "setScriptTimeout" + SET_TIMEOUTS = "setTimeouts" + MAXIMIZE_WINDOW = "windowMaximize" + + #Alerts + DISMISS_ALERT = "dismissAlert" + ACCEPT_ALERT = "acceptAlert" + SET_ALERT_VALUE = "setAlertValue" + GET_ALERT_TEXT = "getAlertText" + + # Advanced user interactions + CLICK = "mouseClick"; + DOUBLE_CLICK = "mouseDoubleClick"; + MOUSE_DOWN = "mouseButtonDown"; + MOUSE_UP = "mouseButtonUp"; + MOVE_TO = "mouseMoveTo"; + + # Screen Orientation + SET_SCREEN_ORIENTATION = "setScreenOrientation" + GET_SCREEN_ORIENTATION = "getScreenOrientation" + + # Touch Actions + SINGLE_TAP = "touchSingleTap"; + TOUCH_DOWN = "touchDown"; + TOUCH_UP = "touchUp"; + TOUCH_MOVE = "touchMove"; + TOUCH_SCROLL = "touchScroll"; + DOUBLE_TAP = "touchDoubleTap"; + LONG_PRESS = "touchLongPress"; + FLICK = "touchFlick"; + + #HTML 5 + EXECUTE_SQL = "executeSql" + + GET_LOCATION = "getLocation" + SET_LOCATION = "setLocation" + + GET_APP_CACHE = "getAppCache" + GET_APP_CACHE_STATUS = "getAppCacheStatus" + CLEAR_APP_CACHE = "clearAppCache" + + IS_BROWSER_ONLINE = "isBrowserOnline" + SET_BROWSER_ONLINE = "setBrowserOnline" + + GET_LOCAL_STORAGE_ITEM = "getLocalStorageItem" + REMOVE_LOCAL_STORAGE_ITEM = "removeLocalStorageItem" + GET_LOCAL_STORAGE_KEYS = "getLocalStorageKeys" + SET_LOCAL_STORAGE_ITEM = "setLocalStorageItem" + CLEAR_LOCAL_STORAGE = "clearLocalStorage" + GET_LOCAL_STORAGE_SIZE = "getLocalStorageSize" + + GET_SESSION_STORAGE_ITEM= "getSessionStorageItem" + REMOVE_SESSION_STORAGE_ITEM = "removeSessionStorageItem" + GET_SESSION_STORAGE_KEYS = "getSessionStorageKeys" + SET_SESSION_STORAGE_ITEM = "setSessionStorageItem" + CLEAR_SESSION_STORAGE = "clearSessionStorage" + GET_SESSION_STORAGE_SIZE = "getSessionStorageSize" diff --git a/selenium/webdriver/remote/errorhandler.py b/selenium/webdriver/remote/errorhandler.py new file mode 100644 index 0000000..84a2256 --- /dev/null +++ b/selenium/webdriver/remote/errorhandler.py @@ -0,0 +1,152 @@ +# 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 InvalidSelectorException +from selenium.common.exceptions import ImeNotAvailableException +from selenium.common.exceptions import ImeActivationFailedException +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 NoAlertPresentException +from selenium.common.exceptions import ErrorInResponseException +from selenium.common.exceptions import TimeoutException +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import MoveTargetOutOfBoundsException + +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 + JAVASCRIPT_ERROR = 17 + XPATH_LOOKUP_ERROR = 19 + TIMEOUT = 21 + NO_SUCH_WINDOW = 23 + INVALID_COOKIE_DOMAIN = 24 + UNABLE_TO_SET_COOKIE = 25 + UNEXPECTED_ALERT_OPEN = 26 + NO_ALERT_OPEN = 27 + SCRIPT_TIMEOUT = 28 + INVALID_ELEMENT_COORDINATES = 29 + IME_NOT_AVAILABLE = 30; + IME_ENGINE_ACTIVATION_FAILED = 31 + INVALID_SELECTOR = 32 + MOVE_TARGET_OUT_OF_BOUNDS = 34 + INVALID_XPATH_SELECTOR = 51 + INVALID_XPATH_SELECTOR_RETURN_TYPER = 52 + METHOD_NOT_ALLOWED = 405 + + +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.INVALID_SELECTOR \ + or status == ErrorCode.INVALID_XPATH_SELECTOR \ + or status == ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER: + exception_class = InvalidSelectorException + elif status == ErrorCode.ELEMENT_IS_NOT_SELECTABLE: + exception_class = ElementNotSelectableException + elif status == ErrorCode.INVALID_COOKIE_DOMAIN: + exception_class = WebDriverException + elif status == ErrorCode.UNABLE_TO_SET_COOKIE: + exception_class = WebDriverException + elif status == ErrorCode.TIMEOUT: + exception_class = TimeoutException + elif status == ErrorCode.SCRIPT_TIMEOUT: + exception_class = TimeoutException + elif status == ErrorCode.UNKNOWN_ERROR: + exception_class = WebDriverException + elif status == ErrorCode.NO_ALERT_OPEN: + exception_class = NoAlertPresentException + elif status == ErrorCode.IME_NOT_AVAILABLE: + exception_class = ImeNotAvailableException + elif status == ErrorCode.IME_ENGINE_ACTIVATION_FAILED: + exception_class = ImeActivationFailedException + elif status == ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS: + exception_class = MoveTargetOutOfBoundsException + else: + exception_class = WebDriverException + 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'] + + screen = None + if 'screen' in value: + screen = value['screen'] + + stacktrace = None + if 'stackTrace' in value and value['stackTrace']: + zeroeth = '' + try: + zeroeth = value['stackTrace'][0] + except: + pass + if zeroeth.has_key('methodName'): + stacktrace = "Method %s threw an error in %s" % \ + (zeroeth['methodName'], + self._value_or_default(zeroeth, 'fileName', '[No file name]')) + if exception_class == ErrorInResponseException: + raise exception_class(response, message) + raise exception_class(message, screen, stacktrace) + + def _value_or_default(self, obj, key, default): + return obj[key] if obj.has_key(key) else default diff --git a/selenium/webdriver/remote/remote_connection.py b/selenium/webdriver/remote/remote_connection.py new file mode 100644 index 0000000..a7c6387 --- /dev/null +++ b/selenium/webdriver/remote/remote_connection.py @@ -0,0 +1,402 @@ +# 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 socket +import string +import urllib2 +import urlparse + +from command import Command +import utils + +LOGGER = logging.getLogger(__name__) + +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. + """ + + 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): + # Attempt to resolve the hostname and get an IP address. + parsed_url = urlparse.urlparse(remote_server_addr) + if parsed_url.hostname: + try: + netloc = socket.gethostbyname(parsed_url.hostname) + if parsed_url.port: + netloc += ':%d' % parsed_url.port + if parsed_url.username: + auth = parsed_url.username + if parsed_url.password: + auth += ':%s' % parsed_url.password + netloc = '%s@%s' % (auth, netloc) + remote_server_addr = urlparse.urlunparse( + (parsed_url.scheme, netloc, parsed_url.path, + parsed_url.params, parsed_url.query, parsed_url.fragment)) + except socket.gaierror: + LOGGER.info('Could not get IP address for host: %s' % + parsed_url.hostname) + + 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.SEND_KEYS_TO_ACTIVE_ELEMENT: + ('POST', '/session/$sessionId/keys'), + Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"), + 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'), + Command.IMPLICIT_WAIT: + ('POST', '/session/$sessionId/timeouts/implicit_wait'), + Command.EXECUTE_ASYNC_SCRIPT: ('POST','/session/$sessionId/execute_async'), + Command.SET_SCRIPT_TIMEOUT: + ('POST', '/session/$sessionId/timeouts/async_script'), + Command.SET_TIMEOUTS: + ('POST', '/session/$sessionId/timeouts'), + Command.DISMISS_ALERT: + ('POST', '/session/$sessionId/dismiss_alert'), + Command.ACCEPT_ALERT: + ('POST', '/session/$sessionId/accept_alert'), + Command.SET_ALERT_VALUE: + ('POST', '/session/$sessionId/alert_text'), + Command.GET_ALERT_TEXT: + ('GET', '/session/$sessionId/alert_text'), + Command.CLICK: + ('POST', '/session/$sessionId/click'), + Command.DOUBLE_CLICK: + ('POST', '/session/$sessionId/doubleclick'), + Command.MOUSE_DOWN: + ('POST', '/session/$sessionId/buttondown'), + Command.MOUSE_UP: + ('POST', '/session/$sessionId/buttonup'), + Command.MOVE_TO: + ('POST', '/session/$sessionId/moveto'), + Command.GET_WINDOW_SIZE: + ('GET', '/session/$sessionId/window/$windowHandle/size'), + Command.SET_WINDOW_SIZE: + ('POST', '/session/$sessionId/window/$windowHandle/size'), + Command.GET_WINDOW_POSITION: + ('GET', '/session/$sessionId/window/$windowHandle/position'), + Command.SET_WINDOW_POSITION: + ('POST', '/session/$sessionId/window/$windowHandle/position'), + Command.MAXIMIZE_WINDOW: + ('POST', '/session/$sessionId/window/$windowHandle/maximize'), + Command.SET_SCREEN_ORIENTATION: + ('POST', '/session/$sessionId/orientation'), + Command.GET_SCREEN_ORIENTATION: + ('GET', '/session/$sessionId/orientation'), + Command.SINGLE_TAP: + ('POST', '/session/$sessionId/touch/click'), + Command.TOUCH_DOWN: + ('POST', '/session/$sessionId/touch/down'), + Command.TOUCH_UP: + ('POST', '/session/$sessionId/touch/up'), + Command.TOUCH_MOVE: + ('POST', '/session/$sessionId/touch/move'), + Command.TOUCH_SCROLL: + ('POST', '/session/$sessionId/touch/scroll'), + Command.DOUBLE_TAP: + ('POST', '/session/$sessionId/touch/doubleclick'), + Command.LONG_PRESS: + ('POST', '/session/$sessionId/touch/longclick'), + Command.FLICK: + ('POST', '/session/$sessionId/touch/flick'), + Command.EXECUTE_SQL: + ('POST', '/session/$sessionId/execute_sql'), + Command.GET_LOCATION: + ('GET', '/session/$sessionId/location'), + Command.SET_LOCATION: + ('POST', '/session/$sessionId/location'), + Command.GET_APP_CACHE: + ('GET', '/session/$sessionId/application_cache'), + Command.GET_APP_CACHE_STATUS: + ('GET', '/session/$sessionId/application_cache/status'), + Command.CLEAR_APP_CACHE: + ('DELETE', '/session/$sessionId/application_cache/clear'), + Command.IS_BROWSER_ONLINE: + ('GET', '/session/$sessionId/browser_connection'), + Command.SET_BROWSER_ONLINE: + ('POST', '/session/$sessionId/browser_connection'), + Command.GET_LOCAL_STORAGE_ITEM: + ('GET', '/session/$sessionId/local_storage/key/$key'), + Command.REMOVE_LOCAL_STORAGE_ITEM: + ('POST', '/session/$sessionId/local_storage/key/$key'), + Command.GET_LOCAL_STORAGE_KEYS: + ('GET', '/session/$sessionId/local_storage'), + Command.SET_LOCAL_STORAGE_ITEM: + ('POST', '/session/$sessionId/local_storage'), + Command.CLEAR_LOCAL_STORAGE: + ('DELETE', '/session/$sessionId/local_storage'), + Command.GET_LOCAL_STORAGE_SIZE: + ('GET', '/session/$sessionId/local_storage/size'), + Command.GET_SESSION_STORAGE_ITEM: + ('GET', '/session/$sessionId/session_storage/key/$key'), + Command.REMOVE_SESSION_STORAGE_ITEM: + ('DELETE', '/session/$sessionId/session_storage/key/$key'), + Command.GET_SESSION_STORAGE_KEYS: + ('GET', '/session/$sessionId/session_storage'), + Command.SET_SESSION_STORAGE_ITEM: + ('POST', '/session/$sessionId/session_storage'), + Command.CLEAR_SESSION_STORAGE: + ('DELETE', '/session/$sessionId/session_storage'), + Command.GET_SESSION_STORAGE_SIZE: + ('GET','/session/$sessionId/session_storage/size'), + } + + 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. + """ + LOGGER.debug('%s %s %s' % (method, url, data)) + + parsed_url = urlparse.urlparse(url) + auth = None + password_manager = None + if parsed_url.username: + netloc = parsed_url.hostname + if parsed_url.port: + netloc += ":%s" % parsed_url.port + cleaned_url = urlparse.urlunparse((parsed_url.scheme, netloc, parsed_url.path, + parsed_url.params, parsed_url.query, parsed_url.fragment)) + password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + password_manager.add_password(None, "%s://%s" % (parsed_url.scheme, netloc), parsed_url.username, parsed_url.password) + request = Request(cleaned_url, data=data, method=method) + else: + request = Request(url, data=data, method=method) + + + request.add_header('Accept', 'application/json') + request.add_header('Content-Type', 'application/json;charset=UTF-8') + + if password_manager: + opener = urllib2.build_opener(urllib2.HTTPRedirectHandler(), + HttpErrorHandler(), + urllib2.HTTPBasicAuthHandler(password_manager)) + else: + 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() + content_type = response.info().getheader('Content-Type') or [] + if 'application/json' in content_type: + 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 + elif 'image/png' in content_type: + data = {'status': 0, 'value': body.strip()} + return data + finally: + response.close() diff --git a/selenium/webdriver/remote/utils.py b/selenium/webdriver/remote/utils.py new file mode 100644 index 0000000..aa32fa6 --- /dev/null +++ b/selenium/webdriver/remote/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 + +LOGGER = logging.getLogger(__name__) + +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 + LOGGER.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) + LOGGER.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)): + LOGGER.debug("Copying file %s......" % dest) + outfile = open(dest, 'wb') + outfile.write(zf.read(zip_name)) + outfile.close() + LOGGER.debug("File %s copied." % dest) + + LOGGER.info("Unzipped file can be found at %s" % tempdir) + return tempdir + + except IOError, err: + LOGGER.error("Error in extracting webdriver.xpi: %s" % err) + return None diff --git a/selenium/webdriver/remote/webdriver.py b/selenium/webdriver/remote/webdriver.py new file mode 100644 index 0000000..64faebf --- /dev/null +++ b/selenium/webdriver/remote/webdriver.py @@ -0,0 +1,821 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 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 +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import InvalidSelectorException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.alert import Alert +from selenium.webdriver.common.html5.application_cache import ApplicationCache + + +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='http://127.0.0.1:4444/wd/hub', + desired_capabilities=None, browser_profile=None): + """ + 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. + - desired_capabilities - Dictionary holding predefined values for starting a browser + - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested. + """ + if desired_capabilities is None: + raise WebDriverException("Desired Capabilities can't be None") + if not isinstance(desired_capabilities, dict): + raise WebDriverException("Desired Capabilities must be a dictionary") + self.command_executor = command_executor + if type(self.command_executor) is str or type(self.command_executor) is unicode: + self.command_executor = RemoteConnection(command_executor) + self.session_id = None + self.capabilities = {} + self.error_handler = ErrorHandler() + self.start_client() + self.start_session(desired_capabilities, browser_profile) + + @property + def name(self): + """Returns the name of the underlying browser for this instance. + + :Usage: + - driver.name + """ + 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, desired_capabilities, browser_profile=None): + """ + 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. + - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested. + """ + if browser_profile: + desired_capabilities['firefox_profile'] = browser_profile.encoded + response = self.execute(Command.NEW_SESSION, { + 'desiredCapabilities': desired_capabilities, + }) + 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): + """ + Creates a web element with the specified 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, 'sessionId': self.session_id} + + def get(self, url): + """ + Loads a web page in the current browser session. + """ + self.execute(Command.GET, {'url': url}) + + @property + def title(self): + """Returns the title of the current page. + + :Usage: + driver.title + """ + resp = self.execute(Command.GET_TITLE) + return resp['value'] if resp['value'] is not None else "" + + def find_element_by_id(self, id_): + """Finds an element by id. + + :Args: + - id\_ - The id of the element to be found. + + :Usage: + driver.find_element_by_id('foo') + """ + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + """ + Finds multiple elements by id. + + :Args: + - id\_ - The id of the elements to be found. + + :Usage: + driver.find_element_by_id('foo') + """ + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_xpath(self, xpath): + """ + Finds an element by xpath. + + :Args: + - xpath - The xpath locator of the element to find. + + :Usage: + driver.find_element_by_xpath('//div/td[1]') + """ + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + """ + Finds multiple elements by xpath. + + :Args: + - xpath - The xpath locator of the elements to be found. + + :Usage: + driver.find_elements_by_xpath("//div[contains(@class, 'foo')]") + """ + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_link_text(self, link_text): + """ + Finds an element by link text. + + :Args: + - link_text: The text of the element to be found. + + :Usage: + driver.find_element_by_link_text('Sign In') + """ + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, text): + """ + Finds elements by link text. + + :Args: + - link_text: The text of the elements to be found. + + :Usage: + driver.find_elements_by_link_text('Sign In') + """ + return self.find_elements(by=By.LINK_TEXT, value=text) + + def find_element_by_partial_link_text(self, link_text): + """ + Finds an element by a partial match of its link text. + + :Args: + - link_text: The text of the element to partially match on. + + :Usage: + driver.find_element_by_partial_link_text('Sign') + """ + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + """ + Finds elements by a partial match of their link text. + + :Args: + - link_text: The text of the element to partial match on. + + :Usage: + driver.find_element_by_partial_link_text('Sign') + """ + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_name(self, name): + """ + Finds an element by name. + + :Args: + - name: The name of the element to find. + + :Usage: + driver.find_element_by_name('foo') + """ + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + """ + Finds elements by name. + + :Args: + - name: The name of the elements to find. + + :Usage: + driver.find_elements_by_name('foo') + """ + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_tag_name(self, name): + """ + Finds an element by tag name. + + :Args: + - name: The tag name of the element to find. + + :Usage: + driver.find_element_by_tag_name('foo') + """ + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + """ + Finds elements by tag name. + + :Args: + - name: The tag name the use when finding elements. + + :Usage: + driver.find_elements_by_tag_name('foo') + """ + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_class_name(self, name): + """ + Finds an element by class name. + + :Args: + - name: The class name of the element to find. + + :Usage: + driver.find_element_by_class_name('foo') + """ + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + """ + Finds elements by class name. + + :Args: + - name: The class name of the elements to find. + + :Usage: + driver.find_elements_by_class_name('foo') + """ + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + """ + Finds an element by css selector. + + :Args: + - css_selector: The css selector to use when finding elements. + + :Usage: + driver.find_element_by_css_selector('#foo') + """ + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + """ + Finds elements by css selector. + + :Args: + - css_selector: The css selector to use when finding elements. + + :Usage: + driver.find_element_by_css_selector('#foo') + """ + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def execute_script(self, script, *args): + """ + Synchronously Executes JavaScript in the current window/frame. + + :Args: + - script: The JavaScript to execute. + - \*args: Any applicable arguments for your JavaScript. + + :Usage: + driver.execute_script('document.title') + """ + 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 execute_async_script(self, script, *args): + """ + Asynchronously Executes JavaScript in the current window/frame. + + :Args: + - script: The JavaScript to execute. + - \*args: Any applicable arguments for your JavaScript. + + :Usage: + driver.execute_async_script('document.title') + """ + if len(args) == 1: + converted_args = args[0] + else: + converted_args = list(args) + converted_args = list(args) + return self.execute(Command.EXECUTE_ASYNC_SCRIPT, + {'script': script, 'args':converted_args})['value'] + + @property + def current_url(self): + """ + Gets the URL of the current page. + + :Usage: + driver.current_url + """ + return self.execute(Command.GET_CURRENT_URL)['value'] + + @property + def page_source(self): + """ + Gets the source of the current page. + + :Usage: + driver.page_source + """ + return self.execute(Command.GET_PAGE_SOURCE)['value'] + + def close(self): + """ + Closes the current window. + + :Usage: + driver.close() + """ + self.execute(Command.CLOSE) + + def quit(self): + """ + Quits the driver and closes every associated window. + + :Usage: + driver.quit() + """ + try: + self.execute(Command.QUIT) + finally: + self.stop_client() + + @property + def current_window_handle(self): + """ + Returns the handle of the current window. + + :Usage: + driver.current_window_handle + """ + return self.execute(Command.GET_CURRENT_WINDOW_HANDLE)['value'] + + @property + def window_handles(self): + """ + Returns the handles of all windows within the current session. + + :Usage: + driver.window_handles + """ + return self.execute(Command.GET_WINDOW_HANDLES)['value'] + + def maximize_window(self): + """ + Maximizes the current window that webdriver is using + """ + self.execute(Command.MAXIMIZE_WINDOW, {"windowHandle": "current"}) + + #Target Locators + def switch_to_active_element(self): + """ + Returns the element with focus, or BODY if nothing has focus. + + :Usage: + driver.switch_to_active_element() + """ + return self.execute(Command.GET_ACTIVE_ELEMENT)['value'] + + def switch_to_window(self, window_name): + """ + Switches focus to the specified window. + + :Args: + - window_name: The name or window handle of the window to switch to. + + :Usage: + driver.switch_to_window('main') + """ + self.execute(Command.SWITCH_TO_WINDOW, {'name': window_name}) + + def switch_to_frame(self, frame_reference): + """ + Switches focus to the specified frame, by index, name, or webelement. + + :Args: + - frame_reference: The name of the window to switch to, an integer representing the index, + or a webelement that is an (i)frame to switch to. + + :Usage: + driver.switch_to_frame('frame_name') + driver.switch_to_frame(1) + driver.switch_to_frame(driver.find_elements_by_tag_name("iframe")[0]) + """ + self.execute(Command.SWITCH_TO_FRAME, {'id': frame_reference}) + + def switch_to_default_content(self): + """ + Switch focus to the default frame. + + :Usage: + driver.switch_to_default_content() + """ + self.execute(Command.SWITCH_TO_FRAME, {'id': None}) + + def switch_to_alert(self): + """ + Switches focus to an alert on the page. + + :Usage: + driver.switch_to_alert() + """ + return Alert(self) + + #Navigation + def back(self): + """ + Goes one step backward in the browser history. + + :Usage: + driver.back() + """ + self.execute(Command.GO_BACK) + + def forward(self): + """ + Goes one step forward in the browser history. + + :Usage: + driver.forward() + """ + self.execute(Command.GO_FORWARD) + + def refresh(self): + """ + Refreshes the current page. + + :Usage: + driver.refresh() + """ + self.execute(Command.REFRESH) + + # Options + def get_cookies(self): + """ + Returns a set of dictionaries, corresponding to cookies visible in the current session. + + :Usage: + driver.get_cookies() + """ + return self.execute(Command.GET_ALL_COOKIES)['value'] + + def get_cookie(self, name): + """ + Get a single cookie by name. Returns the cookie if found, None if not. + + :Usage: + driver.get_cookie('my_cookie') + """ + cookies = self.get_cookies() + for cookie in cookies: + if cookie['name'] == name: + return cookie + return None + + def delete_cookie(self, name): + """ + Deletes a single cookie with the given name. + + :Usage: + driver.delete_cookie('my_cookie') + """ + self.execute(Command.DELETE_COOKIE, {'name': name}) + + def delete_all_cookies(self): + """ + Delete all cookies in the scope of the session. + + :Usage: + driver.delete_all_cookies() + """ + self.execute(Command.DELETE_ALL_COOKIES) + + def add_cookie(self, cookie_dict): + """ + Adds a cookie to your current session. + + :Args: + - cookie_dict: A dictionary object, with required keys - "name" and "value"; + optional keys - "path", "domain", "secure", "expiry" + + Usage: + driver.add_cookie({'name' : 'foo', 'value' : 'bar'}) + driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'}) + driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/', 'secure':True}) + + """ + self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict}) + + # Timeouts + def implicitly_wait(self, time_to_wait): + """ + Sets a sticky timeout to implicitly wait for an element to be found, + or a command to complete. This method only needs to be called one + time per session. To set the timeout for calls to + execute_async_script, see set_script_timeout. + + :Args: + - time_to_wait: Amount of time to wait (in seconds) + + :Usage: + driver.implicitly_wait(30) + """ + self.execute(Command.IMPLICIT_WAIT, {'ms': float(time_to_wait) * 1000}) + + def set_script_timeout(self, time_to_wait): + """ + Set the amount of time that the script should wait during an + execute_async_script call before throwing an error. + + :Args: + - time_to_wait: The amount of time to wait (in seconds) + + :Usage: + driver.set_script_timeout(30) + """ + self.execute(Command.SET_SCRIPT_TIMEOUT, + {'ms': float(time_to_wait) * 1000}) + + def set_page_load_timeout(self, time_to_wait): + """ + Set the amount of time to wait for a page load to complete + before throwing an error. + + :Args: + - time_to_wait: The amount of time to wait + + :Usage: + driver.set_page_load_timeout(30) + """ + self.execute(Command.SET_TIMEOUTS, + {'ms': float(time_to_wait) * 1000, 'type':'page load'}) + + def find_element(self, by=By.ID, value=None): + """ + 'Private' method used by the find_element_by_* methods. + + :Usage: + Use the corresponding find_element_by_* instead of this. + """ + if isinstance(by, tuple) or isinstance(value, int) or value==None: + raise InvalidSelectorException("Invalid locator values passed in") + + return self.execute(Command.FIND_ELEMENT, + {'using': by, 'value': value})['value'] + + def find_elements(self, by=By.ID, value=None): + """ + 'Private' method used by the find_elements_by_* methods. + + :Usage: + Use the corresponding find_elements_by_* instead of this. + """ + if isinstance(by, tuple) or isinstance(value, int) or value==None: + raise InvalidSelectorException("Invalid locator values passed in") + + return self.execute(Command.FIND_ELEMENTS, + {'using': by, 'value': value})['value'] + @property + def desired_capabilities(self): + """ + returns the drivers current desired capabilities being used + """ + return self.capabilities + + def get_screenshot_as_file(self, filename): + """ + Gets the screenshot of the current window. Returns False if there is + any IOError, else returns True. Use full paths in your filename. + + :Args: + - filename: The full path you wish to save your screenshot to. + + :Usage: + driver.get_screenshot_as_file('/Screenshots/foo.png') + """ + png = self.execute(Command.SCREENSHOT)['value'] + try: + with open(filename, 'wb') as f: + f.write(base64.decodestring(png)) + except IOError: + return False + del png + 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. + + :Usage: + driver.get_screenshot_as_base64() + """ + return self.execute(Command.SCREENSHOT)['value'] + + def set_window_size(self, width, height, windowHandle='current'): + """ + Sets the width and height of the current window. (window.resizeTo) + + :Args: + - width: the width in pixels to set the window to + - height: the height in pixels to set the window to + + :Usage: + driver.set_window_size(800,600) + """ + self.execute(Command.SET_WINDOW_SIZE, {'width': width, 'height': height, + 'windowHandle': windowHandle}) + + def get_window_size(self, windowHandle='current'): + """ + Gets the width and height of the current window. + + :Usage: + driver.get_window_size() + """ + return self.execute(Command.GET_WINDOW_SIZE, + {'windowHandle': windowHandle})['value'] + + def set_window_position(self, x, y, windowHandle='current'): + """ + Sets the x,y position of the current window. (window.moveTo) + + :Args: + - x: the x-coordinate in pixels to set the window position + - y: the y-coordinate in pixels to set the window position + + :Usage: + driver.set_window_position(0,0) + """ + self.execute(Command.SET_WINDOW_POSITION, {'x': x, 'y': y, + 'windowHandle': windowHandle}) + + def get_window_position(self, windowHandle='current'): + """ + Gets the x,y position of the current window. + + :Usage: + driver.get_window_position() + """ + return self.execute(Command.GET_WINDOW_POSITION, + {'windowHandle': windowHandle})['value'] + + @property + def orientation(self): + """ + Gets the current orientation of the device + + :Usage: + orientation = driver.orientation + """ + return self.execute(Command.GET_SCREEN_ORIENTATION)['value'] + + @orientation.setter + def orientation(self, value): + """ + Sets the current orientation of the device + + :Args: + - value: orientation to set it to. + + :Usage: + driver.orientation = 'landscape' + """ + allowed_values = ['LANDSCAPE', 'PORTRAIT'] + if value.upper() in allowed_values: + self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value})['value'] + else: + raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + + def is_online(self): + """ Returns a boolean if the browser is online or offline""" + return self.execute(Command.IS_BROWSER_ONLINE)['value'] + + @property + def application_cache(self): + """ Returns a ApplicationCache Object to interact with the browser app cache""" + return ApplicationCache(self) + + def save_screenshot(self, filename): + """ + Gets the screenshot of the current window. Returns False if there is + any IOError, else returns True. Use full paths in your filename. + """ + png = self.execute(Command.SCREENSHOT)['value'] + try: + f = open(filename, 'wb') + f.write(base64.decodestring(png)) + f.close() + except IOError: + return False + finally: + del png + return True diff --git a/selenium/webdriver/remote/webelement.py b/selenium/webdriver/remote/webelement.py new file mode 100644 index 0000000..baa8cae --- /dev/null +++ b/selenium/webdriver/remote/webelement.py @@ -0,0 +1,284 @@ +# 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.""" +import os +import zipfile +from StringIO import StringIO +import base64 + + +from command import Command +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import InvalidSelectorException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + + +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_ + + @property + def tag_name(self): + """Gets this element's tagName property.""" + return self._execute(Command.GET_ELEMENT_TAG_NAME)['value'] + + @property + def 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 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.""" + resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name': name}) + attributeValue = '' + if resp['value'] is None: + attributeValue = None + else: + attributeValue = unicode(resp['value']) + if type(resp['value']) is bool: + attributeValue = attributeValue.lower() + + return attributeValue + + def is_selected(self): + """Whether the element is selected.""" + return self._execute(Command.IS_ELEMENT_SELECTED)['value'] + + def is_enabled(self): + """Whether the element is enabled.""" + return self._execute(Command.IS_ELEMENT_ENABLED)['value'] + + def find_element_by_id(self, id_): + """Finds element by id.""" + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_name(self, name): + """Find element by name.""" + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_link_text(self, link_text): + """Finds element by link text.""" + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, link_text): + return self.find_elements(by=By.LINK_TEXT, value=link_text) + + def find_element_by_partial_link_text(self, link_text): + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_tag_name(self, name): + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_xpath(self, xpath): + """Finds element by xpath.""" + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + """Finds elements within the elements by xpath.""" + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_class_name(self, name): + """Finds an element by their class name.""" + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + """Finds elements by their class name.""" + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + """Find and return an element by CSS selector.""" + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + """Find and return list of multiple elements by CSS selector.""" + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def send_keys(self, *value): + """Simulates typing into the element.""" + # transfer file to another machine only if remote driver is used + # the same behaviour as for java binding + parent_class = self.parent.__class__ + fqcn = parent_class.__module__ + "." + parent_class.__name__ + is_remote = fqcn == "selenium.webdriver.remote.webdriver.WebDriver" + if is_remote: + local_file = LocalFileDetector.is_local_file(*value) + if local_file is not None: + value = self._upload(local_file) + + typing = [] + for val in value: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = str(val) + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + self._execute(Command.SEND_KEYS_TO_ELEMENT, {'value': typing}) + + # RenderedWebElement Items + def is_displayed(self): + """Whether the element would be visible to a user""" + return self._execute(Command.IS_ELEMENT_DISPLAYED)['value'] + + @property + def location_once_scrolled_into_view(self): + """CONSIDERED LIABLE TO CHANGE WITHOUT WARNING. Use this to discover where on the screen an + element is so that we can click it. This method should cause the element to be scrolled + into view. + + Returns the top lefthand corner location on the screen, or None if the element is not visible""" + return self._execute(Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW)['value'] + + @property + def size(self): + """ Returns the size of the element """ + size = self._execute(Command.GET_ELEMENT_SIZE)['value'] + new_size = {} + new_size["height"] = size["height"] + new_size["width"] = size["width"] + return new_size + + def value_of_css_property(self, property_name): + """ Returns the value of a CSS property """ + return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, + {'propertyName': property_name})['value'] + + @property + def location(self): + """ Returns the location of the element in the renderable canvas""" + return self._execute(Command.GET_ELEMENT_LOCATION)['value'] + + @property + def parent(self): + return self._parent + + @property + def id(self): + return self._id + + def __eq__(self, element): + return self._id == element.id + + # Private Methods + 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 find_element(self, by=By.ID, value=None): + if isinstance(by, tuple) or isinstance(value, int) or value==None: + raise InvalidSelectorException("Invalid locator values passed in") + + return self._execute(Command.FIND_CHILD_ELEMENT, + {"using": by, "value": value})['value'] + + def find_elements(self, by=By.ID, value=None): + if isinstance(by, tuple) or isinstance(value, int) or value==None: + raise InvalidSelectorException("Invalid locator values passed in") + + return self._execute(Command.FIND_CHILD_ELEMENTS, + {"using": by, "value": value})['value'] + + def _upload(self, filename): + fp = StringIO() + zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) + zipped.write(filename, os.path.split(filename)[1]) + zipped.close() + try: + return self._execute(Command.UPLOAD_FILE, + {'file': base64.encodestring(fp.getvalue())})['value'] + except WebDriverException as e: + if "Unrecognized command: POST" in e.__str__(): + return filename + elif "Command not found: POST " in e.__str__(): + return filename + elif '{"status":405,"value":["GET","HEAD","DELETE"]}' in e.__str__(): + return filename + else: + raise e + +class LocalFileDetector(object): + + @classmethod + def is_local_file(cls, *keys): + file_path = '' + typing = [] + for val in keys: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = str(val) + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + file_path = ''.join(typing) + + if file_path is '': + return None + + try: + if os.path.exists(file_path): + return file_path + except: + pass + return None + diff --git a/selenium/webdriver/support/__init__.py b/selenium/webdriver/support/__init__.py new file mode 100644 index 0000000..79219fd --- /dev/null +++ b/selenium/webdriver/support/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# +# Copyright 2011 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/selenium/webdriver/support/abstract_event_listener.py b/selenium/webdriver/support/abstract_event_listener.py new file mode 100644 index 0000000..1c2d44e --- /dev/null +++ b/selenium/webdriver/support/abstract_event_listener.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# +# Copyright 2011 Software Freedom Conservancy. +# +# 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 AbstractEventListener(object): + """ + Event listener must subclass and implement this fully or partially + """ + + def before_navigate_to(self, url, driver): pass + + def after_navigate_to(self, url, driver): pass + + def before_navigate_back(self, driver): pass + + def after_navigate_back(self, driver): pass + + def before_navigate_forward(self, driver): pass + + def after_navigate_forward(self, driver): pass + + def before_find(self, by, value, driver): pass + + def after_find(self, by, value, driver): pass + + def before_click(self, element, driver): pass + + def after_click(self, element, driver): pass + + def before_change_value_of(self, element, driver): pass + + def after_change_value_of(self, element, driver): pass + + def before_execute_script(self, script, driver): pass + + def after_execute_script(self, script, driver): pass + + def before_close(self, driver): pass + + def after_close(self, driver): pass + + def before_quit(self, driver): pass + + def after_quit(self, driver): pass + + def on_exception(self, exception, driver): pass diff --git a/selenium/webdriver/support/color.py b/selenium/webdriver/support/color.py new file mode 100644 index 0000000..a6fff23 --- /dev/null +++ b/selenium/webdriver/support/color.py @@ -0,0 +1,303 @@ +#!/usr/bin/python + +# Copyright 2011 Software Freedom Conservancy. +# +# 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. + +RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$" +RGB_PCT_PATTERN = r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$" +RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$" +RGBA_PCT_PATTERN = r"^\s*rgba\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(0|1|0\.\d+)\s*\)\s*$" +HEX_PATTERN = r"#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})" +HEX3_PATTERN = r"#([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])" +HSL_PATTERN = r"^\s*hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)\s*$" +HSLA_PATTERN = r"^\s*hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0\.\d+)\s*\)\s*$" + + +class Color(object): + """ + Color conversion support class + + Example: + + .. code-block:: python + + from selenium.webdriver.support.color import Color + + print Color.from_string('#00ff33').rgba + print Color.from_string('rgb(1, 255, 3)').hex + print Color.from_string('blue').rgba + """ + + @staticmethod + def from_string(str_): + import re + + class Matcher(object): + def __init__(self): + self.match_obj = None + + def match(self, pattern, str_): + self.match_obj = re.match(pattern, str_) + return self.match_obj + + @property + def groups(self): + return () if self.match_obj is None else self.match_obj.groups() + + m = Matcher() + + if m.match(RGB_PATTERN, str_): + return Color(*m.groups) + elif m.match(RGB_PCT_PATTERN, str_): + rgb = tuple([float(each) / 100 * 255 for each in m.groups]) + return Color(*rgb) + elif m.match(RGBA_PATTERN, str_): + return Color(*m.groups) + elif m.match(RGBA_PCT_PATTERN, str_): + rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]]) + return Color(*rgba) + elif m.match(HEX_PATTERN, str_): + rgb = tuple([int(each, 16) for each in m.groups]) + return Color(*rgb) + elif m.match(HEX3_PATTERN, str_): + rgb = tuple([int(each * 2, 16) for each in m.groups]) + return Color(*rgb) + elif m.match(HSL_PATTERN, str_) or m.match(HSLA_PATTERN, str_): + return Color._from_hsl(*m.groups) + elif str_.upper() in Colors.keys(): + return Colors[str_.upper()] + else: + raise ValueError("Could not convert %s into color" % str_) + + @staticmethod + def _from_hsl(h, s, l, a=1): + h = float(h) / 360 + s = float(s) / 100 + l = float(l) / 100 + + if s == 0: + r = l + g = r + b = r + else: + luminocity2 = l * (1 + s) if l < 0.5 else l + s - l * s + luminocity1 = 2 * l - luminocity2 + + def hue_to_rgb(lum1, lum2, hue): + if hue < 0.0: + hue += 1 + if hue > 1.0: + hue -= 1 + + if hue < 1.0 / 6.0: + return (lum1 + (lum2 - lum1) * 6.0 * hue) + elif hue < 1.0 / 2.0: + return lum2 + elif hue < 2.0 / 3.0: + return lum1 + (lum2 - lum1) * ((2.0 / 3.0) - hue) * 6.0 + else: + return lum1 + + r = hue_to_rgb(luminocity1, luminocity2, h + 1.0 / 3.0) + g = hue_to_rgb(luminocity1, luminocity2, h) + b = hue_to_rgb(luminocity1, luminocity2, h - 1.0 / 3.0) + + return Color(r * 256, g * 256, b * 256, a) + + def __init__(self, red, green, blue, alpha=1): + self.red = int(red) + self.green = int(green) + self.blue = int(blue) + self.alpha = float(alpha) or 0 + + @property + def rgb(self): + return "rgb(%d, %d, %d)" % (self.red, self.green, self.blue) + + @property + def rgba(self): + a = "1" if self.alpha == 1 else str(self.alpha) + return "rgba(%d, %d, %d, %s)" % (self.red, self.green, self.blue, a) + + @property + def hex(self): + return "#%02x%02x%02x" % (self.red, self.green, self.blue) + + def __eq__(self, other): + if isinstance(other, Color): + return self.rgba == other.rgba + return NotImplemented + + def __ne__(self, other): + result = self.__eq__(other) + if result is NotImplemented: + return result + return not result + + def __hash__(self): + return hash((self.red, self.green, self.blue, self.alpha)) + + +# Basic, extended and transparent colour keywords as defined by the W3C HTML4 spec +# See http://www.w3.org/TR/css3-color/#html4 +Colors = { + "TRANSPARENT": Color(0, 0, 0, 0), + "ALICEBLUE": Color(240, 248, 255), + "ANTIQUEWHITE": Color(250, 235, 215), + "AQUA": Color(0, 255, 255), + "AQUAMARINE": Color(127, 255, 212), + "AZURE": Color(240, 255, 255), + "BEIGE": Color(245, 245, 220), + "BISQUE": Color(255, 228, 196), + "BLACK": Color(0, 0, 0), + "BLANCHEDALMOND": Color(255, 235, 205), + "BLUE": Color(0, 0, 255), + "BLUEVIOLET": Color(138, 43, 226), + "BROWN": Color(165, 42, 42), + "BURLYWOOD": Color(222, 184, 135), + "CADETBLUE": Color(95, 158, 160), + "CHARTREUSE": Color(127, 255, 0), + "CHOCOLATE": Color(210, 105, 30), + "CORAL": Color(255, 127, 80), + "CORNFLOWERBLUE": Color(100, 149, 237), + "CORNSILK": Color(255, 248, 220), + "CRIMSON": Color(220, 20, 60), + "CYAN": Color(0, 255, 255), + "DARKBLUE": Color(0, 0, 139), + "DARKCYAN": Color(0, 139, 139), + "DARKGOLDENROD": Color(184, 134, 11), + "DARKGRAY": Color(169, 169, 169), + "DARKGREEN": Color(0, 100, 0), + "DARKGREY": Color(169, 169, 169), + "DARKKHAKI": Color(189, 183, 107), + "DARKMAGENTA": Color(139, 0, 139), + "DARKOLIVEGREEN": Color(85, 107, 47), + "DARKORANGE": Color(255, 140, 0), + "DARKORCHID": Color(153, 50, 204), + "DARKRED": Color(139, 0, 0), + "DARKSALMON": Color(233, 150, 122), + "DARKSEAGREEN": Color(143, 188, 143), + "DARKSLATEBLUE": Color(72, 61, 139), + "DARKSLATEGRAY": Color(47, 79, 79), + "DARKSLATEGREY": Color(47, 79, 79), + "DARKTURQUOISE": Color(0, 206, 209), + "DARKVIOLET": Color(148, 0, 211), + "DEEPPINK": Color(255, 20, 147), + "DEEPSKYBLUE": Color(0, 191, 255), + "DIMGRAY": Color(105, 105, 105), + "DIMGREY": Color(105, 105, 105), + "DODGERBLUE": Color(30, 144, 255), + "FIREBRICK": Color(178, 34, 34), + "FLORALWHITE": Color(255, 250, 240), + "FORESTGREEN": Color(34, 139, 34), + "FUCHSIA": Color(255, 0, 255), + "GAINSBORO": Color(220, 220, 220), + "GHOSTWHITE": Color(248, 248, 255), + "GOLD": Color(255, 215, 0), + "GOLDENROD": Color(218, 165, 32), + "GRAY": Color(128, 128, 128), + "GREY": Color(128, 128, 128), + "GREEN": Color(0, 128, 0), + "GREENYELLOW": Color(173, 255, 47), + "HONEYDEW": Color(240, 255, 240), + "HOTPINK": Color(255, 105, 180), + "INDIANRED": Color(205, 92, 92), + "INDIGO": Color(75, 0, 130), + "IVORY": Color(255, 255, 240), + "KHAKI": Color(240, 230, 140), + "LAVENDER": Color(230, 230, 250), + "LAVENDERBLUSH": Color(255, 240, 245), + "LAWNGREEN": Color(124, 252, 0), + "LEMONCHIFFON": Color(255, 250, 205), + "LIGHTBLUE": Color(173, 216, 230), + "LIGHTCORAL": Color(240, 128, 128), + "LIGHTCYAN": Color(224, 255, 255), + "LIGHTGOLDENRODYELLOW": Color(250, 250, 210), + "LIGHTGRAY": Color(211, 211, 211), + "LIGHTGREEN": Color(144, 238, 144), + "LIGHTGREY": Color(211, 211, 211), + "LIGHTPINK": Color(255, 182, 193), + "LIGHTSALMON": Color(255, 160, 122), + "LIGHTSEAGREEN": Color(32, 178, 170), + "LIGHTSKYBLUE": Color(135, 206, 250), + "LIGHTSLATEGRAY": Color(119, 136, 153), + "LIGHTSLATEGREY": Color(119, 136, 153), + "LIGHTSTEELBLUE": Color(176, 196, 222), + "LIGHTYELLOW": Color(255, 255, 224), + "LIME": Color(0, 255, 0), + "LIMEGREEN": Color(50, 205, 50), + "LINEN": Color(250, 240, 230), + "MAGENTA": Color(255, 0, 255), + "MAROON": Color(128, 0, 0), + "MEDIUMAQUAMARINE": Color(102, 205, 170), + "MEDIUMBLUE": Color(0, 0, 205), + "MEDIUMORCHID": Color(186, 85, 211), + "MEDIUMPURPLE": Color(147, 112, 219), + "MEDIUMSEAGREEN": Color(60, 179, 113), + "MEDIUMSLATEBLUE": Color(123, 104, 238), + "MEDIUMSPRINGGREEN": Color(0, 250, 154), + "MEDIUMTURQUOISE": Color(72, 209, 204), + "MEDIUMVIOLETRED": Color(199, 21, 133), + "MIDNIGHTBLUE": Color(25, 25, 112), + "MINTCREAM": Color(245, 255, 250), + "MISTYROSE": Color(255, 228, 225), + "MOCCASIN": Color(255, 228, 181), + "NAVAJOWHITE": Color(255, 222, 173), + "NAVY": Color(0, 0, 128), + "OLDLACE": Color(253, 245, 230), + "OLIVE": Color(128, 128, 0), + "OLIVEDRAB": Color(107, 142, 35), + "ORANGE": Color(255, 165, 0), + "ORANGERED": Color(255, 69, 0), + "ORCHID": Color(218, 112, 214), + "PALEGOLDENROD": Color(238, 232, 170), + "PALEGREEN": Color(152, 251, 152), + "PALETURQUOISE": Color(175, 238, 238), + "PALEVIOLETRED": Color(219, 112, 147), + "PAPAYAWHIP": Color(255, 239, 213), + "PEACHPUFF": Color(255, 218, 185), + "PERU": Color(205, 133, 63), + "PINK": Color(255, 192, 203), + "PLUM": Color(221, 160, 221), + "POWDERBLUE": Color(176, 224, 230), + "PURPLE": Color(128, 0, 128), + "RED": Color(255, 0, 0), + "ROSYBROWN": Color(188, 143, 143), + "ROYALBLUE": Color(65, 105, 225), + "SADDLEBROWN": Color(139, 69, 19), + "SALMON": Color(250, 128, 114), + "SANDYBROWN": Color(244, 164, 96), + "SEAGREEN": Color(46, 139, 87), + "SEASHELL": Color(255, 245, 238), + "SIENNA": Color(160, 82, 45), + "SILVER": Color(192, 192, 192), + "SKYBLUE": Color(135, 206, 235), + "SLATEBLUE": Color(106, 90, 205), + "SLATEGRAY": Color(112, 128, 144), + "SLATEGREY": Color(112, 128, 144), + "SNOW": Color(255, 250, 250), + "SPRINGGREEN": Color(0, 255, 127), + "STEELBLUE": Color(70, 130, 180), + "TAN": Color(210, 180, 140), + "TEAL": Color(0, 128, 128), + "THISTLE": Color(216, 191, 216), + "TOMATO": Color(255, 99, 71), + "TURQUOISE": Color(64, 224, 208), + "VIOLET": Color(238, 130, 238), + "WHEAT": Color(245, 222, 179), + "WHITE": Color(255, 255, 255), + "WHITESMOKE": Color(245, 245, 245), + "YELLOW": Color(255, 255, 0), + "YELLOWGREEN": Color(154, 205, 50) +} diff --git a/selenium/webdriver/support/event_firing_webdriver.py b/selenium/webdriver/support/event_firing_webdriver.py new file mode 100644 index 0000000..5f0ee8b --- /dev/null +++ b/selenium/webdriver/support/event_firing_webdriver.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# +# Copyright 2011 Software Freedom Conservancy. +# +# 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 WebDriverException +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement +from abstract_event_listener import AbstractEventListener + + +def _wrap_elements(result, ef_driver): + if isinstance(result, WebElement): + return EventFiringWebElement(result, ef_driver) + elif isinstance(result, list): + return [_wrap_elements(item, ef_driver) for item in result] + else: + return result + +class EventFiringWebDriver(object): + """ + A wrapper around an arbitrary WebDriver instance which supports firing events + """ + + def __init__(self, driver, event_listener): + """ + Creates a new instance of the EventFiringWebDriver + + :Args: + - driver : A WebDriver instance + - event_listener : Instance of a class that subclasses AbstractEventListener and implements it fully or partially + + Example: + + .. code-block:: python + + from selenium.webdriver import Firefox + from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener + + class MyListener(AbstractEventListener): + def before_navigate_to(self, url, driver): + print "Before navigate to %s" % url + def after_navigate_to(self, url, driver): + print "After navigate to %s" % url + + driver = Firefox() + ef_driver = EventFiringWebDriver(driver, MyListener()) + ef_driver.get("http://www.google.co.in/") + """ + if not isinstance(driver, WebDriver): + raise WebDriverException("A WebDriver instance must be supplied") + if not isinstance(event_listener, AbstractEventListener): + raise WebDriverException("Event listener must be a subclass of AbstractEventListener") + self._driver = driver + self._listener = event_listener + + @property + def wrapped_driver(self): + """Returns the WebDriver instance wrapped by this EventsFiringWebDriver""" + return self._driver + + def get(self, url): + self._dispatch("navigate_to", (url, self._driver), "get", (url, )) + + def back(self): + self._dispatch("navigate_back", (self._driver,), "back", ()) + + def forward(self): + self._dispatch("navigate_forward", (self._driver,), "forward", ()) + + def execute_script(self, script, *args): + unwrapped_args = (script,) + self._unwrap_element_args(args) + return self._dispatch("execute_script", (script, self._driver), "execute_script", unwrapped_args) + + def execute_async_script(self, script, *args): + unwrapped_args = (script,) + self._unwrap_element_args(args) + return self._dispatch("execute_script", (script, self._driver), "execute_async_script", unwrapped_args) + + def close(self): + self._dispatch("close", (self._driver,), "close", ()) + + def quit(self): + self._dispatch("quit", (self._driver,), "quit", ()) + + def find_element(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_element", (by, value)) + + def find_elements(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value)) + + def find_element_by_id(self, id_): + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_xpath(self, xpath): + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_link_text(self, link_text): + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, text): + return self.find_elements(by=By.LINK_TEXT, value=text) + + def find_element_by_partial_link_text(self, link_text): + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_name(self, name): + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_tag_name(self, name): + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_class_name(self, name): + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def _dispatch(self, l_call, l_args, d_call, d_args): + getattr(self._listener, "before_%s" % l_call)(*l_args) + try: + result = getattr(self._driver, d_call)(*d_args) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + getattr(self._listener, "after_%s" % l_call)(*l_args) + return _wrap_elements(result, self) + + def _unwrap_element_args(self, args): + if isinstance(args, EventFiringWebElement): + return args.wrapped_element + elif isinstance(args, tuple): + return tuple([self._unwrap_element_args(item) for item in args]) + elif isinstance(args, list): + return [self._unwrap_element_args(item) for item in args] + else: + return args + + def __setattr__(self, item, value): + if item.startswith("_") or not hasattr(self._driver, item): + object.__setattr__(self, item, value) + else: + try: + object.__setattr__(self._driver, item, value) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + + def __getattr__(self, name): + + def _wrap(*args): + try: + result = attrib(*args) + return _wrap_elements(result, self) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + + if hasattr(self._driver, name): + try: + attrib = getattr(self._driver, name) + if not callable(attrib): + return attrib + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + return _wrap + + raise AttributeError(name) + + +class EventFiringWebElement(object): + """" + A wrapper around WebElement instance which supports firing events + """ + + def __init__(self, webelement, ef_driver): + """ + Creates a new instance of the EventFiringWebElement + """ + self._webelement = webelement + self._ef_driver = ef_driver + self._driver = ef_driver.wrapped_driver + self._listener = ef_driver._listener + + @property + def wrapped_element(self): + """Returns the WebElement wrapped by this EventFiringWebElement instance""" + return self._webelement + + def click(self): + self._dispatch("click", (self._webelement, self._driver), "click", ()) + + def clear(self): + self._dispatch("change_value_of", (self._webelement, self._driver), "clear", ()) + + def send_keys(self, *value): + self._dispatch("change_value_of", (self._webelement, self._driver), "send_keys", value) + + def find_element(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_element", (by, value)) + + def find_elements(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value)) + + def find_element_by_id(self, id_): + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_name(self, name): + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_link_text(self, link_text): + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, link_text): + return self.find_elements(by=By.LINK_TEXT, value=link_text) + + def find_element_by_partial_link_text(self, link_text): + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_tag_name(self, name): + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_xpath(self, xpath): + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_class_name(self, name): + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def _dispatch(self, l_call, l_args, d_call, d_args): + getattr(self._listener, "before_%s" % l_call)(*l_args) + try: + result = getattr(self._webelement, d_call)(*d_args) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + getattr(self._listener, "after_%s" % l_call)(*l_args) + return _wrap_elements(result, self._ef_driver) + + def __setattr__(self, item, value): + if item.startswith("_") or not hasattr(self._webelement, item): + object.__setattr__(self, item, value) + else: + try: + object.__setattr__(self._webelement, item, value) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + + def __getattr__(self, name): + + def _wrap(*args): + try: + result = attrib(*args) + return _wrap_elements(result, self._ef_driver) + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + + if hasattr(self._webelement, name): + try: + attrib = getattr(self._webelement, name) + if not callable(attrib): + return attrib + except Exception, e: + self._listener.on_exception(e, self._driver) + raise e + return _wrap + + raise AttributeError(name) diff --git a/selenium/webdriver/support/events.py b/selenium/webdriver/support/events.py new file mode 100644 index 0000000..1b3d46a --- /dev/null +++ b/selenium/webdriver/support/events.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# +# Copyright 2011 Software Freedom Conservancy. +# +# 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 abstract_event_listener import AbstractEventListener +from event_firing_webdriver import EventFiringWebDriver diff --git a/selenium/webdriver/support/expected_conditions.py b/selenium/webdriver/support/expected_conditions.py new file mode 100644 index 0000000..a88c277 --- /dev/null +++ b/selenium/webdriver/support/expected_conditions.py @@ -0,0 +1,283 @@ +# +# Copyright 2012 WebDriver committers +# Copyright 2012 Software Freedom Conservancy. +# +# 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 NoSuchElementException +from selenium.common.exceptions import NoSuchFrameException +from selenium.common.exceptions import StaleElementReferenceException +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import NoAlertPresentException + +""" + * Canned "Expected Conditions" which are generally useful within webdriver + * tests. +""" +class title_is(object): + """An expectation for checking the title of a page. + title is the expected title, which must be an exact match + returns True if the title matches, false otherwise.""" + def __init__(self, title): + self.title = title + + def __call__(self, driver): + return self.title == driver.title + +class title_contains(object): + """ An expectation for checking that the title contains a case-sensitive + substring. title is the fragment of title expected + returns True when the title matches, False otherwise + """ + def __init__(self, title): + self.title = title + + def __call__(self, driver): + return self.title in driver.title + +class presence_of_element_located(object): + """ An expectation for checking that an element is present on the DOM + of a page. This does not necessarily mean that the element is visible. + locator - used to find the element + returns the WebElement once it is located + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_element(driver, self.locator) + +class visibility_of_element_located(object): + """ An expectation for checking that an element is present on the DOM of a + page and visible. Visibility means that the element is not only displayed + but also has a height and width that is greater than 0. + locator - used to find the element + returns the WebElement once it is located and visible + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + try: + return _element_if_visible(_find_element(driver, self.locator)) + except StaleElementReferenceException: + return False + +class visibility_of(object): + """ An expectation for checking that an element, known to be present on the + DOM of a page, is visible. Visibility means that the element is not only + displayed but also has a height and width that is greater than 0. + element is the WebElement + returns the (same) WebElement once it is visible + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + return _element_if_visible(self.element) + +def _element_if_visible(element): + return element if element.is_displayed() else False + +class presence_of_all_elements_located(object): + """ An expectation for checking that there is at least one element present + on a web page. + locator is used to find the element + returns the list of WebElements once they are located + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_elements(driver, self.locator) + +class text_to_be_present_in_element(object): + """ An expectation for checking if the given text is present in the + specified element. + locator, text + """ + def __init__(self, locator, text_): + self.locator = locator + self.text = text_ + + def __call__(self, driver): + try : + element_text = _find_element(driver, self.locator).text + return self.text in element_text + except StaleElementReferenceException: + return False + +class text_to_be_present_in_element_value(object): + """ + An expectation for checking if the given text is present in the element's + locator, text + """ + def __init__(self, locator, text_): + self.locator = locator + self.text = text_ + + def __call__(self, driver): + try: + element_text = _find_element(driver, + self.locator).get_attribute("value") + if element_text: + return self.text in element_text + else: + return False + except StaleElementReferenceException: + return False + +class frame_to_be_available_and_switch_to_it(object): + """ An expectation for checking whether the given frame is available to + switch to. If the frame is available it switches the given driver to the + specified frame. + """ + def __init__(self, locator): + self.frame_locator = locator + + def __call__(self, driver): + try: + driver.switch_to_frame(self.frame_locator) + return True + except NoSuchFrameException: + return False + +class invisibility_of_element_located(object): + """ An Expectation for checking that an element is either invisible or not + present on the DOM. + + locator used to find the element + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + try: + return not _find_element(driver, self.locator).is_displayed() + except (NoSuchElementException, StaleElementReferenceException): + # In the case of NoSuchElement, returns true because the element is + # not present in DOM. The try block checks if the element is present + # but is invisible. + # In the case of StaleElementReference, returns true because stale + # element reference implies that element is no longer visible. + return True + +class element_to_be_clickable(object): + """ An Expectation for checking an element is visible and enabled such that + you can click it.""" + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + element = visibility_of_element_located(self.locator)(driver) + if element and element.is_enabled(): + return element + else: + return False + +class staleness_of(object): + """ Wait until an element is no longer attached to the DOM. + element is the element to wait for. + returns False if the element is still attached to the DOM, true otherwise. + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + try: + # Calling any method forces a staleness check + self.element.is_enabled() + return False + except StaleElementReferenceException as expected: + return True + +class element_to_be_selected(object): + """ An expectation for checking the selection is selected. + element is WebElement object + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + return self.element.is_selected() + +class element_located_to_be_selected(object): + """An expectation for the element to be located is selected. + locator is a tuple of (by, path)""" + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_element(driver, self.locator).is_selected() + +class element_selection_state_to_be(object): + """ An expectation for checking if the given element is selected. + element is WebElement object + is_selected is a Boolean." + """ + def __init__(self, element, is_selected): + self.element = element + self.is_selected = is_selected + + def __call__(self, ignored): + return self.element.is_selected() == self.is_selected + +class element_located_selection_state_to_be(object): + """ An expectation to locate an element and check if the selection state + specified is in that state. + locator is a tuple of (by, path) + is_selected is a boolean + """ + def __init__(self, locator, is_selected): + self.locator = locator + self.is_selected = is_selected + + def __call__(self, driver): + try: + element = _find_element(driver, self.locator) + return element.is_selected() == self.is_selected + except StaleElementReferenceException: + return False + +class alert_is_present(object): + """ Expect an alert to be present.""" + def __init__(self): + pass + + def __call__(self, driver): + try: + alert = driver.switch_to_alert() + alert.text + return alert + except NoAlertPresentException: + return False + +def _find_element(driver, by): + """ Looks up an element. Logs and re-raises WebDriverException if thrown. + Method exists to gather data for + http://code.google.com/p/selenium/issues/detail?id=1800 + """ + try : + return driver.find_element(*by) + except NoSuchElementException as e: + raise e + except WebDriverException as e: + raise e + + +def _find_elements(driver, by): + try : + return driver.find_elements(*by) + except WebDriverException as e: + raise e + diff --git a/selenium/webdriver/support/select.py b/selenium/webdriver/support/select.py new file mode 100644 index 0000000..5bd103a --- /dev/null +++ b/selenium/webdriver/support/select.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# +# Copyright 2011 Software Freedom Conservancy. +# +# 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.webdriver.common.by import By +from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException + +class Select: + + def __init__(self, webelement): + """ + Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not, + then an UnexpectedTagNameException is thrown. + + :Args: + - webelement - element SELECT element to wrap + + Example: + from selenium.webdriver.support.ui import Select \n + Select(driver.find_element_by_tag_name("select")).select_by_index(2) + """ + if webelement.tag_name.lower() != "select": + raise UnexpectedTagNameException( + "Select only works on