0

Renaming back 'py' directory, use of 'legacy_create_init' argument resolved the name collision.

Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/SeleniumHQ/selenium
Cr-Mirrored-Commit: b300c358f65f33c0cf43177316f433601c027bdb
This commit is contained in:
Alexei Barantsev
2019-07-26 18:48:34 +03:00
parent b3907b2737
commit 70795f3849
237 changed files with 22834 additions and 0 deletions
BUILD.bazelCHANGESMANIFEST.inREADME.rstbuild.descconftest.py
docs
Makefile
source
api.rst
common
conf.pyindex.rst
webdriver
webdriver_android
webdriver_chrome
webdriver_firefox
webdriver_ie
webdriver_opera
webdriver_phantomjs
webdriver_remote
webdriver_safari
webdriver_support
webdriver_webkitgtk
python.iml
selenium
setup.cfgsetup.py
test
__init__.pyrun_pytest.py
selenium
__init__.py
webdriver
__init__.py
chrome
common
firefox
ie
marionette
remote
safari
support
unit
tox.ini

37
BUILD.bazel Normal file

@ -0,0 +1,37 @@
genrule(
name = "get-attribute",
srcs = ["//javascript/webdriver/atoms:get-attribute.js"],
outs = ["selenium/webdriver/remote/getAttribute.js"],
cmd = "cp $< $@",
)
genrule(
name = "is-displayed",
srcs = ["//javascript/atoms/fragments:is-displayed.js"],
outs = ["selenium/webdriver/remote/isDisplayed.js"],
cmd = "cp $< $@",
)
py_library(
name = "main",
srcs = glob(["selenium/**/*.py"]),
data = [
":get-attribute",
":is-displayed",
],
imports = ["."],
visibility = ["//visibility:public"],
)
py_test(
name = "unit",
size = "small",
srcs = glob([
"test/unit/**/*.py",
]) + [ "test/run_pytest.py" ],
main = "test/run_pytest.py",
deps = [
":main",
],
legacy_create_init = False,
)

701
CHANGES Normal file

