You've already forked SeleniumHQ.selenium.py
Running python unit tests with bazel
Workspace was renamed because bazel does not allow to use the same name for the workspace and the top level python package. Python subdirectory was renamed to avoid conflicts with 'py' package (https://pypi.org/project/py/). Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/SeleniumHQ/selenium Cr-Mirrored-Commit: 0e24f411f2271388f313be787d771445092b5a71
This commit is contained in:
CHANGESMANIFEST.inREADME.rstbuild.descconftest.py
docs
Makefile
python.imlsource
api.rst
common
conf.pyindex.rstwebdriver
selenium.webdriver.common.action_chains.rstselenium.webdriver.common.alert.rstselenium.webdriver.common.by.rstselenium.webdriver.common.desired_capabilities.rstselenium.webdriver.common.html5.application_cache.rstselenium.webdriver.common.keys.rstselenium.webdriver.common.proxy.rstselenium.webdriver.common.service.rstselenium.webdriver.common.touch_actions.rstselenium.webdriver.common.utils.rst
webdriver_android
webdriver_chrome
selenium.webdriver.chrome.options.rstselenium.webdriver.chrome.service.rstselenium.webdriver.chrome.webdriver.rst
webdriver_firefox
selenium.webdriver.firefox.extension_connection.rstselenium.webdriver.firefox.firefox_binary.rstselenium.webdriver.firefox.firefox_profile.rstselenium.webdriver.firefox.options.rstselenium.webdriver.firefox.webdriver.rst
webdriver_ie
webdriver_opera
webdriver_phantomjs
webdriver_remote
selenium.webdriver.remote.command.rstselenium.webdriver.remote.errorhandler.rstselenium.webdriver.remote.mobile.rstselenium.webdriver.remote.remote_connection.rstselenium.webdriver.remote.utils.rstselenium.webdriver.remote.webdriver.rstselenium.webdriver.remote.webelement.rst
webdriver_safari
webdriver_support
selenium.webdriver.support.abstract_event_listener.rstselenium.webdriver.support.color.rstselenium.webdriver.support.event_firing_webdriver.rstselenium.webdriver.support.expected_conditions.rstselenium.webdriver.support.select.rstselenium.webdriver.support.wait.rst
webdriver_webkitgtk
selenium
__init__.py
setup.cfgsetup.pycommon
webdriver
__init__.py
android
blackberry
chrome
common
__init__.pyaction_chains.py
actions
__init__.pyaction_builder.pyinput_device.pyinteraction.pykey_actions.pykey_input.pymouse_button.pypointer_actions.pypointer_input.py
alert.pyby.pydesired_capabilities.pyhtml5
keys.pyoptions.pyproxy.pyservice.pytouch_actions.pyutils.pywindow.pyedge
firefox
__init__.pyextension_connection.pyfirefox_binary.pyfirefox_profile.pyoptions.pyremote_connection.pyservice.pywebdriver.pywebelement.py
ie
opera
phantomjs
remote
__init__.pycommand.pyerrorhandler.pyfile_detector.pymobile.pyremote_connection.pyswitch_to.pyutils.pywebdriver.pywebelement.py
safari
support
__init__.pyabstract_event_listener.pycolor.pyevent_firing_webdriver.pyevents.pyexpected_conditions.pyselect.pyui.pywait.py
webkitgtk
test
__init__.py
tox.iniselenium
__init__.py
webdriver
__init__.py
chrome
common
__init__.pyalerts_tests.pyapi_example_tests.pyappcache_tests.pychildren_finding_tests.pyclear_tests.pyclick_scrolling_tests.pyclick_tests.pyconftest.pycookie_tests.pycorrect_event_firing_tests.pydriver_element_finding_tests.pyelement_attribute_tests.pyelement_equality_tests.pyexample2.pyexecuting_async_javascript_tests.pyexecuting_javascript_tests.pyform_handling_tests.pyframe_switching_tests.pygoogle_one_box.pyimplicit_waits_tests.pyinteractions_tests.pynetwork.pyopacity_tests.pypage_load_timeout_tests.pypage_loader.pypage_loading_tests.pyposition_and_size_tests.pyproxy_tests.pyquit_tests.pyrendered_webelement_tests.pyrepr_tests.pyresults_page.pyselect_class_tests.pyselect_element_handling_tests.pystale_reference_tests.pytakes_screenshots_tests.pytext_handling_tests.pytyping_tests.pyutils.pyvisibility_tests.pyw3c_interaction_tests.pywebdriverwait_tests.pywebserver.pywindow_switching_tests.pywindow_tests.py
firefox
__init__.pyconftest.pyff_launcher_tests.pyff_profile_tests.pyff_takes_full_page_screenshots_tests.py
ie
marionette
__init__.pyconftest.pymn_binary_tests.pymn_context_tests.pymn_launcher_tests.pymn_options_tests.pymn_preferences_tests.pymn_profile_tests.pymn_service_tests.pymn_set_context_tests.py
remote
safari
support
unit
701
CHANGES
701
CHANGES
@@ -1,701 +0,0 @@
|
||||
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
20
MANIFEST.in
@@ -1,20 +0,0 @@
|
||||
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 +0,0 @@
|
||||
docs/source/index.rst
|
85
build.desc
85
build.desc
@@ -1,85 +0,0 @@
|
||||
# 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
219
conftest.py
@@ -1,219 +0,0 @@
|
||||
# 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
131
docs/Makefile
@@ -1,131 +0,0 @@
|
||||
# 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."
|
@@ -1,149 +0,0 @@
|
||||
: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`
|
@@ -1,4 +0,0 @@
|
||||
selenium.common.exceptions
|
||||
==========================
|
||||
|
||||
.. automodule:: selenium.common.exceptions
|
@@ -1,274 +0,0 @@
|
||||
# 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"
|
@@ -1,145 +0,0 @@
|
||||
======================
|
||||
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 |
|
||||
+-----------+-------------------------------------------------------+
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.action_chains
|
||||
=======================================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.action_chains
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.alert
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.alert
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.by
|
||||
============================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.by
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.desired_capabilities
|
||||
==============================================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.desired_capabilities
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.html5.application_cache
|
||||
====================================================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.html5.application_cache
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.keys
|
||||
==============================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.keys
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.proxy
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.proxy
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.service
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.service
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.touch_actions
|
||||
=======================================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.touch_actions
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.common.utils
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.common.utils
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.android.webdriver
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.android.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.chrome.options
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.chrome.options
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.chrome.service
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.chrome.service
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.chrome.webdriver
|
||||
===================================
|
||||
|
||||
.. automodule:: selenium.webdriver.chrome.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.firefox.extension_connection
|
||||
===============================================
|
||||
|
||||
.. automodule:: selenium.webdriver.firefox.extension_connection
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.firefox.firefox_binary
|
||||
=========================================
|
||||
|
||||
.. automodule:: selenium.webdriver.firefox.firefox_binary
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.firefox.firefox_profile
|
||||
==========================================
|
||||
|
||||
.. automodule:: selenium.webdriver.firefox.firefox_profile
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.firefox.options
|
||||
==================================
|
||||
|
||||
.. automodule:: selenium.webdriver.firefox.options
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.firefox.webdriver
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.firefox.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.ie.webdriver
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.ie.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.opera.webdriver
|
||||
==================================
|
||||
|
||||
.. automodule:: selenium.webdriver.opera.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.phantomjs.service
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.phantomjs.service
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.phantomjs.webdriver
|
||||
======================================
|
||||
|
||||
.. automodule:: selenium.webdriver.phantomjs.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.command
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.command
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.errorhandler
|
||||
======================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.errorhandler
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.mobile
|
||||
================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.mobile
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.remote_connection
|
||||
===========================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.remote_connection
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.utils
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.utils
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.webdriver
|
||||
===================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.remote.webelement
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.remote.webelement
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.safari.service
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.safari.service
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.safari.webdriver
|
||||
===================================
|
||||
|
||||
.. automodule:: selenium.webdriver.safari.webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.abstract_event_listener
|
||||
==================================================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.abstract_event_listener
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.color
|
||||
================================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.color
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.event_firing_webdriver
|
||||
=================================================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.event_firing_webdriver
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.expected_conditions
|
||||
==============================================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.expected_conditions
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.select
|
||||
=================================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.select
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.support.wait
|
||||
===============================
|
||||
|
||||
.. automodule:: selenium.webdriver.support.wait
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.webkitgtk.options
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.webkitgtk.options
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.webkitgtk.service
|
||||
====================================
|
||||
|
||||
.. automodule:: selenium.webdriver.webkitgtk.service
|
@@ -1,4 +0,0 @@
|
||||
selenium.webdriver.webkitgtk.webdriver
|
||||
======================================
|
||||
|
||||
.. automodule:: selenium.webdriver.webkitgtk.webdriver
|
24
python.iml
24
python.iml
@@ -1,24 +0,0 @@
|
||||
<?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="<PROJECT>" />
|
||||
<option name="serverName" value="<PROJECT>" />
|
||||
<option name="useAlternativeWorkingDir" value="false" />
|
||||
<option name="workingDirSelection" value="<MODULE>" />
|
||||
</component>
|
||||
</module>
|
@@ -1,19 +0,0 @@
|
||||
# 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"
|
@@ -1,18 +0,0 @@
|
||||
# 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
|
@@ -1,315 +0,0 @@
|
||||
# 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
|
@@ -1,39 +0,0 @@
|
||||
# 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'
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,42 +0,0 @@
|
||||
# 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)
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,121 +0,0 @@
|
||||
# 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
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,177 +0,0 @@
|
||||
# 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()
|
@@ -1,33 +0,0 @@
|
||||
# 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')
|
@@ -1,45 +0,0 @@
|
||||
# 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
|
@@ -1,223 +0,0 @@
|
||||
# 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()
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,365 +0,0 @@
|
||||
# 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.
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,85 +0,0 @@
|
||||
# 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)
|
@@ -1,43 +0,0 @@
|
||||
# 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
|
@@ -1,50 +0,0 @@
|
||||
# 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)
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
# 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
|
@@ -1,51 +0,0 @@
|
||||
# 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}
|
@@ -1,23 +0,0 @@
|
||||
# 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
|
@@ -1,101 +0,0 @@
|
||||
# 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
|
@@ -1,64 +0,0 @@
|
||||
# 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]}
|
@@ -1,105 +0,0 @@
|
||||
# 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})
|
@@ -1,35 +0,0 @@
|
||||
# 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"
|
@@ -1,127 +0,0 @@
|
||||
# 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",
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,48 +0,0 @@
|
||||
# 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']
|
@@ -1,96 +0,0 @@
|
||||
# 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'
|
@@ -1,74 +0,0 @@
|
||||
# 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
|
@@ -1,358 +0,0 @@
|
||||
# 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
|
@@ -1,178 +0,0 @@
|
||||
# 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
|
@@ -1,192 +0,0 @@
|
||||
# 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.
|
@@ -1,155 +0,0 @@
|
||||
# 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
|
@@ -1,29 +0,0 @@
|
||||
# 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'
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,51 +0,0 @@
|
||||
# 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()
|
@@ -1,57 +0,0 @@
|
||||
# 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
|
@@ -1,92 +0,0 @@
|
||||
# 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()
|
@@ -1,16 +0,0 @@
|
||||
# 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.
|
@@ -1,84 +0,0 @@
|
||||
# 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
|
@@ -1,217 +0,0 @@
|
||||
# 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
|
@@ -1,364 +0,0 @@
|
||||
# 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
|
@@ -1,171 +0,0 @@
|
||||
# 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()
|
@@ -1,36 +0,0 @@
|
||||
# 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")
|
@@ -1,54 +0,0 @@
|
||||
# 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
|
@@ -1,328 +0,0 @@
|
||||
# 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.
|
||||
try:
|
||||
basestring
|
||||
except NameError: # Python 3.x
|
||||
basestring = str
|
||||
|
||||
import base64
|
||||
import shutil
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
|
||||
from .firefox_binary import FirefoxBinary
|
||||
from .firefox_profile import FirefoxProfile
|
||||
from .options import Options
|
||||
from .remote_connection import FirefoxRemoteConnection
|
||||
from .service import Service
|
||||
from .webelement import FirefoxWebElement
|
||||
|
||||
|
||||
DEFAULT_SERVICE_LOG_PATH = None
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
|
||||
CONTEXT_CHROME = "chrome"
|
||||
CONTEXT_CONTENT = "content"
|
||||
|
||||
_web_element_cls = FirefoxWebElement
|
||||
|
||||
def __init__(self, firefox_profile=None, firefox_binary=None,
|
||||
timeout=30, capabilities=None, proxy=None,
|
||||
executable_path="geckodriver", options=None,
|
||||
service_log_path="geckodriver.log", firefox_options=None,
|
||||
service_args=None, service=None, desired_capabilities=None, log_path=None,
|
||||
keep_alive=True):
|
||||
"""Starts a new local session of Firefox.
|
||||
|
||||
Based on the combination and specificity of the various keyword
|
||||
arguments, a capabilities dictionary will be constructed that
|
||||
is passed to the remote end.
|
||||
|
||||
The keyword arguments given to this constructor are helpers to
|
||||
more easily allow Firefox WebDriver sessions to be customised
|
||||
with different options. They are mapped on to a capabilities
|
||||
dictionary that is passed on to the remote end.
|
||||
|
||||
As some of the options, such as `firefox_profile` and
|
||||
`options.profile` are mutually exclusive, precedence is
|
||||
given from how specific the setting is. `capabilities` is the
|
||||
least specific keyword argument, followed by `options`,
|
||||
followed by `firefox_binary` and `firefox_profile`.
|
||||
|
||||
In practice this means that if `firefox_profile` and
|
||||
`options.profile` are both set, the selected profile
|
||||
instance will always come from the most specific variable.
|
||||
In this case that would be `firefox_profile`. This will result in
|
||||
`options.profile` to be ignored because it is considered
|
||||
a less specific setting than the top-level `firefox_profile`
|
||||
keyword argument. Similarly, if you had specified a
|
||||
`capabilities["moz:firefoxOptions"]["profile"]` Base64 string,
|
||||
this would rank below `options.profile`.
|
||||
|
||||
:param firefox_profile: Instance of ``FirefoxProfile`` object
|
||||
or a string. If undefined, a fresh profile will be created
|
||||
in a temporary location on the system.
|
||||
:param firefox_binary: Instance of ``FirefoxBinary`` or full
|
||||
path to the Firefox binary. If undefined, the system default
|
||||
Firefox installation will be used.
|
||||
:param timeout: Time to wait for Firefox to launch when using
|
||||
the extension connection.
|
||||
:param capabilities: Dictionary of desired capabilities.
|
||||
:param proxy: The proxy settings to use when communicating with
|
||||
Firefox via the extension connection.
|
||||
:param executable_path: Full path to override which geckodriver
|
||||
binary to use for Firefox 47.0.1 and greater, which
|
||||
defaults to picking up the binary from the system path.
|
||||
:param options: Instance of ``options.Options``.
|
||||
:param service_log_path: Where to log information from the driver.
|
||||
:param service_args: List of args to pass to the driver service
|
||||
:param desired_capabilities: alias of capabilities. In future
|
||||
versions of this library, this will replace 'capabilities'.
|
||||
This will make the signature consistent with RemoteWebDriver.
|
||||
:param keep_alive: Whether to configure remote_connection.RemoteConnection to use
|
||||
HTTP keep-alive.
|
||||
"""
|
||||
|
||||
if executable_path != 'geckodriver':
|
||||
warnings.warn('executable_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 firefox_binary is not None:
|
||||
warnings.warn('firefox_binary has been deprecated, please pass in a Service object',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
self.binary = None
|
||||
if firefox_profile is not None:
|
||||
warnings.warn('firefox_profile has been deprecated, please pass in a Service object',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
self.profile = None
|
||||
|
||||
if log_path != DEFAULT_SERVICE_LOG_PATH:
|
||||
warnings.warn('log_path has been deprecated, please pass in a Service object',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
|
||||
self.service = service
|
||||
|
||||
# If desired capabilities is set, alias it to capabilities.
|
||||
# If both are set ignore desired capabilities.
|
||||
if capabilities is None and desired_capabilities:
|
||||
capabilities = desired_capabilities
|
||||
|
||||
if capabilities is None:
|
||||
capabilities = DesiredCapabilities.FIREFOX.copy()
|
||||
if options is None:
|
||||
options = Options()
|
||||
|
||||
capabilities = dict(capabilities)
|
||||
|
||||
if capabilities.get("binary"):
|
||||
self.binary = capabilities["binary"]
|
||||
|
||||
# options overrides capabilities
|
||||
if options is not None:
|
||||
if options.binary is not None:
|
||||
self.binary = options.binary
|
||||
if options.profile is not None:
|
||||
self.profile = options.profile
|
||||
|
||||
# firefox_binary and firefox_profile
|
||||
# override options
|
||||
if firefox_binary is not None:
|
||||
if isinstance(firefox_binary, basestring):
|
||||
firefox_binary = FirefoxBinary(firefox_binary)
|
||||
self.binary = firefox_binary
|
||||
options.binary = firefox_binary
|
||||
if firefox_profile is not None:
|
||||
if isinstance(firefox_profile, basestring):
|
||||
firefox_profile = FirefoxProfile(firefox_profile)
|
||||
self.profile = firefox_profile
|
||||
options.profile = firefox_profile
|
||||
|
||||
if self.service is None:
|
||||
self.service = Service(
|
||||
executable_path,
|
||||
service_args=service_args,
|
||||
log_path=service_log_path)
|
||||
self.service.start()
|
||||
|
||||
capabilities.update(options.to_capabilities())
|
||||
|
||||
executor = FirefoxRemoteConnection(
|
||||
remote_server_addr=self.service.service_url)
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=executor,
|
||||
desired_capabilities=capabilities,
|
||||
keep_alive=True)
|
||||
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
"""Quits the driver and close every associated window."""
|
||||
try:
|
||||
RemoteWebDriver.quit(self)
|
||||
except Exception:
|
||||
# We don't care about the message because something probably has gone wrong
|
||||
pass
|
||||
|
||||
if self.w3c:
|
||||
self.service.stop()
|
||||
else:
|
||||
self.binary.kill()
|
||||
|
||||
if self.profile is not None:
|
||||
try:
|
||||
shutil.rmtree(self.profile.path)
|
||||
if self.profile.tempfolder is not None:
|
||||
shutil.rmtree(self.profile.tempfolder)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
@property
|
||||
def firefox_profile(self):
|
||||
return self.profile
|
||||
|
||||
# Extension commands:
|
||||
|
||||
def set_context(self, context):
|
||||
self.execute("SET_CONTEXT", {"context": context})
|
||||
|
||||
@contextmanager
|
||||
def context(self, context):
|
||||
"""Sets the context that Selenium commands are running in using
|
||||
a `with` statement. The state of the context on the server is
|
||||
saved before entering the block, and restored upon exiting it.
|
||||
|
||||
:param context: Context, may be one of the class properties
|
||||
`CONTEXT_CHROME` or `CONTEXT_CONTENT`.
|
||||
|
||||
Usage example::
|
||||
|
||||
with selenium.context(selenium.CONTEXT_CHROME):
|
||||
# chrome scope
|
||||
... do stuff ...
|
||||
"""
|
||||
initial_context = self.execute('GET_CONTEXT').pop('value')
|
||||
self.set_context(context)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.set_context(initial_context)
|
||||
|
||||
def install_addon(self, path, temporary=None):
|
||||
"""
|
||||
Installs Firefox addon.
|
||||
|
||||
Returns identifier of installed addon. This identifier can later
|
||||
be used to uninstall addon.
|
||||
|
||||
:param path: Absolute path to the addon that will be installed.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.install_addon('/path/to/firebug.xpi')
|
||||
"""
|
||||
payload = {"path": path}
|
||||
if temporary is not None:
|
||||
payload["temporary"] = temporary
|
||||
return self.execute("INSTALL_ADDON", payload)["value"]
|
||||
|
||||
def uninstall_addon(self, identifier):
|
||||
"""
|
||||
Uninstalls Firefox addon using its identifier.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.uninstall_addon('addon@foo.com')
|
||||
"""
|
||||
self.execute("UNINSTALL_ADDON", {"id": identifier})
|
||||
|
||||
def get_full_page_screenshot_as_file(self, filename):
|
||||
"""
|
||||
Saves a full document screenshot of the current window to a PNG image file. Returns
|
||||
False if there is any IOError, else returns True. Use full paths in
|
||||
your filename.
|
||||
|
||||
:Args:
|
||||
- filename: The full path you wish to save your screenshot to. This
|
||||
should end with a `.png` extension.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.get_full_page_screenshot_as_file('/Screenshots/foo.png')
|
||||
"""
|
||||
if not filename.lower().endswith('.png'):
|
||||
warnings.warn("name used for saved screenshot does not match file "
|
||||
"type. It should end with a `.png` extension", UserWarning)
|
||||
png = self.get_full_page_screenshot_as_png()
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(png)
|
||||
except IOError:
|
||||
return False
|
||||
finally:
|
||||
del png
|
||||
return True
|
||||
|
||||
def save_full_page_screenshot(self, filename):
|
||||
"""
|
||||
Saves a full document screenshot of the current window to a PNG image file. Returns
|
||||
False if there is any IOError, else returns True. Use full paths in
|
||||
your filename.
|
||||
|
||||
:Args:
|
||||
- filename: The full path you wish to save your screenshot to. This
|
||||
should end with a `.png` extension.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.save_full_page_screenshot('/Screenshots/foo.png')
|
||||
"""
|
||||
return self.get_full_page_screenshot_as_file(filename)
|
||||
|
||||
def get_full_page_screenshot_as_png(self):
|
||||
"""
|
||||
Gets the full document screenshot of the current window as a binary data.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.get_full_page_screenshot_as_png()
|
||||
"""
|
||||
return base64.b64decode(self.get_full_page_screenshot_as_base64().encode('ascii'))
|
||||
|
||||
def get_full_page_screenshot_as_base64(self):
|
||||
"""
|
||||
Gets the full document screenshot of the current window as a base64 encoded string
|
||||
which is useful in embedded images in HTML.
|
||||
|
||||
:Usage:
|
||||
::
|
||||
|
||||
driver.get_full_page_screenshot_as_base64()
|
||||
"""
|
||||
return self.execute("FULL_PAGE_SCREENSHOT")['value']
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user