@ -0,0 +1,701 @@
Selenium 4.0 Alpha 1
* Update driver initialisation to use service and option objects
* turn on keep-alive by default for remote connections (#7072)
* Fix ConnectionResetError
* Add new Cast commands
* Suggest download Microsoft Webdriver over HTTPS
* Clear PoolManager in remote_connection to ensure sockets are closed
* remove --disable-gpu option for headless Chrome
* Add support for the New Window command (#6873)
* Update docstrings in Options classes to allow documentation to highlight Return values
* Fix typos in select.py (#6925)
* Remove native events handling code
* Deleting unused imports, fixing flake8 issues
* Remove unused port selection in IE Driver
* Enabling tests xpassed in Chrome
* Pretty-printing code samples
* remove all deprecated methods and args from Python bindings
* Fix DeprecationWarning: invalid escape sequence
* Don't override browser options with desired capabilities by default in WebKitGTK (#6814)
* Add WebKitGTK to API docs (#6815)
* Subclass options classes from a common base class (#6522)
* Update Sphinx (#6728)
* WebDriverWait: update documentation for until and until_not (#6711)
* Fix typo in description of WebDriver class (#6724)
* add strictFileInteractability to acceptable W3C capabilities
* Deprecate Blackberry Driver support
* Fixing/tidying docstring.
Selenium 3.141.0
* Bump version to a better approximation of Π
* Improved Test build targets
* fix os path in test for Windows
* use 'NUL' for /dev/null on Windows
* Update ctor docstrings to explain that a directory passed in is cloned. Fixes #6542
* Allow passing of service_args to Safari. Fixes #6459
* Remove element equals url
* Improved WebExtension support
Selenium 3.14.1
* Fix ability to set timeout for urllib3 (#6286)
* get_cookie uses w3c endpoint when compliant
* Remove body from GET requests (#6250)
* Fix actions pause for fraction of a second (#6300)
* Fixed input pausing for some actions methods
* Capabilities can be set on Options classes
* WebElement rect method is now forward compatible for OSS endpoints (#6355)
* Deprecation warnings now have a stacklevel of 2
* keep_alive can now be set on Webdriver init (#6316)
* isDisplayed atom is now used for all w3c compliant browser, fixing issue with Safari 12
Selenium 3.14.0
* Fix doc of URL-related ExpectedCondition (#6236)
* Added ExpectedCondition invisibility_of_element
* Swap out httplib for urllib3
* Be consistent with webdriver init kwarg service_log_path (#5725)
Selenium 3.13.0
* Add executing Chrome devtools command (#5989)
* fix incorrect w3c action encoding in python client (#6014)
* Implement context manager for WebDriver
* Stop sending "windowHandle" param in maximize_window command for w3c
Selenium 3.12.0
* Add desired_capabilities keyword to IE and Firefox drivers for driver consitency
* Fix bug with creating Safari webdriver instance (#5578)
* Add support for Safari extension command
* Deprecate Options `set_headless` methods in favor of property setter
* Only set --disable-gpu for Chrome headless when on Windows
* Add selenium User-Agent header (#5696)
* Remote webdriver can now be started when passing options
* All Options.to_capabilities now start with default DesiredCapabilities
* Improve the error message that is raised when safaridriver cannot be found (#5739)
* IeOptions class is now imported to selenium.webdriver
* Remove the beta `authenticate` methods from `Alert`
Selenium 3.11.0
No changes just keeping python version in step with the rest of the project.
Selenium 3.10.0
* make tests to check clicking on disabled element work for w3c compliant drivers (#5561)
* add docstring for InvalidElementStateException. Fixes #5520
* Deleting unused imports
* Making python specification in IDEA project more generic
* It should be possible to use a custom safaridriver executable to run Selenium's test suite.
Selenium 3.9.0
* Add docstrings to WebElement find methods (#5384)
* Additional data in unexpected alert error is now handled for w3c drivers (#5416)
* Allow service_args to be passed into Firefox WebDriver (#5421)
* Fix bug introduced with response logging in 3.8.1 (#5362)
Selenium 3.8.1
* Fix bug when creating an Opera driver (#5266)
* Stop sending sessionId in w3c payload. (#4620)
* Fix issue with w3c actions releasing on element (#5180)
* A more descriptive log message is displayed if the port cannot be connected (#2913)
* Initialize Alert object by calling alert.text (#1863)
* PhantomJS is now deprecated, please use either Chrome or Firefox in headless mode
* Legacy Firefox driver: ensuring copy of profile dir, its 'extensions' subdir and 'user.js' file are writable. (#1466)
Selenium 3.8.0
* Firefox options can now be imported from selenium.webdriver as FirefoxOptions (#5120)
* Headless mode can now be set in Chrome Options using `set_headless`
* Headless mode can now be set in Firefox Options using `set_headless`
* Add the WebKitGTK WebDriver and options class (#4635)
* Browser options can now be passed to remote WebDriver via the `options` parameter
* Browser option parameters are now standardized across drivers as `options`. `firefox_options`,
`chrome_options`, and `ie_options` are now deprecated
* Added missing W3C Error Codes (#4556)
* Support has been removed for Python versions 2.6 and 3.3
Selenium 3.7.0
* need to pass applicable environment variables to tox
* Fix active_element for w3c drivers (#3979)
* add support for minimize command
* add support for fullscreen command
* window rect commands should fail on firefox and remote (legacy)
* Fix python backward compatibility for window commands (#4937)
* Update docstrings to specify the type of path needed to install firefox addons. (#4881)
* Update python chromeOptions key for capabilities (#4622)
* Fix python pause action implementation (#4795)
Selenium 3.6.0
* Fix package name in python webelement module (#4670)
* Fix python driver examples (#3872)
* No need to multiply pause by 1000
* Add pause to action chains
* only check for proxyType once
* lowercase proxy type for w3c payload in python #4574
* guarding against null return value from find_elements in python #4555
* remove unnecessary pytest marking, address flake8 issues
* allow IE WebDriver to accept IE Options
* add IE Options class
* convert OSS capabilities to W3C equivalent for W3C payload
* Add Safari to API docs
Selenium 3.5.0
* Numerous test fixes
*Iterate over capabilities in a way to support py2.7 and py3
* Fix W3C switching to window by name.
* Support GeckoDriver addon install/uninstall commands #4215.
* Move firefox_profile into moz:firefoxOptions.
* Filter non-W3C capability names out of alwaysMatch.
* Honor cmd line args passed to Service ctor (#4167)
* Add expected conditions based on URL to Python Expected Conditions #4160
* Add network emulation to Chrome Python bindings (#4011)
* add warning when saving incorrectly named screenshot (#4141)
Selenium 3.4.3
* Fix EventFiringWebdriver and WebElement to raise AttributeError on missing attributes. (#4107)
* unwrap WebElements inside dicts
Selenium 3.4.2
* translate move_by_offset command to w3c
* Update capabilities properly instead of assuming dict structure. Fixes #3927
* Add missing file for Chrome options to API docs.
* Add Chrome options module to API docs.
Selenium 3.4.1
* Add back the ability to set profile when using Firefox 45ESR. Fixes #3897
Selenium 3.4.0
* Correct usage of newSession around `firstMatch` and `alwaysMatch`
* Remove superfluous capabilities that are not needed
* Add expected condition that waits for all found elements to be visible (#3532)
* Allow methods wrapped by EventFiringWebDriver and EventFiringWebElement (#806)
* Dropping `javascriptEnabled` capability for real browsers
* Use W3C check from parent object instead of assuming from capabilities
* Bump example source distribution to match latest release.
* Replace TypeError with KeyError in remote webdriver error handler code (#3826)
* When testing Marionette use default capabilities in testing
* Conform to the api of urllib2 for adding header for a request (#3803)
* Add `text` key to alert#sendKeys parameters for W3C Endpoint
* Location once scrolled into view should use W3C executeScript endpoint not JSONWP
* Fixed the usage information in documentation of "save_screenshot". (#3804)
* Add Element Not Interactable exception
* Clean up imports in error handler
* flake8 cleanup
Selenium 3.3.3
* make w3c execute_script commands unique
Selenium 3.3.2
* Update window commands to use W3C End points
* Update Alert when in W3C mode to use W3C Endpoints
* Update to new W3C Execute Script end points
* Add setting/getting proxy details to Firefox Options
* Deprecate the use of browser profile when instantiating a session
* Update start session to handle the W3C New Session
* Add get/set window rect commands
* Add InvalidArgumentException
* When passing in `text` to send_keys, make sure we send a string not array
* Fix string decoding in remote connection (#3663)
* Fix indentation to satisfy PEP8
* Try use old way of setting page load timeout if new way fails. Fixes #3654
* fix file uploads for Firefox
* Run unit tests on Python 3.3, 3.4, and 3.5 (#3638)
* Fix indentation in double_click.
* Fix non-W3C page load timeout setting.
Selenium 3.3.1
* Fix encoding of basic auth header when using Python 3 Fixes #3622
* Add initial unit test suite
* Update W3C Timeout setting to be in line with the specification
* support.ui.Select class inherits from object (#3067)
* fix bug in proxy constructor that some properties are not proper set (#3459)
* Fix flake8 issues (#3628)
Selenium 3.3.0
** Note ** If you are updating to this version, please also update GeckoDriver to v0.15.0
* Fix python HTTPS encoding for python driver (#3379)
* Allow Firefox preferences to be set directly in Options
* Fix shutdown and process termination (#3263)
* Preventing exception if log_path is none or empty. Fixes #3128
* Add the W3C capability to Firefox for accepting insecure certificates
* Initial implementation of Pointer Actions
* Only skip tests if driver name matches a directory name.
* Update calls that return a pure object with keys to look for 'value' key
* Initial W3C Actions support
* fix docs output directory
Selenium 3.0.2
* Add support for W3C Get Active Element
* Return when we use executeScript for faking WebElement.get_property
* Expand user paths and resolve absolute path for Chrome extensions
* Add support for verbose logging and custom path to EdgeDriver
* Update TakeElementScreenshot to match WebDriver specification
* Raise WebDriverException when FirefoxBinary fails to locate binary
* Fix getting IP for python 3
* Write Service log to DEVNULL by default
* Only attempt to remove the Firefox profile path if one was specified
* Added context manager for chrome/content in Firefox
Selenium 3.0.1
* Fix regressions with python 3
* Add support for Safari Technology Preview
Selenium 3.0.0
* new FirefoxDriver ctor precedence logic and moz:firefoxOptions support (#2882)
* Add W3C Set Window Position and W3C Get Window Position
* enable log path setting from firefox webdriver (#2700)
* Correct encoding of getAttribute.js and only load it once. Fixes #2785
* Encode the isDisplayed atom and only load it once
Selenium 3.0.0.b3
* Use atoms for get_attribute and is_displayed when communicating with
a w3c compliant remote end.
* Make it possible to specialise web element
Selenium 3.0.0.b2
* Updated Marionette port argument to match other drivers.
Selenium 3.0.0.b1
* Fix basestring reference to work with python 3. Fixes #1820
* Correct Length conditional when filtering in PhantomJS. Fixes #1817
* Fix send keys when using PUA keys e.g. Keys.RIGHT #1839
* Fix cookie file leak in PhantomJS #1854
* Use the correct binary path when using Marionette
* Fixed: Unhelpful error message when PhantomJS exits. (#2173 #2168)
* Fix broken link to python documentation (#2159)
* Attempt to remove Firefox profile when using Marionette
* Ensure all capabilities are either within desiredCapabilities or requiredCapabilities
* Correct the expected capability name for the Firefox profile
* Add Firefox options to capabilities
* Visibility_of_all implies it only returns elements if all visible (#2052)
* Find visible elements (#2041)
* Pass the firefox_profile as a desired capability in the Python client when using a remote server
* Avoid checking exception details for invalid locators due to differences in server implementations
* Handle capabilities better with Marionette and GeckoDriver
* Updated the maxVersion of FirefoxDriver xpi maxVersion to work with Firefox 47.0.1
* Remove Selenium RC support
Selenium 2.53.0
* Adding Options object for use with Python FirefoxDriver
* Fixed improper usage of super in exceptions module
* create a temp file for cookies in phantomjs if not specified
* Pass in the executable that FirefoxBinary finds to the service if there isnt one passed in as a kwarg or capability
* Applied some DRY and extracted out the keys_to_typing()
* Fix deselecting options in <select>
Selenium 2.52.0
* Fixing case where UnexpectedAlertException doesn't get the alert_text in the error object
* Firefox: Actually use launch_browser timeout Fixes #1300
Selenium 2.51.1
* correcting bundling issue missing README.rst file
Selenium 2.51.0
* Firefox updates (see java changelog)
Selenium 2.50.1
* Fixing error message handling. Fixes issue #1497
* Fixing error message handling. Fixes issue #1507
* Update webelement to handle W3C commands for size/location and rect
* rewrite click scrolling tests to match the Java ones
Selenium 2.50.0
* handle potential URLError from sending shutdown, set self.process to None after it's already been quit
* Add support for submit() with W3C compliant endpoint
Selenium 2.49.1
* Ensure you can close stream before attempting to close it.
* message response may cause json loads ValueError when it's not actually json
and just a string (like the message that occurs when firefox driver thinks
another element will receive the click)
* Cleanup some error handling when sniffing what protocol you are speaking
Selenium 2.49.0
* Have Firefox service write to a file instead of PIPE
* on osx for firefox, fallback to checking homebrew install, if the default isn't there
* Added Firefox path variable for string placeholder
* Update README to show Python 3.2+
* refactoring all the service classes to use a common one.
* Add Firefox specific command to switch context between Browser content and Browser chrome
* updating files after go copyright:update
* Use specificationLevel to know that we are speaking GeckoDriver
* Bug fixes: #1294, #1186
Selenium 2.48.0
* Update error pulling to match spec when we encounter a spec compliant browser.
* Disable tests that are not working with Marionette when running Marionette tests
* Add the ability to run python marionette tests
* Python 3 compatibility for remote Authorization
* changing casing of children finding tests
Selenium 2.47.3
* Bring back py 3 support
Selenium 2.47.2
* Fix running Edge driver locally on win10
* adding repr to WebDriver and WebElement
Selenium 2.47.1
* Fix the issue of deleting the profile when shutting down Firefox
* WebElement __eq__ compares against more types
* Issues fixed: 850
Selenium 2.47.0
* Add in support for when communicating with a Spec compliant browsers
* Initial support for Edge using EdgeDriver
* Issues fixed: 818
Selenium 2.46.1
* Adding ability to make remote call for webelement screenshots in accordance to the W3C spec
* Adding api to authenticate HTTP Auth modal dialogs via driver.switch_to.alert (beta)
* Add rebeccapurple to Color Object
* Add element screenshot
* Add service handler and minimal update to driver to use service for Marionette
* Add the ability to start FirefoxDriver backed with Marionette via a capability
* support socket timeout for connections
* free_port checks if port is available on all interfaces
* Allow error handling to handle both current errors and w3c errors
* Update find_elements to match spec
* phantomjs: service: remove unused import of signal
* phantomjs: add port information to WebDriverException
* Issues fixed (Github): 478, 612, 734, 780
Selenium 2.46.0
* Firefox support up to 38
* BlackBerry browser support
* remove Presto-Opera support
* firefox extension extraction fixes
* process management fixes with phantomjs
* Comparing remote web element for equality does not require a remote command
* Issues Fixed: (gcode) 8493, 8521, 8498, 8274, 8497, 5923
* Issues Fixed: (github) 401
Selenium 2.45.0
* Firefox support up to 35, support for native events up to 34.
* Make Opera driver support also the new Blink based Opera
* README: Fix the Yahoo example
* WebElement docstring fixes
* Add debugger_address option to the ChromeDriver options list to optionally instruct ChromeDriver to wait for the target devtools instance to be started at a given host:ip
* Set default value for PhantomJS process reference
* Allow setting of FileDetector for send_keys
* Pass info to TimeoutException in WebDriverWait
* Issues Fixed: 8065, 8310, 8539
Selenium 2.44.0
* (previous release person forgot to add release notes! DAVID!)
Selenium 2.43.0
* Expand WebElement.get_attribute API docs
* firefox may be installed without admininstrator privileges
and therefore there may be no HKEY_LOCAL_MACHINE entry. Issue #7784
* UnexpectedAlertPresentException should contain the alert text in python too. Issue #7745
* don't mutate the global 'ignored exceptions', take a copy of the globally specified ones, change the
global to be a tuple instead. Issue #7725
* raise exception when the firefox binary isn't actually found, which usually implies the upgrade failed (on windows) Issue #6092 ,#6847
* Fixing NameError: global name 'options' is not defined.
* Remove unused import subprocess.PIPE
* Redirect Firefox output to /dev/null by default Fixes Issue #7677
* More flexible management of http timeouts in Selenium RC python client
* Generate Python API docs for selenium.webdriver.chrome.options. Fixes issue #7471
* Use 127.0.0.1 as localhost name resolving might fail on some systems
Selenium 2.42.1
* Fixed Py3 issues
* Make firefox_binary.py and firefox_profile.py not executable
* Make exceptions Python 3 compatible
Selenium 2.42
* Support for Firefox 29 Native Events
* "remote_url" and "remote_browser" parameters for "./go test_remote".
* missing __init__ in new android module
* issue #7304 Fix memory leak caused by __del__ in PhantomJS
* File upload using remotedriver on python3
* Updating xpi install to align with mozprofile
* command_executor should also support unicode strings as well.
Selenium 2.41
* Support for Firefox 28
* deprecating switch_to_* in favour of driver.switch_to.*
Selenium 2.40
* Support for Firefox 27
* Fixes related to http connection
* Fix for phantomjs running on windows #6736
Selenium 2.39
* Support for Firefox 26
Selenium 2.38.4
* keep-alive can't be used for phantomjs / IE, fix for that and tested for py3 :)
Selenium 2.38.3
* really supporting py3 :)
Selenium 2.38.2
* py3 support (once again)
Selenium 2.38.1
* fix packaging problem where firefox/webdriver_prefs.json was missing
Selenium 2.38
* Support for Firefox 25
* FirefoxProfile now using common webdriver.json instead of having our own copy in py
- behavior change to the preferences is that they now should be treated
like raw types rather than strings and allow the json library to translate
the types appropriated (e.g. True => true)
* Set proper 'Accept' request header so that Python bindings work with some old WebDriver implementations that reply 404 to requests with no 'Accept' set.
* handle redirect response explicitly (since switching to using keep-alive)
* phantomjs service needs to really kill the spawned process Issue #5921
* removing old api endpoints from command listing
* using keep-alive for remote connection
* adjusting phantomjs subprocess.Popen
* ActionsChains.send_keys should use <session>/keys endpoint Issue #6348
* fix TypeError in chrome_options.extensions for Python3.x
* Other Bugs Fixed: #6531, #6513, #4569, #6454
Selenium 2.37.2
* fix regression added with unicode fix
* Bug fix #6360
Selenium 2.37.1
* fix find_elements on webelement using unicode locators and py 2.7
Selenium 2.37
* repackage with fix for Firefox native events on Linux
* fix issue with unicode By locators w/ python 2.7 #6430
Selenium 2.36
* Added Safari WebDriver. Fixes issue 5352.
* fix platform for safari caps
* Convert all offsets/coordinates/speeds into integers
* Fix drag and drop by offset behaviour
* Fix initialization of Proxy by capabilities when proxyType is set
* Enable SOCKS proxy support
* Validation of passed locator for find_element(s) methods #5690
* Adding support for /status, /sessions commands
* Doc fixes
* ability to set Chrome extensions by using base64 encoded strings #4013
* fix logic regarding Select.select_by_visible_text #3910
* Bugs fixed: #6165, #6231
Selenium 2.35
* Remove duplicate 'get screenshot as file' methods. Add method 'get_screenshot_as_png'
* fixing UnicodeEncodeError on get attribute of webelement
Selenium 2.34
* Corrected webdriverbackedselenium session handling. Fixes issue 4283
* Corrected use of basestring for python 3. Fixes issue 5924
* Support for Firefox 22
* Added support for logging from the browser
* corrected proxy handling on FirefoxProfile
* Corrected handling of chrome extensions. Fixes issue 5762
Selenium 2.33
* getText() ignores elements in the <head>
* Adding both official and informal string representations to Color object.
* Replace distutils.dir_util by shutil
* Allow finding firefox binary at ProgramFiles(x86) on windows(64 bit)
* Py3 compatible winreg import and content-type access
Selenium 2.32
* Support for FF20 Native Events
* Python 3 support
* Misc Python 3 patches
* Allow easy FirefoxBinary subclassing
Selenium 2.31
* Support for FF19 native events
* web element equality is now in conformance with other language bindings
Selenium 2.30
* Allow env to be specified for the chromedriver service
* Allow log path to be specified for phantomjs driver service.
* Bug Fixes: 4608 4940 4974 5034 5075
Selenium 2.29
* Allow subclassing of driver and have the ability to send_keys Issue 4877, 5017
* Simplifying save_screenshot and allow phantomjs to take screenshots
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

20
MANIFEST.in Normal file

@ -0,0 +1,20 @@
prune *
recursive-include selenium/webdriver *.py
recursive-include selenium/webdriver/common *.py
recursive-include selenium/webdriver/common/actions *.py
recursive-include selenium/webdriver/common/html5 *.py
recursive-include selenium/common *.py
recursive-include selenium/webdriver/chrome *.py
recursive-include selenium/webdriver/opera *.py
recursive-include selenium/webdriver/phantomjs *.py
recursive-include selenium/webdriver/firefox *.py *.xpi *.json
recursive-include selenium/webdriver/ie *.py
recursive-include selenium/webdriver/edge *.py
recursive-include selenium/webdriver/remote *.py *.js
recursive-include selenium/webdriver/support *.py
include selenium/selenium.py
include selenium/__init__.py
include CHANGES
include README.rst
include LICENSE
recursive-include selenium.egg-info *

1
README.rst Symbolic link

@ -0,0 +1 @@
docs/source/index.rst

85
build.desc Normal file

@ -0,0 +1,85 @@
# py_test targets emplicitly 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",
deps = [ ":test_ff" ],
resources = [
{ "//third_party/js/selenium:webdriver_prefs" : "selenium/webdriver/firefox/webdriver_prefs.json" },
{ "//third_party/js/selenium:webdriver" : "selenium/webdriver/firefox/" }
],
browsers = [ "ff" ])
py_test(
name = "marionette_test",
deps = [ ":test_marionette" ],
browsers = [ "marionette" ])
py_test(
name = "blackberry_test",
deps = [ ":test_blackberry" ],
browsers = [ "blackberry" ])
py_test(
name = "chrome_test",
deps = [ ":test_chrome" ],
browsers = [ "chrome" ])
py_test(
name = "ie_test",
deps = [ ":test_ie" ],
browsers = [ "ie" ])
py_test(
name = "edge_test",
deps = [":test_edge"],
browsers = [ "edge"])
py_test(
name = "remote_firefox_test",
deps = [ ":test_remote_firefox" ],
browsers = [ "remote_firefox" ])
py_test(
name = "safari_test",
deps = [ ":test_safari" ],
browsers = [ "safari" ])
py_test(
name = "test",
browsers = [
"chrome",
"ff",
"marionette",
"ie",
"edge",
"blackberry",
"remote_firefox",
"safari",
])
py_docs(
name = "docs"
)
py_install(
name = "install"
)
py_prep(
name = "prep",
deps = [
"//cpp:noblur",
"//cpp:noblur64",
"//javascript/atoms/fragments:is-displayed",
"//third_party/js/selenium:webdriver",
"//third_party/js/selenium:webdriver_prefs",
"//javascript/webdriver/atoms:get-attribute"
])

219
conftest.py Normal file

@ -0,0 +1,219 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 socket
import subprocess
import sys
import time
import pytest
from _pytest.skipping import MarkEvaluator
from selenium import webdriver
from selenium.webdriver import DesiredCapabilities
from test.selenium.webdriver.common.webserver import SimpleWebServer
from test.selenium.webdriver.common.network import get_lan_ip
if sys.version_info[0] == 3:
from urllib.request import urlopen
else:
from urllib import urlopen
drivers = (
'BlackBerry',
'Chrome',
'Edge',
'Firefox',
'Ie',
'Marionette',
'Remote',
'Safari',
'WebKitGTK',
)
def pytest_addoption(parser):
parser.addoption('--driver', action='append', choices=drivers, dest='drivers',
metavar='DRIVER',
help='driver to run tests against ({})'.format(', '.join(drivers)))
parser.addoption('--browser-binary', action='store', dest='binary',
help='location of the browser binary')
parser.addoption('--driver-binary', action='store', dest='executable',
help='location of the service executable binary')
parser.addoption('--browser-args', action='store', dest='args',
help='arguments to start the browser with')
def pytest_ignore_collect(path, config):
drivers_opt = config.getoption('drivers')
_drivers = set(drivers).difference(drivers_opt or drivers)
if drivers_opt:
_drivers.add('unit')
parts = path.dirname.split(os.path.sep)
return len([d for d in _drivers if d.lower() in parts]) > 0
driver_instance = None
@pytest.fixture(scope='function')
def driver(request):
kwargs = {}
try:
driver_class = request.param
except AttributeError:
raise Exception('This test requires a --driver to be specified.')
# conditionally mark tests as expected to fail based on driver
request.node._evalxfail = request.node._evalxfail or MarkEvaluator(
request.node, 'xfail_{0}'.format(driver_class.lower()))
if request.node._evalxfail.istrue():
def fin():
global driver_instance
if driver_instance is not None:
driver_instance.quit()
driver_instance = None
request.addfinalizer(fin)
# skip driver instantiation if xfail(run=False)
if not request.config.getoption('runxfail'):
if request.node._evalxfail.istrue():
if request.node._evalxfail.get('run') is False:
yield
return
driver_path = request.config.option.executable
options = None
global driver_instance
if driver_instance is None:
if driver_class == 'BlackBerry':
kwargs.update({'device_password': 'password'})
if driver_class == 'Firefox':
kwargs.update({'capabilities': {'marionette': False}})
options = get_options(driver_class, request.config)
if driver_class == 'Marionette':
driver_class = 'Firefox'
options = get_options(driver_class, request.config)
if driver_class == 'Remote':
capabilities = DesiredCapabilities.FIREFOX.copy()
kwargs.update({'desired_capabilities': capabilities})
options = get_options('Firefox', request.config)
if driver_class == 'WebKitGTK':
options = get_options(driver_class, request.config)
if driver_path is not None:
kwargs['executable_path'] = driver_path
if options is not None:
kwargs['options'] = options
driver_instance = getattr(webdriver, driver_class)(**kwargs)
yield driver_instance
if MarkEvaluator(request.node, 'no_driver_after_test').istrue():
driver_instance = None
def get_options(driver_class, config):
browser_path = config.option.binary
browser_args = config.option.args
options = None
if browser_path or browser_args:
options = getattr(webdriver, '{}Options'.format(driver_class))()
if driver_class == 'WebKitGTK':
options.overlay_scrollbars_enabled = False
if browser_path is not None:
options.binary_location = browser_path
if browser_args is not None:
for arg in browser_args.split():
options.add_argument(arg)
return options
@pytest.fixture(scope='session', autouse=True)
def stop_driver(request):
def fin():
global driver_instance
if driver_instance is not None:
driver_instance.quit()
driver_instance = None
request.addfinalizer(fin)
def pytest_exception_interact(node, call, report):
if report.failed:
global driver_instance
if driver_instance is not None:
driver_instance.quit()
driver_instance = None
@pytest.fixture
def pages(driver, webserver):
class Pages(object):
def url(self, name):
return webserver.where_is(name)
def load(self, name):
driver.get(self.url(name))
return Pages()
@pytest.fixture(autouse=True, scope='session')
def server(request):
drivers = request.config.getoption('drivers')
if drivers is None or 'Remote' not in drivers:
yield None
return
_host = 'localhost'
_port = 4444
_path = '../buck-out/gen/java/server/src/org/openqa/grid/selenium/selenium.jar'
def wait_for_server(url, timeout):
start = time.time()
while time.time() - start < timeout:
try:
urlopen(url)
return 1
except IOError:
time.sleep(0.2)
return 0
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
url = 'http://{}:{}/wd/hub'.format(_host, _port)
try:
_socket.connect((_host, _port))
print('The remote driver server is already running or something else'
'is using port {}, continuing...'.format(_port))
except Exception:
print('Starting the Selenium server')
process = subprocess.Popen(['java', '-jar', _path])
print('Selenium server running as process: {}'.format(process.pid))
assert wait_for_server(url, 10), 'Timed out waiting for Selenium server at {}'.format(url)
print('Selenium server is ready')
yield process
process.terminate()
process.wait()
print('Selenium server has been terminated')
@pytest.fixture(autouse=True, scope='session')
def webserver():
webserver = SimpleWebServer(host=get_lan_ip())
webserver.start()
yield webserver
webserver.stop()

131
docs/Makefile Normal file

@ -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 <target>' where <target> 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."

149
docs/source/api.rst Normal file

@ -0,0 +1,149 @@
:orphan:
======================
Selenium Documentation
======================
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.touch_actions
selenium.webdriver.common.utils
selenium.webdriver.common.proxy
selenium.webdriver.common.service
selenium.webdriver.common.html5.application_cache
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.android
-----------------
.. currentmodule:: selenium.webdriver.android
.. autosummary::
:toctree: webdriver_android
selenium.webdriver.android.webdriver
Webdriver.chrome
----------------
.. currentmodule:: selenium.webdriver.chrome
.. autosummary::
:toctree: webdriver_chrome
selenium.webdriver.chrome.options
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.options
selenium.webdriver.firefox.firefox_profile
selenium.webdriver.firefox.webdriver
Webdriver.ie
------------
.. currentmodule:: selenium.webdriver.ie
.. autosummary::
:toctree: webdriver_ie
selenium.webdriver.ie.webdriver
Webdriver.opera
---------------
.. currentmodule:: selenium.webdriver.opera
.. autosummary::
:toctree: webdriver_opera
selenium.webdriver.opera.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.mobile
selenium.webdriver.remote.remote_connection
selenium.webdriver.remote.utils
selenium.webdriver.remote.webdriver
selenium.webdriver.remote.webelement
Webdriver.safari
----------------
.. currentmodule:: selenium.webdriver.safari
.. autosummary::
:toctree: webdriver_safari
selenium.webdriver.safari.service
selenium.webdriver.safari.webdriver
Webdriver.webkitgtk
-------------------
.. currentmodule:: selenium.webdriver.webkitgtk
.. autosummary::
:toctree: webdriver_webkitgtk
selenium.webdriver.webkitgtk.options
selenium.webdriver.webkitgtk.service
selenium.webdriver.webkitgtk.webdriver
Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

@ -0,0 +1,4 @@
selenium.common.exceptions
==========================
.. automodule:: selenium.common.exceptions

274
docs/source/conf.py Normal file

@ -0,0 +1,274 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 unicode_literals
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.imgmath', '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 = 'Selenium'
copyright = '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 = '3.141'
# 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
# "<project> v<release> 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 = []
# 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 <link> 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', 'Selenium Documentation',
'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', 'Selenium Documentation',
['plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'], 1)
]
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = 'Selenium'
epub_author = 'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
epub_publisher = 'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
epub_copyright = '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"

145
docs/source/index.rst Normal file

@ -0,0 +1,145 @@
======================
Selenium Client Driver
======================
Introduction
============
Python language bindings for Selenium WebDriver.
The `selenium` package is used to automate web browser interaction from Python.
+-----------+--------------------------------------------------------------------------------------+
| **Home**: | http://www.seleniumhq.org |
+-----------+--------------------------------------------------------------------------------------+
| **Docs**: | `selenium package API <https://seleniumhq.github.io/selenium/docs/api/py/api.html>`_ |
+-----------+--------------------------------------------------------------------------------------+
| **Dev**: | https://github.com/SeleniumHQ/Selenium |
+-----------+--------------------------------------------------------------------------------------+
| **PyPI**: | https://pypi.org/project/selenium/ |
+-----------+--------------------------------------------------------------------------------------+
| **IRC**: | **#selenium** channel on freenode |
+-----------+--------------------------------------------------------------------------------------+
Several browsers/drivers are supported (Firefox, Chrome, Internet Explorer), as well as the Remote protocol.
Supported Python Versions
=========================
* Python 2.7, 3.4+
Installing
==========
If you have `pip <https://pip.pypa.io/>`_ on your system, you can simply install or upgrade the Python bindings::
pip install -U selenium
Alternately, you can download the source distribution from `PyPI <https://pypi.org/project/selenium/#files>`_ (e.g. selenium-4.0.0a1.tar.gz), unarchive it, and run::
python setup.py install
Note: You may want to consider using `virtualenv <http://www.virtualenv.org/>`_ to create isolated Python environments.
Drivers
=======
Selenium requires a driver to interface with the chosen browser. Firefox,
for example, requires `geckodriver <https://github.com/mozilla/geckodriver/releases>`_, which needs to be installed before the below examples can be run. Make sure it's in your `PATH`, e. g., place it in `/usr/bin` or `/usr/local/bin`.
Failure to observe this step will give you an error `selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.`
Other supported browsers will have their own drivers available. Links to some of the more popular browser drivers follow.
+--------------+-----------------------------------------------------------------------+
| **Chrome**: | https://sites.google.com/a/chromium.org/chromedriver/downloads |
+--------------+-----------------------------------------------------------------------+
| **Edge**: | https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ |
+--------------+-----------------------------------------------------------------------+
| **Firefox**: | https://github.com/mozilla/geckodriver/releases |
+--------------+-----------------------------------------------------------------------+
| **Safari**: | https://webkit.org/blog/6900/webdriver-support-in-safari-10/ |
+--------------+-----------------------------------------------------------------------+
Example 0:
==========
* open a new Firefox browser
* load the page at the given URL
.. code-block:: python
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://seleniumhq.org/')
Example 1:
==========
* open a new Firefox browser
* load the Yahoo homepage
* search for "seleniumhq"
* close the browser
.. code-block:: python
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
browser = webdriver.Firefox()
browser.get('http://www.yahoo.com')
assert 'Yahoo' in browser.title
elem = browser.find_element_by_name('p') # Find the search box
elem.send_keys('seleniumhq' + Keys.RETURN)
browser.quit()
Example 2:
==========
Selenium WebDriver is often used as a basis for testing web applications. Here is a simple example using Python's standard `unittest <http://docs.python.org/3/library/unittest.html>`_ library:
.. code-block:: python
import unittest
from selenium import webdriver
class GoogleTestCase(unittest.TestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.addCleanup(self.browser.quit)
def testPageTitle(self):
self.browser.get('http://www.google.com')
self.assertIn('Google', self.browser.title)
if __name__ == '__main__':
unittest.main(verbosity=2)
Selenium Server (optional)
==========================
For normal WebDriver scripts (non-Remote), the Java server is not needed.
However, to use Selenium Webdriver Remote or the legacy Selenium API (Selenium-RC), you need to also run the Selenium server. The server requires a Java Runtime Environment (JRE).
Download the server separately, from: http://selenium-release.storage.googleapis.com/4.0/selenium-server-standalone-4.0.0.jar
Run the server from the command line::
java -jar selenium-server-standalone-3.141.0.jar
Then run your Python client scripts.
Use The Source Luke!
====================
View source code online:
+-----------+-------------------------------------------------------+
| official: | https://github.com/SeleniumHQ/selenium/tree/master/py |
+-----------+-------------------------------------------------------+

@ -0,0 +1,4 @@
selenium.webdriver.common.action_chains
=======================================
.. automodule:: selenium.webdriver.common.action_chains

@ -0,0 +1,4 @@
selenium.webdriver.common.alert
===============================
.. automodule:: selenium.webdriver.common.alert

@ -0,0 +1,4 @@
selenium.webdriver.common.by
============================
.. automodule:: selenium.webdriver.common.by

@ -0,0 +1,4 @@
selenium.webdriver.common.desired_capabilities
==============================================
.. automodule:: selenium.webdriver.common.desired_capabilities

@ -0,0 +1,4 @@
selenium.webdriver.common.html5.application_cache
====================================================
.. automodule:: selenium.webdriver.common.html5.application_cache

@ -0,0 +1,4 @@
selenium.webdriver.common.keys
==============================
.. automodule:: selenium.webdriver.common.keys

@ -0,0 +1,4 @@
selenium.webdriver.common.proxy
===============================
.. automodule:: selenium.webdriver.common.proxy

@ -0,0 +1,4 @@
selenium.webdriver.common.service
=================================
.. automodule:: selenium.webdriver.common.service

@ -0,0 +1,4 @@
selenium.webdriver.common.touch_actions
=======================================
.. automodule:: selenium.webdriver.common.touch_actions

@ -0,0 +1,4 @@
selenium.webdriver.common.utils
===============================
.. automodule:: selenium.webdriver.common.utils

@ -0,0 +1,4 @@
selenium.webdriver.android.webdriver
====================================
.. automodule:: selenium.webdriver.android.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.chrome.options
=================================
.. automodule:: selenium.webdriver.chrome.options

@ -0,0 +1,4 @@
selenium.webdriver.chrome.service
=================================
.. automodule:: selenium.webdriver.chrome.service

@ -0,0 +1,4 @@
selenium.webdriver.chrome.webdriver
===================================
.. automodule:: selenium.webdriver.chrome.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.firefox.extension_connection
===============================================
.. automodule:: selenium.webdriver.firefox.extension_connection

@ -0,0 +1,4 @@
selenium.webdriver.firefox.firefox_binary
=========================================
.. automodule:: selenium.webdriver.firefox.firefox_binary

@ -0,0 +1,4 @@
selenium.webdriver.firefox.firefox_profile
==========================================
.. automodule:: selenium.webdriver.firefox.firefox_profile

@ -0,0 +1,4 @@
selenium.webdriver.firefox.options
==================================
.. automodule:: selenium.webdriver.firefox.options

@ -0,0 +1,4 @@
selenium.webdriver.firefox.webdriver
====================================
.. automodule:: selenium.webdriver.firefox.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.ie.webdriver
===============================
.. automodule:: selenium.webdriver.ie.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.opera.webdriver
==================================
.. automodule:: selenium.webdriver.opera.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.phantomjs.service
====================================
.. automodule:: selenium.webdriver.phantomjs.service

@ -0,0 +1,4 @@
selenium.webdriver.phantomjs.webdriver
======================================
.. automodule:: selenium.webdriver.phantomjs.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.remote.command
=================================
.. automodule:: selenium.webdriver.remote.command

@ -0,0 +1,4 @@
selenium.webdriver.remote.errorhandler
======================================
.. automodule:: selenium.webdriver.remote.errorhandler

@ -0,0 +1,4 @@
selenium.webdriver.remote.mobile
================================
.. automodule:: selenium.webdriver.remote.mobile

@ -0,0 +1,4 @@
selenium.webdriver.remote.remote_connection
===========================================
.. automodule:: selenium.webdriver.remote.remote_connection

@ -0,0 +1,4 @@
selenium.webdriver.remote.utils
===============================
.. automodule:: selenium.webdriver.remote.utils

@ -0,0 +1,4 @@
selenium.webdriver.remote.webdriver
===================================
.. automodule:: selenium.webdriver.remote.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.remote.webelement
====================================
.. automodule:: selenium.webdriver.remote.webelement

@ -0,0 +1,4 @@
selenium.webdriver.safari.service
=================================
.. automodule:: selenium.webdriver.safari.service

@ -0,0 +1,4 @@
selenium.webdriver.safari.webdriver
===================================
.. automodule:: selenium.webdriver.safari.webdriver

@ -0,0 +1,4 @@
selenium.webdriver.support.abstract_event_listener
==================================================
.. automodule:: selenium.webdriver.support.abstract_event_listener

@ -0,0 +1,4 @@
selenium.webdriver.support.color
================================
.. automodule:: selenium.webdriver.support.color

@ -0,0 +1,4 @@
selenium.webdriver.support.event_firing_webdriver
=================================================
.. automodule:: selenium.webdriver.support.event_firing_webdriver

@ -0,0 +1,4 @@
selenium.webdriver.support.expected_conditions
==============================================
.. automodule:: selenium.webdriver.support.expected_conditions

@ -0,0 +1,4 @@
selenium.webdriver.support.select
=================================
.. automodule:: selenium.webdriver.support.select

@ -0,0 +1,4 @@
selenium.webdriver.support.wait
===============================
.. automodule:: selenium.webdriver.support.wait

@ -0,0 +1,4 @@
selenium.webdriver.webkitgtk.options
====================================
.. automodule:: selenium.webdriver.webkitgtk.options

@ -0,0 +1,4 @@
selenium.webdriver.webkitgtk.service
====================================
.. automodule:: selenium.webdriver.webkitgtk.service

@ -0,0 +1,4 @@
selenium.webdriver.webkitgtk.webdriver
======================================
.. automodule:: selenium.webdriver.webkitgtk.webdriver

24
python.iml Normal file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python">
<configuration sdkName="" />
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/selenium" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="Python 2.7" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="sonarModuleSettings">
<option name="alternativeWorkingDirPath" value="" />
<option name="localAnalysisScripName" value="&lt;PROJECT&gt;" />
<option name="serverName" value="&lt;PROJECT&gt;" />
<option name="useAlternativeWorkingDir" value="false" />
<option name="workingDirSelection" value="&lt;MODULE&gt;" />
</component>
</module>

19
selenium/__init__.py Normal file

@ -0,0 +1,19 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.
__version__ = "4.0.0a1"

@ -0,0 +1,18 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 . import exceptions # noqa

@ -0,0 +1,315 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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):
"""
Base webdriver 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\n" % self.msg
if self.screen is not None:
exception_msg += "Screenshot: available via screen\n"
if self.stacktrace is not None:
stacktrace = "\n".join(self.stacktrace)
exception_msg += "Stacktrace:\n%s" % stacktrace
return exception_msg
class InvalidSwitchToTargetException(WebDriverException):
"""
Thrown when frame or window target to be switched doesn't exist.
"""
pass
class NoSuchFrameException(InvalidSwitchToTargetException):
"""
Thrown when frame target to be switched doesn't exist.
"""
pass
class NoSuchWindowException(InvalidSwitchToTargetException):
"""
Thrown when window target to be switched doesn't exist.
To find the current set of active window handles, you can get a list
of the active window handles in the following way::
print driver.window_handles
"""
pass
class NoSuchElementException(WebDriverException):
"""
Thrown when element could not be found.
If you encounter this exception, you may want to check the following:
* Check your selector used in your find_by...
* Element may not yet be on the screen at the time of the find operation,
(webpage is still loading) see selenium.webdriver.support.wait.WebDriverWait()
for how to write a wait wrapper to wait for an element to appear.
"""
pass
class NoSuchAttributeException(WebDriverException):
"""
Thrown when the attribute of element could not be found.
You may want to check if the attribute exists in the particular browser you are
testing against. Some browsers may have different property names for the same
property. (IE8's .innerText vs. Firefox .textContent)
"""
pass
class StaleElementReferenceException(WebDriverException):
"""
Thrown when a reference to an element is now "stale".
Stale means the element no longer appears on the DOM of the page.
Possible causes of StaleElementReferenceException include, but not limited to:
* You are no longer on the same page, or the page may have refreshed since the element
was located.
* The element may have been removed and re-added to the screen, since it was located.
Such as an element being relocated.
This can happen typically with a javascript framework when values are updated and the
node is rebuilt.
* Element may have been inside an iframe or another context which was refreshed.
"""
pass
class InvalidElementStateException(WebDriverException):
"""
Thrown when a command could not be completed because the element is in an invalid state.
This can be caused by attempting to clear an element that isn't both editable and resettable.
"""
pass
class UnexpectedAlertPresentException(WebDriverException):
"""
Thrown when an unexpected alert has appeared.
Usually raised when an unexpected modal is blocking the webdriver from executing
commands.
"""
def __init__(self, msg=None, screen=None, stacktrace=None, alert_text=None):
super(UnexpectedAlertPresentException, self).__init__(msg, screen, stacktrace)
self.alert_text = alert_text
def __str__(self):
return "Alert Text: %s\n%s" % (self.alert_text, super(UnexpectedAlertPresentException, self).__str__())
class NoAlertPresentException(WebDriverException):
"""
Thrown when switching to no presented alert.
This can be caused by calling an operation on the Alert() class when an alert is
not yet on the screen.
"""
pass
class ElementNotVisibleException(InvalidElementStateException):
"""
Thrown when an element is present on the DOM, but
it is not visible, and so is not able to be interacted with.
Most commonly encountered when trying to click or read text
of an element that is hidden from view.
"""
pass
class ElementNotInteractableException(InvalidElementStateException):
"""
Thrown when an element is present in the DOM but interactions
with that element will hit another element do to paint order
"""
pass
class ElementNotSelectableException(InvalidElementStateException):
"""
Thrown when trying to select an unselectable element.
For example, selecting a 'script' element.
"""
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):
"""
Thrown when the target provided to the `ActionsChains` move()
method is invalid, i.e. out of document.
"""
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 and it 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):
"""
Thrown when 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):
"""
Thrown when activating an IME engine has failed.
"""
pass
class InvalidArgumentException(WebDriverException):
"""
The arguments passed to a command are either invalid or malformed.
"""
pass
class JavascriptException(WebDriverException):
"""
An error occurred while executing JavaScript supplied by the user.
"""
pass
class NoSuchCookieException(WebDriverException):
"""
No cookie matching the given path name was found amongst the associated cookies of the
current browsing context's active document.
"""
pass
class ScreenshotException(WebDriverException):
"""
A screen capture was made impossible.
"""
pass
class ElementClickInterceptedException(WebDriverException):
"""
The Element Click command could not be completed because the element receiving the events
is obscuring the element that was requested clicked.
"""
pass
class InsecureCertificateException(WebDriverException):
"""
Navigation caused the user agent to hit a certificate warning, which is usually the result
of an expired or invalid TLS certificate.
"""
pass
class InvalidCoordinatesException(WebDriverException):
"""
The coordinates provided to an interactions operation are invalid.
"""
pass
class InvalidSessionIdException(WebDriverException):
"""
Occurs if the given session id is not in the list of active sessions, meaning the session
either does not exist or that it's not active.
"""
pass
class SessionNotCreatedException(WebDriverException):
"""
A new session could not be created.
"""
pass
class UnknownMethodException(WebDriverException):
"""
The requested command matched a known URL but did not match an method for that URL.
"""
pass

@ -0,0 +1,39 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 # noqa
from .firefox.firefox_profile import FirefoxProfile # noqa
from .firefox.options import Options as FirefoxOptions # noqa
from .chrome.webdriver import WebDriver as Chrome # noqa
from .chrome.options import Options as ChromeOptions # noqa
from .ie.webdriver import WebDriver as Ie # noqa
from .ie.options import Options as IeOptions # noqa
from .edge.webdriver import WebDriver as Edge # noqa
from .opera.webdriver import WebDriver as Opera # noqa
from .safari.webdriver import WebDriver as Safari # noqa
from .blackberry.webdriver import WebDriver as BlackBerry # noqa
from .phantomjs.webdriver import WebDriver as PhantomJS # noqa
from .android.webdriver import WebDriver as Android # noqa
from .webkitgtk.webdriver import WebDriver as WebKitGTK # noqa
from .webkitgtk.options import Options as WebKitGTKOptions # noqa
from .remote.webdriver import WebDriver as Remote # noqa
from .common.desired_capabilities import DesiredCapabilities # noqa
from .common.action_chains import ActionChains # noqa
from .common.touch_actions import TouchActions # noqa
from .common.proxy import Proxy # noqa
__version__ = '4.0.0a1'

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,42 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.webdriver import WebDriver as RemoteWebDriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
class WebDriver(RemoteWebDriver):
"""
Simple RemoteWebDriver wrapper to start connect to Selendroid's WebView app
For more info on getting started with Selendroid
http://selendroid.io/mobileWeb.html
"""
def __init__(self, host="localhost", port=4444, desired_capabilities=DesiredCapabilities.ANDROID):
"""
Creates a new instance of Selendroid using the WebView app
:Args:
- host - location of where selendroid is running
- port - port that selendroid is running on
- desired_capabilities: Dictionary object with capabilities
"""
RemoteWebDriver.__init__(
self,
command_executor="http://%s:%d/wd/hub" % (host, port),
desired_capabilities=desired_capabilities)

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,121 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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
import subprocess
try:
import http.client as http_client
except ImportError:
import httplib as http_client
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support.ui import WebDriverWait
LOAD_TIMEOUT = 5
class WebDriver(RemoteWebDriver):
"""
Controls the BlackBerry Browser and allows you to drive it.
:Args:
- device_password - password for the BlackBerry device or emulator you are
trying to drive
- bb_tools_dir path to the blackberry-deploy executable. If the default
is used it assumes it is in the $PATH
- hostip - the ip for the device you are trying to drive. Falls back to
169.254.0.1 which is the default ip used
- port - the port being used for WebDriver on device. defaults to 1338
- desired_capabilities: Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
Note: To get blackberry-deploy you will need to install the BlackBerry
WebWorks SDK - the default install will put it in the $PATH for you.
Download at https://developer.blackberry.com/html5/downloads/
"""
def __init__(self, device_password, bb_tools_dir=None,
hostip='169.254.0.1', port=1338, desired_capabilities={}):
import warnings
warnings.warn('BlackBerry Driver is no longer supported and will be '
'removed in future versions',
DeprecationWarning, stacklevel=2)
remote_addr = 'http://{}:{}'.format(hostip, port)
filename = 'blackberry-deploy'
if platform.system() == "Windows":
filename += '.bat'
if bb_tools_dir is not None:
if os.path.isdir(bb_tools_dir):
bb_deploy_location = os.path.join(bb_tools_dir, filename)
if not os.path.isfile(bb_deploy_location):
raise WebDriverException('Invalid blackberry-deploy location: {}'.format(bb_deploy_location))
else:
raise WebDriverException('Invalid blackberry tools location, must be a directory: {}'.format(bb_tools_dir))
else:
bb_deploy_location = filename
"""
Now launch the BlackBerry browser before allowing anything else to run.
"""
try:
launch_args = [bb_deploy_location,
'-launchApp',
str(hostip),
'-package-name', 'sys.browser',
'-package-id', 'gYABgJYFHAzbeFMPCCpYWBtHAm0',
'-password', str(device_password)]
with open(os.devnull, 'w') as fp:
p = subprocess.Popen(launch_args, stdout=fp)
returncode = p.wait()
if returncode == 0:
# wait for the BlackBerry10 browser to load.
is_running_args = [bb_deploy_location,
'-isAppRunning',
str(hostip),
'-package-name', 'sys.browser',
'-package-id', 'gYABgJYFHAzbeFMPCCpYWBtHAm0',
'-password', str(device_password)]
WebDriverWait(None, LOAD_TIMEOUT)\
.until(lambda x: subprocess.check_output(is_running_args)
.find('result::true'),
message='waiting for BlackBerry10 browser to load')
RemoteWebDriver.__init__(self,
command_executor=remote_addr,
desired_capabilities=desired_capabilities)
else:
raise WebDriverException('blackberry-deploy failed to launch browser')
except Exception as e:
raise WebDriverException('Something went wrong launching blackberry-deploy', stacktrace=getattr(e, 'stacktrace', None))
def quit(self):
"""
Closes the browser and shuts down the
"""
try:
RemoteWebDriver.quit(self)
except http_client.BadStatusLine:
pass

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,177 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 os
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.options import ArgOptions
class Options(ArgOptions):
KEY = "goog:chromeOptions"
def __init__(self):
super(Options, self).__init__()
self._binary_location = ''
self._extension_files = []
self._extensions = []
self._experimental_options = {}
self._debugger_address = None
@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 debugger_address(self):
"""
:Returns: The address of the remote devtools instance
"""
return self._debugger_address
@debugger_address.setter
def debugger_address(self, value):
"""
Allows you to set the address of the remote devtools instance
that the ChromeDriver instance will try to connect to during an
active wait.
:Args:
- value: address of remote devtools instance if any (hostname[:port])
"""
self._debugger_address = value
@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, 'rb')
# 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()).decode('UTF-8'))
file_.close()
return encoded_extensions + self._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:
extension_to_add = os.path.abspath(os.path.expanduser(extension))
if os.path.exists(extension_to_add):
self._extension_files.append(extension_to_add)
else:
raise IOError("Path to the extension doesn't exist")
else:
raise ValueError("argument can not be null")
def add_encoded_extension(self, extension):
"""
Adds Base64 encoded string with extension data to a list that will be used to extract it
to the ChromeDriver
:Args:
- extension: Base64 encoded string with extension data
"""
if extension:
self._extensions.append(extension)
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
@property
def headless(self):
"""
:Returns: True if the headless argument is set, else False
"""
return '--headless' in self._arguments
@headless.setter
def headless(self, value):
"""
Sets the headless argument
:Args:
value: boolean value indicating to set the headless option
"""
args = {'--headless'}
if value is True:
self._arguments.extend(args)
else:
self._arguments = list(set(self._arguments) - args)
def to_capabilities(self):
"""
Creates a capabilities with all the options that have been set
:Returns: A dictionary with everything
"""
caps = self._caps
chrome_options = self.experimental_options.copy()
chrome_options["extensions"] = self.extensions
if self.binary_location:
chrome_options["binary"] = self.binary_location
chrome_options["args"] = self.arguments
if self.debugger_address:
chrome_options["debuggerAddress"] = self.debugger_address
caps[self.KEY] = chrome_options
return caps
@property
def default_capabilities(self):
return DesiredCapabilities.CHROME.copy()

@ -0,0 +1,33 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.remote_connection import RemoteConnection
class ChromeRemoteConnection(RemoteConnection):
def __init__(self, remote_server_addr, keep_alive=True):
RemoteConnection.__init__(self, remote_server_addr, keep_alive)
self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions')
self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions')
self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/goog/cdp/execute')
self._commands['getSinks'] = ('GET', '/session/$sessionId/goog/cast/get_sinks')
self._commands['getIssueMessage'] = ('GET', '/session/$sessionId/goog/cast/get_issue_message')
self._commands['setSinkToUse'] = ('POST', '/session/$sessionId/goog/cast/set_sink_to_use')
self._commands['startTabMirroring'] = ('POST', '/session/$sessionId/goog/cast/start_tab_mirroring')
self._commands['stopCasting'] = ('POST', '/session/$sessionId/goog/cast/stop_casting')

@ -0,0 +1,45 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 service
class Service(service.Service):
"""
Object that manages the starting and stopping of the ChromeDriver
"""
def __init__(self, executable_path, port=0, service_args=None,
log_path=None, env=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.service_args = service_args or []
if log_path:
self.service_args.append('--log-path=%s' % log_path)
service.Service.__init__(self, executable_path, port=port, env=env,
start_error_message="Please see https://sites.google.com/a/chromium.org/chromedriver/home")
def command_line_args(self):
return ["--port=%d" % self.port] + self.service_args

@ -0,0 +1,223 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 warnings
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
from .remote_connection import ChromeRemoteConnection
from .service import Service
from .options import Options
DEFAULT_PORT = 0
DEFAULT_SERVICE_LOG_PATH = None
class WebDriver(RemoteWebDriver):
"""
Controls the ChromeDriver and allows you to drive the browser.
You will need to download the ChromeDriver executable from
http://chromedriver.storage.googleapis.com/index.html
"""
def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT,
options=None, service_args=None,
desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH,
chrome_options=None, service=None, keep_alive=True):
"""
Creates a new instance of the chrome driver.
Starts the service and then creates new instance of chrome driver.
:Args:
- executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH
- port - Deprecated: port you would like the service to run, if left as 0, a free port will be found.
- options - this takes an instance of ChromeOptions
- service_args - Deprecated: List of args to pass to the driver service
- desired_capabilities - Deprecated: Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
- service_log_path - Deprecated: Where to log information from the driver.
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
if executable_path != 'chromedriver':
warnings.warn('executable_path has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if desired_capabilities is not None:
warnings.warn('desired_capabilities has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if port != DEFAULT_PORT:
warnings.warn('port has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
self.port = port
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if chrome_options:
warnings.warn('use options instead of chrome_options',
DeprecationWarning, stacklevel=2)
options = chrome_options
if options is None:
# desired_capabilities stays as passed in
if desired_capabilities is None:
desired_capabilities = self.create_options().to_capabilities()
else:
if desired_capabilities is None:
desired_capabilities = options.to_capabilities()
else:
desired_capabilities.update(options.to_capabilities())
if service:
self.service = service
else:
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=ChromeRemoteConnection(
remote_server_addr=self.service.service_url,
keep_alive=keep_alive),
desired_capabilities=desired_capabilities)
except Exception:
self.quit()
raise
self._is_remote = False
def launch_app(self, id):
"""Launches Chrome app specified by id."""
return self.execute("launchApp", {'id': id})
def get_network_conditions(self):
"""
Gets Chrome network emulation settings.
:Returns:
A dict. For example:
{'latency': 4, 'download_throughput': 2, 'upload_throughput': 2,
'offline': False}
"""
return self.execute("getNetworkConditions")['value']
def set_network_conditions(self, **network_conditions):
"""
Sets Chrome network emulation settings.
:Args:
- network_conditions: A dict with conditions specification.
:Usage:
::
driver.set_network_conditions(
offline=False,
latency=5, # additional latency (ms)
download_throughput=500 * 1024, # maximal throughput
upload_throughput=500 * 1024) # maximal throughput
Note: 'throughput' can be used to set both (for download and upload).
"""
self.execute("setNetworkConditions", {
'network_conditions': network_conditions
})
def execute_cdp_cmd(self, cmd, cmd_args):
"""
Execute Chrome Devtools Protocol command and get returned result
The command and command args should follow chrome devtools protocol domains/commands, refer to link
https://chromedevtools.github.io/devtools-protocol/
:Args:
- cmd: A str, command name
- cmd_args: A dict, command args. empty dict {} if there is no command args
:Usage:
::
driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
:Returns:
A dict, empty dict {} if there is no result to return.
For example to getResponseBody:
{'base64Encoded': False, 'body': 'response body string'}
"""
return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
def get_sinks(self):
"""
:Returns: A list of sinks avaliable for Cast.
"""
return self.execute('getSinks')['value']
def get_issue_message(self):
"""
:Returns: An error message when there is any issue in a Cast session.
"""
return self.execute('getIssueMessage')['value']
def set_sink_to_use(self, sink_name):
"""
Sets a specific sink, using its name, as a Cast session receiver target.
:Args:
- sink_name: Name of the sink to use as the target.
"""
return self.execute('setSinkToUse', {'sinkName': sink_name})
def start_tab_mirroring(self, sink_name):
"""
Starts a tab mirroring session on a specific receiver target.
:Args:
- sink_name: Name of the sink to use as the target.
"""
return self.execute('startTabMirroring', {'sinkName': sink_name})
def stop_casting(self, sink_name):
"""
Stops the existing Cast session on a specific receiver target.
:Args:
- sink_name: Name of the sink to stop the Cast session.
"""
return self.execute('stopCasting', {'sinkName': sink_name})
def quit(self):
"""
Closes the browser and shuts down the ChromeDriver executable
that is started when starting the ChromeDriver
"""
try:
RemoteWebDriver.quit(self)
except Exception:
# We don't care about the message because something probably has gone wrong
pass
finally:
self.service.stop()
def create_options(self):
return Options()

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,365 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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,
"""
import time
from selenium.webdriver.remote.command import Command
from .utils import keys_to_typing
from .actions.action_builder import ActionBuilder
class ActionChains(object):
"""
ActionChains are a way to automate low level interactions such as
mouse movements, mouse button actions, key press, and context menu interactions.
This is useful for doing more complex actions like hover over and drag and drop.
Generate user actions.
When you call methods for actions on the ActionChains object,
the actions are stored in a queue in the ActionChains object.
When you call perform(), the events are fired in the order they
are queued up.
ActionChains can be used in a chain pattern::
menu = driver.find_element_by_css_selector(".nav")
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
Or actions can be queued up one by one, then performed.::
menu = driver.find_element_by_css_selector(".nav")
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
actions = ActionChains(driver)
actions.move_to_element(menu)
actions.click(hidden_submenu)
actions.perform()
Either way, the actions are performed in the order they are called, one after
another.
"""
def __init__(self, driver):
"""
Creates a new ActionChains.
:Args:
- driver: The WebDriver instance which performs user actions.
"""
self._driver = driver
self._actions = []
if self._driver.w3c:
self.w3c_actions = ActionBuilder(driver)
def perform(self):
"""
Performs all stored actions.
"""
if self._driver.w3c:
self.w3c_actions.perform()
else:
for action in self._actions:
action()
def reset_actions(self):
"""
Clears actions that are already stored locally and on the remote end
"""
if self._driver.w3c:
self.w3c_actions.clear_actions()
for device in self.w3c_actions.devices:
device.clear_actions()
self._actions = []
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)
if self._driver.w3c:
self.w3c_actions.pointer_action.click()
self.w3c_actions.key_action.pause()
self.w3c_actions.key_action.pause()
else:
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)
if self._driver.w3c:
self.w3c_actions.pointer_action.click_and_hold()
self.w3c_actions.key_action.pause()
else:
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)
if self._driver.w3c:
self.w3c_actions.pointer_action.context_click()
self.w3c_actions.key_action.pause()
self.w3c_actions.key_action.pause()
else:
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)
if self._driver.w3c:
self.w3c_actions.pointer_action.double_click()
for _ in range(4):
self.w3c_actions.key_action.pause()
else:
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 offset 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()
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:
- value: The modifier key to send. Values are defined in `Keys` class.
- element: The element to send keys.
If None, sends a key to current focused element.
Example, pressing ctrl+c::
ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
"""
if element:
self.click(element)
if self._driver.w3c:
self.w3c_actions.key_action.key_down(value)
self.w3c_actions.pointer_action.pause()
else:
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
{"value": keys_to_typing(value)}))
return self
def key_up(self, value, element=None):
"""
Releases a modifier key.
:Args:
- value: The modifier key to send. Values are defined in Keys class.
- element: The element to send keys.
If None, sends a key to current focused element.
Example, pressing ctrl+c::
ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
"""
if element:
self.click(element)
if self._driver.w3c:
self.w3c_actions.key_action.key_up(value)
self.w3c_actions.pointer_action.pause()
else:
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
{"value": keys_to_typing(value)}))
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, as a positive or negative integer.
- yoffset: Y offset to move to, as a positive or negative integer.
"""
if self._driver.w3c:
self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
self.w3c_actions.key_action.pause()
else:
self._actions.append(lambda: self._driver.execute(
Command.MOVE_TO, {
'xoffset': int(xoffset),
'yoffset': int(yoffset)}))
return self
def move_to_element(self, to_element):
"""
Moving the mouse to the middle of an element.
:Args:
- to_element: The WebElement to move to.
"""
if self._driver.w3c:
self.w3c_actions.pointer_action.move_to(to_element)
self.w3c_actions.key_action.pause()
else:
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 specified element.
Offsets are relative to the top-left corner of the element.
:Args:
- to_element: The WebElement to move to.
- xoffset: X offset to move to.
- yoffset: Y offset to move to.
"""
if self._driver.w3c:
self.w3c_actions.pointer_action.move_to(to_element, xoffset, yoffset)
self.w3c_actions.key_action.pause()
else:
self._actions.append(
lambda: self._driver.execute(Command.MOVE_TO, {
'element': to_element.id,
'xoffset': int(xoffset),
'yoffset': int(yoffset)}))
return self
def pause(self, seconds):
""" Pause all inputs for the specified duration in seconds """
if self._driver.w3c:
self.w3c_actions.pointer_action.pause(seconds)
self.w3c_actions.key_action.pause(seconds)
else:
self._actions.append(lambda: time.sleep(seconds))
return self
def release(self, on_element=None):
"""
Releasing a held mouse button on an element.
:Args:
- on_element: The element to mouse up.
If None, releases on current mouse position.
"""
if on_element:
self.move_to_element(on_element)
if self._driver.w3c:
self.w3c_actions.pointer_action.release()
self.w3c_actions.key_action.pause()
else:
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. Modifier keys constants can be found in the
'Keys' class.
"""
typing = keys_to_typing(keys_to_send)
if self._driver.w3c:
for key in typing:
self.key_down(key)
self.key_up(key)
else:
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': typing}))
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. Modifier keys constants can be found in the
'Keys' class.
"""
self.click(element)
self.send_keys(*keys_to_send)
return self
# Context manager so ActionChains can be used in a 'with .. as' statements.
def __enter__(self):
return self # Return created instance of self.
def __exit__(self, _type, _value, _traceback):
pass # Do nothing, does not require additional cleanup.

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,85 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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
from . import interaction
from .key_actions import KeyActions
from .key_input import KeyInput
from .pointer_actions import PointerActions
from .pointer_input import PointerInput
class ActionBuilder(object):
def __init__(self, driver, mouse=None, keyboard=None):
if mouse is None:
mouse = PointerInput(interaction.POINTER_MOUSE, "mouse")
if keyboard is None:
keyboard = KeyInput(interaction.KEY)
self.devices = [mouse, keyboard]
self._key_action = KeyActions(keyboard)
self._pointer_action = PointerActions(mouse)
self.driver = driver
def get_device_with(self, name):
try:
idx = self.devices.index(name)
return self.devices[idx]
except:
pass
@property
def pointer_inputs(self):
return [device for device in self.devices if device.type == interaction.POINTER]
@property
def key_inputs(self):
return [device for device in self.devices if device.type == interaction.KEY]
@property
def key_action(self):
return self._key_action
@property
def pointer_action(self):
return self._pointer_action
def add_key_input(self, name):
new_input = KeyInput(name)
self._add_input(new_input)
return new_input
def add_pointer_input(self, kind, name):
new_input = PointerInput(kind, name)
self._add_input(new_input)
return new_input
def perform(self):
enc = {"actions": []}
for device in self.devices:
encoded = device.encode()
if encoded['actions']:
enc["actions"].append(encoded)
self.driver.execute(Command.W3C_ACTIONS, enc)
def clear_actions(self):
"""
Clears actions that are already stored on the remote end
"""
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
def _add_input(self, input):
self.devices.append(input)

@ -0,0 +1,43 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 uuid
class InputDevice(object):
"""
Describes the input device being used for the action.
"""
def __init__(self, name=None):
if name is None:
self.name = uuid.uuid4()
else:
self.name = name
self.actions = []
def add_action(self, action):
"""
"""
self.actions.append(action)
def clear_actions(self):
self.actions = []
def create_pause(self, duraton=0):
pass

@ -0,0 +1,50 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.
KEY = "key"
POINTER = "pointer"
NONE = "none"
SOURCE_TYPES = set([KEY, POINTER, NONE])
POINTER_MOUSE = "mouse"
POINTER_TOUCH = "touch"
POINTER_PEN = "pen"
POINTER_KINDS = set([POINTER_MOUSE, POINTER_TOUCH, POINTER_PEN])
class Interaction(object):
PAUSE = "pause"
def __init__(self, source):
self.source = source
class Pause(Interaction):
def __init__(self, source, duration=0):
super(Interaction, self).__init__()
self.source = source
self.duration = duration
def encode(self):
return {
"type": self.PAUSE,
"duration": int(self.duration * 1000)
}

@ -0,0 +1,50 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 .interaction import Interaction, KEY
from .key_input import KeyInput
from ..utils import keys_to_typing
class KeyActions(Interaction):
def __init__(self, source=None):
if source is None:
source = KeyInput(KEY)
self.source = source
super(KeyActions, self).__init__(source)
def key_down(self, letter):
return self._key_action("create_key_down", letter)
def key_up(self, letter):
return self._key_action("create_key_up", letter)
def pause(self, duration=0):
return self._key_action("create_pause", duration)
def send_keys(self, text):
if not isinstance(text, list):
text = keys_to_typing(text)
for letter in text:
self.key_down(letter)
self.key_up(letter)
return self
def _key_action(self, action, letter):
meth = getattr(self.source, action)
meth(letter)
return self

@ -0,0 +1,51 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 . import interaction
from .input_device import InputDevice
from .interaction import (Interaction,
Pause)
class KeyInput(InputDevice):
def __init__(self, name):
super(KeyInput, self).__init__()
self.name = name
self.type = interaction.KEY
def encode(self):
return {"type": self.type, "id": self.name, "actions": [acts.encode() for acts in self.actions]}
def create_key_down(self, key):
self.add_action(TypingInteraction(self, "keyDown", key))
def create_key_up(self, key):
self.add_action(TypingInteraction(self, "keyUp", key))
def create_pause(self, pause_duration=0):
self.add_action(Pause(self, pause_duration))
class TypingInteraction(Interaction):
def __init__(self, source, type_, key):
super(TypingInteraction, self).__init__(source)
self.type = type_
self.key = key
def encode(self):
return {"type": self.type, "value": self.key}

@ -0,0 +1,23 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 MouseButton(object):
LEFT = 0
MIDDLE = 1
RIGHT = 2

@ -0,0 +1,101 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 . import interaction
from .interaction import Interaction
from .mouse_button import MouseButton
from .pointer_input import PointerInput
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement
class PointerActions(Interaction):
def __init__(self, source=None):
if source is None:
source = PointerInput(interaction.POINTER_MOUSE, "mouse")
self.source = source
super(PointerActions, self).__init__(source)
def pointer_down(self, button=MouseButton.LEFT):
self._button_action("create_pointer_down", button=button)
def pointer_up(self, button=MouseButton.LEFT):
self._button_action("create_pointer_up", button=button)
def move_to(self, element, x=None, y=None):
if not isinstance(element, (WebElement, EventFiringWebElement)):
raise AttributeError("move_to requires a WebElement")
if x is not None or y is not None:
el_rect = element.rect
left_offset = el_rect['width'] / 2
top_offset = el_rect['height'] / 2
left = -left_offset + (x or 0)
top = -top_offset + (y or 0)
else:
left = 0
top = 0
self.source.create_pointer_move(origin=element, x=int(left), y=int(top))
return self
def move_by(self, x, y):
self.source.create_pointer_move(origin=interaction.POINTER, x=int(x), y=int(y))
return self
def move_to_location(self, x, y):
self.source.create_pointer_move(origin='viewport', x=int(x), y=int(y))
return self
def click(self, element=None):
if element:
self.move_to(element)
self.pointer_down(MouseButton.LEFT)
self.pointer_up(MouseButton.LEFT)
return self
def context_click(self, element=None):
if element:
self.move_to(element)
self.pointer_down(MouseButton.RIGHT)
self.pointer_up(MouseButton.RIGHT)
return self
def click_and_hold(self, element=None):
if element:
self.move_to(element)
self.pointer_down()
return self
def release(self):
self.pointer_up()
return self
def double_click(self, element=None):
if element:
self.move_to(element)
self.click()
self.click()
def pause(self, duration=0):
self.source.create_pause(duration)
return self
def _button_action(self, action, button=MouseButton.LEFT):
meth = getattr(self.source, action)
meth(button)
return self

@ -0,0 +1,64 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 .input_device import InputDevice
from .interaction import POINTER, POINTER_KINDS
from selenium.common.exceptions import InvalidArgumentException
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement
class PointerInput(InputDevice):
DEFAULT_MOVE_DURATION = 250
def __init__(self, kind, name):
super(PointerInput, self).__init__()
if (kind not in POINTER_KINDS):
raise InvalidArgumentException("Invalid PointerInput kind '%s'" % kind)
self.type = POINTER
self.kind = kind
self.name = name
def create_pointer_move(self, duration=DEFAULT_MOVE_DURATION, x=None, y=None, origin=None):
action = dict(type="pointerMove", duration=duration)
action["x"] = x
action["y"] = y
if isinstance(origin, (WebElement, EventFiringWebElement)):
action["origin"] = {"element-6066-11e4-a52e-4f735466cecf": origin.id}
elif origin is not None:
action["origin"] = origin
self.add_action(action)
def create_pointer_down(self, button):
self.add_action({"type": "pointerDown", "duration": 0, "button": button})
def create_pointer_up(self, button):
self.add_action({"type": "pointerUp", "duration": 0, "button": button})
def create_pointer_cancel(self):
self.add_action({"type": "pointerCancel"})
def create_pause(self, pause_duration):
self.add_action({"type": "pause", "duration": int(pause_duration * 1000)})
def encode(self):
return {"type": self.type,
"parameters": {"pointerType": self.kind},
"id": self.name,
"actions": [acts for acts in self.actions]}

@ -0,0 +1,105 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 Alert implementation.
"""
from selenium.webdriver.common.utils import keys_to_typing
from selenium.webdriver.remote.command import Command
class Alert(object):
"""
Allows to work with alerts.
Use this class to interact with alert prompts. It contains methods for dismissing,
accepting, inputting, and getting text from alert prompts.
Accepting / Dismissing alert prompts::
Alert(driver).accept()
Alert(driver).dismiss()
Inputting a value into an alert prompt:
name_prompt = Alert(driver)
name_prompt.send_keys("Willian Shakesphere")
name_prompt.accept()
Reading a the text of a prompt for verification:
alert_text = Alert(driver).text
self.assertEqual("Do you wish to quit?", alert_text)
"""
def __init__(self, driver):
"""
Creates a new Alert.
:Args:
- driver: The WebDriver instance which performs user actions.
"""
self.driver = driver
@property
def text(self):
"""
Gets the text of the Alert.
"""
if self.driver.w3c:
return self.driver.execute(Command.W3C_GET_ALERT_TEXT)["value"]
else:
return self.driver.execute(Command.GET_ALERT_TEXT)["value"]
def dismiss(self):
"""
Dismisses the alert available.
"""
if self.driver.w3c:
self.driver.execute(Command.W3C_DISMISS_ALERT)
else:
self.driver.execute(Command.DISMISS_ALERT)
def accept(self):
"""
Accepts the alert available.
Usage::
Alert(driver).accept() # Confirm a alert dialog.
"""
if self.driver.w3c:
self.driver.execute(Command.W3C_ACCEPT_ALERT)
else:
self.driver.execute(Command.ACCEPT_ALERT)
def send_keys(self, keysToSend):
"""
Send Keys to the Alert.
:Args:
- keysToSend: The text to be sent to Alert.
"""
if self.driver.w3c:
self.driver.execute(Command.W3C_SET_ALERT_VALUE, {'value': keys_to_typing(keysToSend),
'text': keysToSend})
else:
self.driver.execute(Command.SET_ALERT_VALUE, {'text': keysToSend})

@ -0,0 +1,35 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 By implementation.
"""
class By(object):
"""
Set of supported locator strategies.
"""
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"

@ -0,0 +1,127 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 Desired Capabilities implementation.
"""
class DesiredCapabilities(object):
"""
Set of default supported desired capabilities.
Use this as a starting point for creating a desired capabilities object for
requesting remote webdrivers for connecting to selenium server or selenium grid.
Usage Example::
from selenium import webdriver
selenium_grid_url = "http://198.0.0.1:4444/wd/hub"
# Create a desired capabilities object as a starting point.
capabilities = DesiredCapabilities.FIREFOX.copy()
capabilities['platform'] = "WINDOWS"
capabilities['version'] = "10"
# Instantiate an instance of Remote WebDriver with the desired capabilities.
driver = webdriver.Remote(desired_capabilities=capabilities,
command_executor=selenium_grid_url)
Note: Always use '.copy()' on the DesiredCapabilities object to avoid the side
effects of altering the Global class instance.
"""
FIREFOX = {
"browserName": "firefox",
"acceptInsecureCerts": True,
}
INTERNETEXPLORER = {
"browserName": "internet explorer",
"version": "",
"platform": "WINDOWS",
}
EDGE = {
"browserName": "MicrosoftEdge",
"version": "",
"platform": "WINDOWS"
}
CHROME = {
"browserName": "chrome",
"version": "",
"platform": "ANY",
}
OPERA = {
"browserName": "opera",
"version": "",
"platform": "ANY",
}
SAFARI = {
"browserName": "safari",
"version": "",
"platform": "MAC",
}
HTMLUNIT = {
"browserName": "htmlunit",
"version": "",
"platform": "ANY",
}
HTMLUNITWITHJS = {
"browserName": "htmlunit",
"version": "firefox",
"platform": "ANY",
"javascriptEnabled": True,
}
IPHONE = {
"browserName": "iPhone",
"version": "",
"platform": "MAC",
}
IPAD = {
"browserName": "iPad",
"version": "",
"platform": "MAC",
}
ANDROID = {
"browserName": "android",
"version": "",
"platform": "ANDROID",
}
PHANTOMJS = {
"browserName": "phantomjs",
"version": "",
"platform": "ANY",
"javascriptEnabled": True,
}
WEBKITGTK = {
"browserName": "MiniBrowser",
"version": "",
"platform": "ANY",
}

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,48 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 ApplicationCache implementaion.
"""
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):
"""
Creates a new Aplication Cache.
:Args:
- driver: The WebDriver instance which performs user actions.
"""
self.driver = driver
@property
def status(self):
"""
Returns a current status of application cache.
"""
return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value']

@ -0,0 +1,96 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 Keys implementation.
"""
from __future__ import unicode_literals
class Keys(object):
"""
Set of special keys codes.
"""
NULL = '\ue000'
CANCEL = '\ue001' # ^break
HELP = '\ue002'
BACKSPACE = '\ue003'
BACK_SPACE = BACKSPACE
TAB = '\ue004'
CLEAR = '\ue005'
RETURN = '\ue006'
ENTER = '\ue007'
SHIFT = '\ue008'
LEFT_SHIFT = SHIFT
CONTROL = '\ue009'
LEFT_CONTROL = CONTROL
ALT = '\ue00a'
LEFT_ALT = ALT
PAUSE = '\ue00b'
ESCAPE = '\ue00c'
SPACE = '\ue00d'
PAGE_UP = '\ue00e'
PAGE_DOWN = '\ue00f'
END = '\ue010'
HOME = '\ue011'
LEFT = '\ue012'
ARROW_LEFT = LEFT
UP = '\ue013'
ARROW_UP = UP
RIGHT = '\ue014'
ARROW_RIGHT = RIGHT
DOWN = '\ue015'
ARROW_DOWN = DOWN
INSERT = '\ue016'
DELETE = '\ue017'
SEMICOLON = '\ue018'
EQUALS = '\ue019'
NUMPAD0 = '\ue01a' # number pad keys
NUMPAD1 = '\ue01b'
NUMPAD2 = '\ue01c'
NUMPAD3 = '\ue01d'
NUMPAD4 = '\ue01e'
NUMPAD5 = '\ue01f'
NUMPAD6 = '\ue020'
NUMPAD7 = '\ue021'
NUMPAD8 = '\ue022'
NUMPAD9 = '\ue023'
MULTIPLY = '\ue024'
ADD = '\ue025'
SEPARATOR = '\ue026'
SUBTRACT = '\ue027'
DECIMAL = '\ue028'
DIVIDE = '\ue029'
F1 = '\ue031' # function keys
F2 = '\ue032'
F3 = '\ue033'
F4 = '\ue034'
F5 = '\ue035'
F6 = '\ue036'
F7 = '\ue037'
F8 = '\ue038'
F9 = '\ue039'
F10 = '\ue03a'
F11 = '\ue03b'
F12 = '\ue03c'
META = '\ue03d'
COMMAND = '\ue03d'

@ -0,0 +1,74 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 abc import ABCMeta, abstractmethod
class BaseOptions(object):
"""
Base class for individual browser options
"""
__metaclass__ = ABCMeta
def __init__(self):
self._caps = self.default_capabilities
@property
def capabilities(self):
return self._caps
def set_capability(self, name, value):
""" Sets a capability """
self._caps[name] = value
@abstractmethod
def to_capabilities(self):
return
@property
@abstractmethod
def default_capabilities(self):
return {}
class ArgOptions(BaseOptions):
def __init__(self):
super(ArgOptions, self).__init__()
self._arguments = []
@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')
def to_capabilities(self):
return self._caps

@ -0,0 +1,358 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 Proxy implementation.
"""
class ProxyTypeFactory:
"""
Factory for proxy types.
"""
@staticmethod
def make(ff_value, string):
return {'ff_value': ff_value, 'string': string}
class ProxyType:
"""
Set of possible types of proxy.
Each proxy type has 2 properties:
'ff_value' is value of Firefox profile preference,
'string' is id of proxy type.
"""
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') # Not initialized (for internal use).
@classmethod
def load(cls, value):
if isinstance(value, dict) and 'string' in value:
value = value['string']
value = str(value).upper()
for attr in dir(cls):
attr_value = getattr(cls, attr)
if isinstance(attr_value, dict) and \
'string' in attr_value and \
attr_value['string'] is not None and \
attr_value['string'] == value:
return attr_value
raise Exception("No proxy type is found for %s" % (value))
class Proxy(object):
"""
Proxy contains information about proxy type and necessary proxy settings.
"""
proxyType = ProxyType.UNSPECIFIED
autodetect = False
ftpProxy = ''
httpProxy = ''
noProxy = ''
proxyAutoconfigUrl = ''
sslProxy = ''
socksProxy = ''
socksUsername = ''
socksPassword = ''
socksVersion = None
def __init__(self, raw=None):
"""
Creates a new Proxy.
:Args:
- raw: raw proxy data. If None, default class values are used.
"""
if raw is not None:
if 'proxyType' in raw and raw['proxyType'] is not None:
self.proxy_type = ProxyType.load(raw['proxyType'])
if 'ftpProxy' in raw and raw['ftpProxy'] is not None:
self.ftp_proxy = raw['ftpProxy']
if 'httpProxy' in raw and raw['httpProxy'] is not None:
self.http_proxy = raw['httpProxy']
if 'noProxy' in raw and raw['noProxy'] is not None:
self.no_proxy = raw['noProxy']
if 'proxyAutoconfigUrl' in raw and raw['proxyAutoconfigUrl'] is not None:
self.proxy_autoconfig_url = raw['proxyAutoconfigUrl']
if 'sslProxy' in raw and raw['sslProxy'] is not None:
self.sslProxy = raw['sslProxy']
if 'autodetect' in raw and raw['autodetect'] is not None:
self.auto_detect = raw['autodetect']
if 'socksProxy' in raw and raw['socksProxy'] is not None:
self.socks_proxy = raw['socksProxy']
if 'socksUsername' in raw and raw['socksUsername'] is not None:
self.socks_username = raw['socksUsername']
if 'socksPassword' in raw and raw['socksPassword'] is not None:
self.socks_password = raw['socksPassword']
if 'socksVersion' in raw and raw['socksVersion'] is not None:
self.socks_version = raw['socksVersion']
@property
def proxy_type(self):
"""
Returns proxy type as `ProxyType`.
"""
return self.proxyType
@proxy_type.setter
def proxy_type(self, value):
"""
Sets proxy type.
:Args:
- value: The proxy type.
"""
self._verify_proxy_type_compatibility(value)
self.proxyType = value
@property
def auto_detect(self):
"""
Returns autodetect setting.
"""
return self.autodetect
@auto_detect.setter
def auto_detect(self, value):
"""
Sets autodetect setting.
:Args:
- value: The autodetect value.
"""
if isinstance(value, bool):
if self.autodetect is not value:
self._verify_proxy_type_compatibility(ProxyType.AUTODETECT)
self.proxyType = ProxyType.AUTODETECT
self.autodetect = value
else:
raise ValueError("Autodetect proxy value needs to be a boolean")
@property
def ftp_proxy(self):
"""
Returns ftp proxy setting.
"""
return self.ftpProxy
@ftp_proxy.setter
def ftp_proxy(self, value):
"""
Sets ftp proxy setting.
:Args:
- value: The ftp proxy value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.ftpProxy = value
@property
def http_proxy(self):
"""
Returns http proxy setting.
"""
return self.httpProxy
@http_proxy.setter
def http_proxy(self, value):
"""
Sets http proxy setting.
:Args:
- value: The http proxy value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.httpProxy = value
@property
def no_proxy(self):
"""
Returns noproxy setting.
"""
return self.noProxy
@no_proxy.setter
def no_proxy(self, value):
"""
Sets noproxy setting.
:Args:
- value: The noproxy value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.noProxy = value
@property
def proxy_autoconfig_url(self):
"""
Returns proxy autoconfig url setting.
"""
return self.proxyAutoconfigUrl
@proxy_autoconfig_url.setter
def proxy_autoconfig_url(self, value):
"""
Sets proxy autoconfig url setting.
:Args:
- value: The proxy autoconfig url value.
"""
self._verify_proxy_type_compatibility(ProxyType.PAC)
self.proxyType = ProxyType.PAC
self.proxyAutoconfigUrl = value
@property
def ssl_proxy(self):
"""
Returns https proxy setting.
"""
return self.sslProxy
@ssl_proxy.setter
def ssl_proxy(self, value):
"""
Sets https proxy setting.
:Args:
- value: The https proxy value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.sslProxy = value
@property
def socks_proxy(self):
"""
Returns socks proxy setting.
"""
return self.socksProxy
@socks_proxy.setter
def socks_proxy(self, value):
"""
Sets socks proxy setting.
:Args:
- value: The socks proxy value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.socksProxy = value
@property
def socks_username(self):
"""
Returns socks proxy username setting.
"""
return self.socksUsername
@socks_username.setter
def socks_username(self, value):
"""
Sets socks proxy username setting.
:Args:
- value: The socks proxy username value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.socksUsername = value
@property
def socks_password(self):
"""
Returns socks proxy password setting.
"""
return self.socksPassword
@socks_password.setter
def socks_password(self, value):
"""
Sets socks proxy password setting.
:Args:
- value: The socks proxy password value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.socksPassword = value
@property
def socks_version(self):
"""
Returns socks proxy version setting.
"""
return self.socksVersion
@socks_version.setter
def socks_version(self, value):
"""
Sets socks proxy version setting.
:Args:
- value: The socks proxy version value.
"""
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
self.proxyType = ProxyType.MANUAL
self.socksVersion = value
def _verify_proxy_type_compatibility(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):
"""
Adds proxy information as capability in specified capabilities.
:Args:
- capabilities: The capabilities to which proxy will be added.
"""
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
if self.noProxy:
proxy_caps['noProxy'] = self.noProxy
if self.socksProxy:
proxy_caps['socksProxy'] = self.socksProxy
if self.socksUsername:
proxy_caps['socksUsername'] = self.socksUsername
if self.socksPassword:
proxy_caps['socksPassword'] = self.socksPassword
if self.socksVersion:
proxy_caps['socksVersion'] = self.socksVersion
capabilities['proxy'] = proxy_caps

@ -0,0 +1,178 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 errno
import os
import platform
import subprocess
from subprocess import PIPE
import time
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common import utils
try:
from subprocess import DEVNULL
_HAS_NATIVE_DEVNULL = True
except ImportError:
DEVNULL = -3
_HAS_NATIVE_DEVNULL = False
class Service(object):
def __init__(self, executable, port=0, log_file=DEVNULL, env=None, start_error_message=""):
self.path = executable
self.port = port
if self.port == 0:
self.port = utils.free_port()
if not _HAS_NATIVE_DEVNULL and log_file == DEVNULL:
log_file = open(os.devnull, 'wb')
self.start_error_message = start_error_message
self.log_file = log_file
self.env = env or os.environ
@property
def service_url(self):
"""
Gets the url of the Service
"""
return "http://%s" % utils.join_host_port('localhost', self.port)
def command_line_args(self):
raise NotImplemented("This method needs to be implemented in a sub class")
def start(self):
"""
Starts the 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]
cmd.extend(self.command_line_args())
self.process = subprocess.Popen(cmd, env=self.env,
close_fds=platform.system() != 'Windows',
stdout=self.log_file,
stderr=self.log_file,
stdin=PIPE)
except TypeError:
raise
except OSError as err:
if err.errno == errno.ENOENT:
raise WebDriverException(
"'%s' executable needs to be in PATH. %s" % (
os.path.basename(self.path), self.start_error_message)
)
elif err.errno == errno.EACCES:
raise WebDriverException(
"'%s' executable may have wrong permissions. %s" % (
os.path.basename(self.path), self.start_error_message)
)
else:
raise
except Exception as e:
raise WebDriverException(
"The executable %s needs to be available in the path. %s\n%s" %
(os.path.basename(self.path), self.start_error_message, str(e)))
count = 0
while True:
self.assert_process_still_running()
if self.is_connectable():
break
count += 1
time.sleep(1)
if count == 30:
raise WebDriverException("Can not connect to the Service %s" % self.path)
def assert_process_still_running(self):
return_code = self.process.poll()
if return_code is not None:
raise WebDriverException(
'Service %s unexpectedly exited. Status code was: %s'
% (self.path, return_code)
)
def is_connectable(self):
return utils.is_connectable(self.port)
def send_remote_shutdown_command(self):
try:
from urllib import request as url_request
URLError = url_request.URLError
except ImportError:
import urllib2 as url_request
import urllib2
URLError = urllib2.URLError
try:
url_request.urlopen("%s/shutdown" % self.service_url)
except URLError:
return
for x in range(30):
if not self.is_connectable():
break
else:
time.sleep(1)
def stop(self):
"""
Stops the service.
"""
if self.log_file != PIPE and not (self.log_file == DEVNULL and _HAS_NATIVE_DEVNULL):
try:
self.log_file.close()
except Exception:
pass
if self.process is None:
return
try:
self.send_remote_shutdown_command()
except TypeError:
pass
try:
if self.process:
for stream in [self.process.stdin,
self.process.stdout,
self.process.stderr]:
try:
stream.close()
except AttributeError:
pass
self.process.terminate()
self.process.wait()
self.process.kill()
self.process = None
except OSError:
pass
def __del__(self):
# `subprocess.Popen` doesn't send signal on `__del__`;
# so we attempt to close the launched process when `__del__`
# is triggered.
try:
self.stop()
except Exception:
pass

@ -0,0 +1,192 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 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 performs user actions.
It should be with 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:
- on_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:
- on_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):
"""
Touch down at given coordinates.
:Args:
- xcoord: X Coordinate to touch down.
- ycoord: Y Coordinate to touch down.
"""
self._actions.append(lambda: self._driver.execute(
Command.TOUCH_DOWN, {
'x': int(xcoord),
'y': int(ycoord)}))
return self
def move(self, xcoord, ycoord):
"""
Move held tap to specified location.
:Args:
- xcoord: X Coordinate to move.
- ycoord: Y Coordinate to move.
"""
self._actions.append(lambda: self._driver.execute(
Command.TOUCH_MOVE, {
'x': int(xcoord),
'y': int(ycoord)}))
return self
def release(self, xcoord, ycoord):
"""
Release previously issued tap 'and hold' command at specified location.
:Args:
- xcoord: X Coordinate to release.
- ycoord: Y Coordinate to release.
"""
self._actions.append(lambda: self._driver.execute(
Command.TOUCH_UP, {
'x': int(xcoord),
'y': int(ycoord)}))
return self
def scroll(self, xoffset, yoffset):
"""
Touch and scroll, moving by xoffset and yoffset.
:Args:
- xoffset: X offset to scroll to.
- yoffset: Y offset to scroll to.
"""
self._actions.append(lambda: self._driver.execute(
Command.TOUCH_SCROLL, {
'xoffset': int(xoffset),
'yoffset': int(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: The element where scroll starts.
- xoffset: X offset to scroll to.
- yoffset: Y offset to scroll to.
"""
self._actions.append(lambda: self._driver.execute(
Command.TOUCH_SCROLL, {
'element': on_element.id,
'xoffset': int(xoffset),
'yoffset': int(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': int(xspeed),
'yspeed': int(yspeed)}))
return self
def flick_element(self, on_element, xoffset, yoffset, speed):
"""
Flick starting at on_element, and moving by the xoffset and yoffset
with specified speed.
: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': int(xoffset),
'yoffset': int(yoffset),
'speed': int(speed)}))
return self
# Context manager so TouchActions can be used in a 'with .. as' statements.
def __enter__(self):
return self # Return created instance of self.
def __exit__(self, _type, _value, _traceback):
pass # Do nothing, does not require additional cleanup.

@ -0,0 +1,155 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 Utils methods.
"""
import socket
from selenium.webdriver.common.keys import Keys
try:
# Python 2
basestring
_is_connectable_exceptions = (socket.error,)
except NameError:
# Python 3
basestring = str
_is_connectable_exceptions = (socket.error, ConnectionResetError)
def free_port():
"""
Determines a free port using sockets.
"""
free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
free_socket.bind(('0.0.0.0', 0))
free_socket.listen(5)
port = free_socket.getsockname()[1]
free_socket.close()
return port
def find_connectable_ip(host, port=None):
"""Resolve a hostname to an IP, preferring IPv4 addresses.
We prefer IPv4 so that we don't change behavior from previous IPv4-only
implementations, and because some drivers (e.g., FirefoxDriver) do not
support IPv6 connections.
If the optional port number is provided, only IPs that listen on the given
port are considered.
:Args:
- host - A hostname.
- port - Optional port number.
:Returns:
A single IP address, as a string. If any IPv4 address is found, one is
returned. Otherwise, if any IPv6 address is found, one is returned. If
neither, then None is returned.
"""
try:
addrinfos = socket.getaddrinfo(host, None)
except socket.gaierror:
return None
ip = None
for family, _, _, _, sockaddr in addrinfos:
connectable = True
if port:
connectable = is_connectable(port, sockaddr[0])
if connectable and family == socket.AF_INET:
return sockaddr[0]
if connectable and not ip and family == socket.AF_INET6:
ip = sockaddr[0]
return ip
def join_host_port(host, port):
"""Joins a hostname and port together.
This is a minimal implementation intended to cope with IPv6 literals. For
example, _join_host_port('::1', 80) == '[::1]:80'.
:Args:
- host - A hostname.
- port - An integer port.
"""
if ':' in host and not host.startswith('['):
return '[%s]:%d' % (host, port)
return '%s:%d' % (host, port)
def is_connectable(port, host="localhost"):
"""
Tries to connect to the server at port to see if it is running.
:Args:
- port - The port to connect.
"""
socket_ = None
try:
socket_ = socket.create_connection((host, port), 1)
result = True
except _is_connectable_exceptions:
result = False
finally:
if socket_:
socket_.close()
return result
def is_url_connectable(port):
"""
Tries to connect to the HTTP server at /status path
and specified port to see if it responds successfully.
:Args:
- port - The port to connect.
"""
try:
from urllib import request as url_request
except ImportError:
import urllib2 as url_request
try:
res = url_request.urlopen("http://127.0.0.1:%s/status" % port)
if res.getcode() == 200:
return True
else:
return False
except Exception:
return False
def keys_to_typing(value):
"""Processes the values that will be typed in the 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])
return typing

@ -0,0 +1,29 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 WindowTypes implementation.
"""
from __future__ import unicode_literals
class WindowTypes(object):
"""Set of supported window types."""
TAB = 'tab'
WINDOW = 'window'

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,51 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.options import BaseOptions
class Options(BaseOptions):
def __init__(self):
super(Options, self).__init__()
self._page_load_strategy = "normal"
@property
def page_load_strategy(self):
return self._page_load_strategy
@page_load_strategy.setter
def page_load_strategy(self, value):
if value not in ['normal', 'eager', 'none']:
raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
self._page_load_strategy = value
def to_capabilities(self):
"""
Creates a capabilities with all the options that have been set and
:Returns: A dictionary with everything
"""
caps = self._caps
caps['pageLoadStrategy'] = self._page_load_strategy
return caps
@property
def default_capabilities(self):
return DesiredCapabilities.EDGE.copy()

@ -0,0 +1,57 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 service
class Service(service.Service):
def __init__(self, executable_path, port=0, verbose=False, log_path=None):
"""
Creates a new instance of the EdgeDriver service.
EdgeDriver provides an interface for Microsoft WebDriver to use
with Microsoft Edge.
:param executable_path: Path to the Microsoft WebDriver binary.
:param port: Run the remote service on a specified port.
Defaults to 0, which binds to a random open port of the
system's choosing.
:verbose: Whether to make the webdriver more verbose (passes the
--verbose option to the binary). Defaults to False.
:param log_path: Optional path for the webdriver binary to log to.
Defaults to None which disables logging.
"""
self.service_args = []
if verbose:
self.service_args.append("--verbose")
params = {
"executable": executable_path,
"port": port,
"start_error_message": "Please download from https://go.microsoft.com/fwlink/?LinkId=619687"
}
if log_path:
params["log_file"] = open(log_path, "a+")
service.Service.__init__(self, **params)
def command_line_args(self):
return ["--port=%d" % self.port] + self.service_args

@ -0,0 +1,92 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 warnings
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.edge.service import Service
from selenium.webdriver.remote.remote_connection import RemoteConnection
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
DEFAULT_PORT = 0
DEFAULT_SERVICE_LOG_PATH = None
class WebDriver(RemoteWebDriver):
def __init__(self, executable_path='MicrosoftWebDriver.exe',
capabilities=None, port=DEFAULT_PORT, verbose=False,
service_log_path=None, log_path=DEFAULT_SERVICE_LOG_PATH,
service=None, options=None, keep_alive=False):
"""
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
- capabilities - Dictionary object with non-browser specific
capabilities only, such as "proxy" or "loggingPref".
- port - port you would like the service to run, if left as 0, a free port will be found.
- verbose - whether to set verbose logging in the service
- service_log_path - Where to log information from the driver.
- keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive.
"""
if port != DEFAULT_PORT:
warnings.warn('port has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
self.port = port
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if capabilities is not None:
warnings.warn('capabilities has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if verbose:
warnings.warn('verbose has been deprecated, please pass in a Service object',
DeprecationWarning, stacklevel=2)
if service:
self.service = service
else:
self.service = Service(executable_path, port=self.port, verbose=verbose,
log_path=service_log_path)
self.service.start()
if capabilities is None:
capabilities = DesiredCapabilities.EDGE
RemoteWebDriver.__init__(
self,
command_executor=RemoteConnection(self.service.service_url,
resolve_ip=False,
keep_alive=keep_alive),
desired_capabilities=capabilities)
self._is_remote = False
@property
def edge_service(self):
warnings.warn("'edge_service' has been deprecated, please use 'service'",
DeprecationWarning, stacklevel=2)
return self.service
def quit(self):
RemoteWebDriver.quit(self)
self.service.stop()

@ -0,0 +1,16 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.

@ -0,0 +1,84 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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
timeout = int(timeout)
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, timeout=timeout)
_URL = "http://%s:%d/hub" % (HOST, PORT)
RemoteConnection.__init__(
self, _URL, keep_alive=True)
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.profile.port)
class ExtensionConnectionError(Exception):
"""An internal error occurred int the extension.
Might be caused by bad input or bugs in webdriver
"""
pass

@ -0,0 +1,217 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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, 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, log_file=None):
"""
Creates a new instance of Firefox binary.
:Args:
- firefox_path - Path to the Firefox executable. By default, it will be detected from the standard locations.
- log_file - A file object to redirect the firefox process output to. It can be sys.stdout.
Please note that with parallel run the output won't be synchronous.
By default, it will be redirected to /dev/null.
"""
self._start_cmd = firefox_path
# We used to default to subprocess.PIPE instead of /dev/null, but after
# a while the pipe would fill up and Firefox would freeze.
self._log_file = log_file or open(os.devnull, "wb")
self.command_line = None
if self._start_cmd is None:
self._start_cmd = self._get_firefox_start_cmd()
if not self._start_cmd.strip():
raise WebDriverException(
"Failed to find firefox binary. You can set it by specifying "
"the path to 'firefox_binary':\n\nfrom "
"selenium.webdriver.firefox.firefox_binary import "
"FirefoxBinary\n\nbinary = "
"FirefoxBinary('/path/to/binary')\ndriver = "
"webdriver.Firefox(firefox_binary=binary)")
# Rather than modifying the environment of the calling Python process
# copy it and modify as needed.
self._firefox_env = os.environ.copy()
self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1"
self._firefox_env["MOZ_NO_REMOTE"] = "1"
self._firefox_env["NO_EM_RESTART"] = "1"
def add_command_line_options(self, *args):
self.command_line = args
def launch_browser(self, profile, timeout=30):
"""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(timeout=timeout)
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
if platform.system().lower() == 'linux':
self._modify_link_library_path()
command = [self._start_cmd, "-foreground"]
if self.command_line is not None:
for cli in self.command_line:
command.append(cli)
self.process = Popen(
command, stdout=self._log_file, stderr=STDOUT,
env=self._firefox_env)
def _wait_until_connectable(self, timeout=30):
"""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. If you specified a log_file in "
"the FirefoxBinary constructor, check it for details.")
if count >= timeout:
self.kill()
raise WebDriverException(
"Can't load the profile. Possible firefox version mismatch. "
"You must use GeckoDriver instead for Firefox 48+. Profile "
"Dir: %s If you specified a log_file in the "
"FirefoxBinary constructor, check it for details."
% (self.profile.path))
count += 1
time.sleep(1)
return True
def _find_exe_in_registry(self):
try:
from _winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER
except ImportError:
from winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER
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 OSError:
try:
key = OpenKey(HKEY_CURRENT_USER, path)
command = QueryValue(key, "")
break
except OSError:
pass
else:
return ""
if not command:
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"
# fallback to homebrew installation for mac users
if not os.path.exists(start_cmd):
start_cmd = os.path.expanduser("~") + start_cmd
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:
for ffname in ["firefox", "iceweasel"]:
start_cmd = self.which(ffname)
if start_cmd is not None:
break
else:
# couldn't find firefox on the system path
raise RuntimeError(
"Could not find firefox in your system PATH." +
" Please specify the firefox binary location or install firefox")
return start_cmd
def _default_windows_location(self):
program_files = [os.getenv("PROGRAMFILES", r"C:\Program Files"),
os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)")]
for path in program_files:
binary_path = os.path.join(path, r"Mozilla Firefox\firefox.exe")
if os.access(binary_path, os.X_OK):
return binary_path
return ""
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)
if not os.path.exists(library_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

@ -0,0 +1,364 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 json
import os
import re
import shutil
import sys
import tempfile
import zipfile
try:
from cStringIO import StringIO as BytesIO
except ImportError:
from io import BytesIO
from xml.dom import minidom
from selenium.common.exceptions import WebDriverException
WEBDRIVER_EXT = "webdriver.xpi"
WEBDRIVER_PREFERENCES = "webdriver_prefs.json"
EXTENSION_NAME = "fxdriver@googlecode.com"
class AddonFormatError(Exception):
"""Exception for not well-formed add-on manifest files"""
class FirefoxProfile(object):
ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE"
DEFAULT_PREFERENCES = None
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. If a
directory is passed in it will be cloned and the cloned directory
will be used by the driver when instantiated.
This defaults to None and will create a new
directory when object is created.
"""
if not FirefoxProfile.DEFAULT_PREFERENCES:
with open(os.path.join(os.path.dirname(__file__),
WEBDRIVER_PREFERENCES)) as default_prefs:
FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)
self.default_preferences = copy.deepcopy(
FirefoxProfile.DEFAULT_PREFERENCES['mutable'])
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
os.chmod(self.profile_dir, 0o755)
self._read_existing_userjs(os.path.join(self.profile_dir, "user.js"))
self.extensionsDir = os.path.join(self.profile_dir, "extensions")
self.userPrefs = os.path.join(self.profile_dir, "user.js")
if os.path.isfile(self.userPrefs):
os.chmod(self.userPrefs, 0o644)
# Public Methods
def set_preference(self, key, value):
"""
sets the preference that we want in the profile.
"""
self.default_preferences[key] = value
def add_extension(self, extension=WEBDRIVER_EXT):
self._install_extension(extension)
def update_preferences(self):
for key, value in FirefoxProfile.DEFAULT_PREFERENCES['frozen'].items():
self.default_preferences[key] = value
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")
try:
port = int(port)
if port < 1 or port > 65535:
raise WebDriverException("Port number must be in the range 1..65535")
except (ValueError, TypeError):
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.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.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 encoded(self):
"""
A zipped, base64 encoded string of profile directory
for use with remote WebDriver JSON wire protocol
"""
self.update_preferences()
fp = BytesIO()
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.b64encode(fp.getvalue()).decode('UTF-8')
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, json.dumps(value)))
def _read_existing_userjs(self, userjs):
import warnings
PREF_RE = re.compile(r'user_pref\("(.*)",\s(.*)\)')
try:
with open(userjs) as f:
for usr in f:
matches = re.search(PREF_RE, usr)
try:
self.default_preferences[matches.group(1)] = json.loads(matches.group(2))
except Exception:
warnings.warn("(skipping) failed to json.loads existing preference: " +
matches.group(1) + matches.group(2))
except Exception:
# 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, absolute 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('/'):
if not os.path.isdir(os.path.join(tmpdir, name)):
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
addon_path = os.path.join(self.extensionsDir, addon_id)
if not unpack and not addon_details['unpack'] and xpifile:
if not os.path.exists(self.extensionsDir):
os.makedirs(self.extensionsDir)
os.chmod(self.extensionsDir, 0o755)
shutil.copy(xpifile, addon_path + '.xpi')
else:
if not os.path.exists(addon_path):
shutil.copytree(addon, addon_path, symlinks=True)
# remove the temporary directory, if any
if tmpdir:
shutil.rmtree(tmpdir)
def _addon_details(self, addon_path):
"""
Returns a dictionary of details about the addon.
:param addon_path: path to the add-on directory or XPI
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
"""
details = {
'id': None,
'unpack': False,
'name': None,
'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()
def parse_manifest_json(content):
"""Extracts the details from the contents of a WebExtensions `manifest.json` file."""
manifest = json.loads(content)
try:
id = manifest['applications']['gecko']['id']
except KeyError:
id = manifest['name'].replace(" ", "") + "@" + manifest['version']
return {
'id': id,
'version': manifest['version'],
'name': manifest['version'],
'unpack': False,
}
if not os.path.exists(addon_path):
raise IOError('Add-on path does not exist: %s' % addon_path)
try:
if zipfile.is_zipfile(addon_path):
# Bug 944361 - We cannot use 'with' together with zipFile because
# it will cause an exception thrown in Python 2.6.
try:
compressed_file = zipfile.ZipFile(addon_path, 'r')
if 'manifest.json' in compressed_file.namelist():
return parse_manifest_json(compressed_file.read('manifest.json'))
manifest = compressed_file.read('install.rdf')
finally:
compressed_file.close()
elif os.path.isdir(addon_path):
manifest_json_filename = os.path.join(addon_path, 'manifest.json')
if os.path.exists(manifest_json_filename):
with open(manifest_json_filename, 'r') as f:
return parse_manifest_json(f.read())
with open(os.path.join(addon_path, 'install.rdf'), 'r') as f:
manifest = f.read()
else:
raise IOError('Add-on path is neither an XPI nor a directory: %s' % addon_path)
except (IOError, KeyError) as e:
raise AddonFormatError(str(e), sys.exc_info()[2])
try:
doc = minidom.parseString(manifest)
# 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)
if description is None:
description = doc.getElementsByTagName('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)})
if details.get('id') is None:
for i in range(description.attributes.length):
attribute = description.attributes.item(i)
if attribute.name == em + 'id':
details.update({'id': attribute.value})
except Exception as e:
raise AddonFormatError(str(e), sys.exc_info()[2])
# turn unpack into a true/false value
if isinstance(details['unpack'], str):
details['unpack'] = details['unpack'].lower() == 'true'
# If no ID is set, the add-on is invalid
if details.get('id') is None:
raise AddonFormatError('Add-on id could not be found.')
return details

@ -0,0 +1,171 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 InvalidArgumentException
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.proxy import Proxy
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
from selenium.webdriver.common.options import ArgOptions
class Log(object):
def __init__(self):
self.level = None
def to_capabilities(self):
if self.level is not None:
return {"log": {"level": self.level}}
return {}
class Options(ArgOptions):
KEY = "moz:firefoxOptions"
def __init__(self):
super(Options, self).__init__()
self._binary = None
self._preferences = {}
self._profile = None
self._proxy = None
self.log = Log()
@property
def binary(self):
"""Returns the FirefoxBinary instance"""
return self._binary
@binary.setter
def binary(self, new_binary):
"""Sets location of the browser binary, either by string or
``FirefoxBinary`` instance.
"""
if not isinstance(new_binary, FirefoxBinary):
new_binary = FirefoxBinary(new_binary)
self._binary = new_binary
@property
def binary_location(self):
"""
:Returns: The location of the binary.
"""
return self.binary._start_cmd
@binary_location.setter # noqa
def binary_location(self, value):
""" Sets the location of the browser binary by string """
self.binary = value
@property
def accept_insecure_certs(self):
return self._caps.get('acceptInsecureCerts')
@accept_insecure_certs.setter
def accept_insecure_certs(self, value):
self._caps['acceptInsecureCerts'] = value
@property
def preferences(self):
""":Returns: A dict of preferences."""
return self._preferences
def set_preference(self, name, value):
"""Sets a preference."""
self._preferences[name] = value
@property
def proxy(self):
"""
:Returns: Proxy if set, otherwise None.
"""
return self._proxy
@proxy.setter
def proxy(self, value):
if not isinstance(value, Proxy):
raise InvalidArgumentException("Only Proxy objects can be passed in.")
self._proxy = value
@property
def profile(self):
"""
:Returns: The Firefox profile to use.
"""
return self._profile
@profile.setter
def profile(self, new_profile):
"""Sets location of the browser profile to use, either by string
or ``FirefoxProfile``.
"""
if not isinstance(new_profile, FirefoxProfile):
new_profile = FirefoxProfile(new_profile)
self._profile = new_profile
@property
def headless(self):
"""
:Returns: True if the headless argument is set, else False
"""
return '-headless' in self._arguments
@headless.setter
def headless(self, value):
"""
Sets the headless argument
Args:
value: boolean value indicating to set the headless option
"""
if value is True:
self._arguments.append('-headless')
elif '-headless' in self._arguments:
self._arguments.remove('-headless')
def to_capabilities(self):
"""Marshals the Firefox options to a `moz:firefoxOptions`
object.
"""
# This intentionally looks at the internal properties
# so if a binary or profile has _not_ been set,
# it will defer to geckodriver to find the system Firefox
# and generate a fresh profile.
caps = self._caps
opts = {}
if self._binary is not None:
opts["binary"] = self._binary._start_cmd
if len(self._preferences) > 0:
opts["prefs"] = self._preferences
if self._proxy is not None:
self._proxy.add_to_capabilities(caps)
if self._profile is not None:
opts["profile"] = self._profile.encoded
if len(self._arguments) > 0:
opts["args"] = self._arguments
opts.update(self.log.to_capabilities())
if len(opts) > 0:
caps[Options.KEY] = opts
return caps
@property
def default_capabilities(self):
return DesiredCapabilities.FIREFOX.copy()

@ -0,0 +1,36 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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.remote_connection import RemoteConnection
class FirefoxRemoteConnection(RemoteConnection):
def __init__(self, remote_server_addr, keep_alive=True):
RemoteConnection.__init__(self, remote_server_addr, keep_alive)
self._commands["GET_CONTEXT"] = ('GET', '/session/$sessionId/moz/context')
self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context")
self._commands["ELEMENT_GET_ANONYMOUS_CHILDREN"] = \
("POST", "/session/$sessionId/moz/xbl/$id/anonymous_children")
self._commands["ELEMENT_FIND_ANONYMOUS_ELEMENTS_BY_ATTRIBUTE"] = \
("POST", "/session/$sessionId/moz/xbl/$id/anonymous_by_attribute")
self._commands["INSTALL_ADDON"] = \
("POST", "/session/$sessionId/moz/addon/install")
self._commands["UNINSTALL_ADDON"] = \
("POST", "/session/$sessionId/moz/addon/uninstall")
self._commands["FULL_PAGE_SCREENSHOT"] = \
("GET", "/session/$sessionId/moz/screenshot/full")

@ -0,0 +1,54 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you 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 service
class Service(service.Service):
"""Object that manages the starting and stopping of the
GeckoDriver."""
def __init__(self, executable_path, port=0, service_args=None,
log_path="geckodriver.log", env=None):
"""Creates a new instance of the GeckoDriver remote service proxy.
GeckoDriver provides a HTTP interface speaking the W3C WebDriver
protocol to Marionette.
:param executable_path: Path to the GeckoDriver binary.
:param port: Run the remote service on a specified port.
Defaults to 0, which binds to a random open port of the
system's choosing.
:param service_args: Optional list of arguments to pass to the
GeckoDriver binary.
:param log_path: Optional path for the GeckoDriver to log to.
Defaults to _geckodriver.log_ in the current working directory.
:param env: Optional dictionary of output variables to expose
in the services' environment.
"""
log_file = open(log_path, "a+") if log_path is not None and log_path != "" else None
service.Service.__init__(
self, executable_path, port=port, log_file=log_file, env=env)
self.service_args = service_args or []
def command_line_args(self):
return ["--port", "%d" % self.port] + self.service_args
def send_remote_shutdown_command(self):
pass

Some files were not shown because too many files have changed in this diff Show More