You've already forked SeleniumHQ.selenium.py
Renaming back 'py' directory, use of 'legacy_create_init' argument resolved the name collision.
Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/SeleniumHQ/selenium Cr-Mirrored-Commit: b300c358f65f33c0cf43177316f433601c027bdb
This commit is contained in:
BUILD.bazelCHANGESMANIFEST.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__.pyrun_pytest.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
37
BUILD.bazel
Normal file
37
BUILD.bazel
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
genrule(
|
||||||
|
name = "get-attribute",
|
||||||
|
srcs = ["//javascript/webdriver/atoms:get-attribute.js"],
|
||||||
|
outs = ["selenium/webdriver/remote/getAttribute.js"],
|
||||||
|
cmd = "cp $< $@",
|
||||||
|
)
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "is-displayed",
|
||||||
|
srcs = ["//javascript/atoms/fragments:is-displayed.js"],
|
||||||
|
outs = ["selenium/webdriver/remote/isDisplayed.js"],
|
||||||
|
cmd = "cp $< $@",
|
||||||
|
)
|
||||||
|
|
||||||
|
py_library(
|
||||||
|
name = "main",
|
||||||
|
srcs = glob(["selenium/**/*.py"]),
|
||||||
|
data = [
|
||||||
|
":get-attribute",
|
||||||
|
":is-displayed",
|
||||||
|
],
|
||||||
|
imports = ["."],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "unit",
|
||||||
|
size = "small",
|
||||||
|
srcs = glob([
|
||||||
|
"test/unit/**/*.py",
|
||||||
|
]) + [ "test/run_pytest.py" ],
|
||||||
|
main = "test/run_pytest.py",
|
||||||
|
deps = [
|
||||||
|
":main",
|
||||||
|
],
|
||||||
|
legacy_create_init = False,
|
||||||
|
)
|
701
CHANGES
Normal file
701
CHANGES
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
Selenium 4.0 Alpha 1
|
||||||
|
* Update driver initialisation to use service and option objects
|
||||||
|
* turn on keep-alive by default for remote connections (#7072)
|
||||||
|
* Fix ConnectionResetError
|
||||||
|
* Add new Cast commands
|
||||||
|
* Suggest download Microsoft Webdriver over HTTPS
|
||||||
|
* Clear PoolManager in ‘remote_connection’ to ensure sockets are closed
|
||||||
|
* remove --disable-gpu option for headless Chrome
|
||||||
|
* Add support for the New Window command (#6873)
|
||||||
|
* Update docstrings in Options classes to allow documentation to highlight Return values
|
||||||
|
* Fix typos in select.py (#6925)
|
||||||
|
* Remove native events handling code
|
||||||
|
* Deleting unused imports, fixing flake8 issues
|
||||||
|
* Remove unused port selection in IE Driver
|
||||||
|
* Enabling tests xpassed in Chrome
|
||||||
|
* Pretty-printing code samples
|
||||||
|
* remove all deprecated methods and args from Python bindings
|
||||||
|
* Fix DeprecationWarning: invalid escape sequence
|
||||||
|
* Don't override browser options with desired capabilities by default in WebKitGTK (#6814)
|
||||||
|
* Add WebKitGTK to API docs (#6815)
|
||||||
|
* Subclass options classes from a common base class (#6522)
|
||||||
|
* Update Sphinx (#6728)
|
||||||
|
* WebDriverWait: update documentation for until and until_not (#6711)
|
||||||
|
* Fix typo in description of WebDriver class (#6724)
|
||||||
|
* add strictFileInteractability to acceptable W3C capabilities
|
||||||
|
* Deprecate Blackberry Driver support
|
||||||
|
* Fixing/tidying docstring.
|
||||||
|
|
||||||
|
|
||||||
|
Selenium 3.141.0
|
||||||
|
* Bump version to a better approximation of Π
|
||||||
|
* Improved Test build targets
|
||||||
|
* fix os path in test for Windows
|
||||||
|
* use 'NUL' for /dev/null on Windows
|
||||||
|
* Update ctor docstrings to explain that a directory passed in is cloned. Fixes #6542
|
||||||
|
* Allow passing of service_args to Safari. Fixes #6459
|
||||||
|
* Remove element equals url
|
||||||
|
* Improved WebExtension support
|
||||||
|
|
||||||
|
Selenium 3.14.1
|
||||||
|
* Fix ability to set timeout for urllib3 (#6286)
|
||||||
|
* get_cookie uses w3c endpoint when compliant
|
||||||
|
* Remove body from GET requests (#6250)
|
||||||
|
* Fix actions pause for fraction of a second (#6300)
|
||||||
|
* Fixed input pausing for some actions methods
|
||||||
|
* Capabilities can be set on Options classes
|
||||||
|
* WebElement rect method is now forward compatible for OSS endpoints (#6355)
|
||||||
|
* Deprecation warnings now have a stacklevel of 2
|
||||||
|
* keep_alive can now be set on Webdriver init (#6316)
|
||||||
|
* isDisplayed atom is now used for all w3c compliant browser, fixing issue with Safari 12
|
||||||
|
|
||||||
|
Selenium 3.14.0
|
||||||
|
* Fix doc of URL-related ExpectedCondition (#6236)
|
||||||
|
* Added ExpectedCondition invisibility_of_element
|
||||||
|
* Swap out httplib for urllib3
|
||||||
|
* Be consistent with webdriver init kwarg service_log_path (#5725)
|
||||||
|
|
||||||
|
Selenium 3.13.0
|
||||||
|
|
||||||
|
* Add executing Chrome devtools command (#5989)
|
||||||
|
* fix incorrect w3c action encoding in python client (#6014)
|
||||||
|
* Implement context manager for WebDriver
|
||||||
|
* Stop sending "windowHandle" param in maximize_window command for w3c
|
||||||
|
|
||||||
|
Selenium 3.12.0
|
||||||
|
|
||||||
|
* Add desired_capabilities keyword to IE and Firefox drivers for driver consitency
|
||||||
|
* Fix bug with creating Safari webdriver instance (#5578)
|
||||||
|
* Add support for Safari extension command
|
||||||
|
* Deprecate Options `set_headless` methods in favor of property setter
|
||||||
|
* Only set --disable-gpu for Chrome headless when on Windows
|
||||||
|
* Add selenium User-Agent header (#5696)
|
||||||
|
* Remote webdriver can now be started when passing options
|
||||||
|
* All Options.to_capabilities now start with default DesiredCapabilities
|
||||||
|
* Improve the error message that is raised when safaridriver cannot be found (#5739)
|
||||||
|
* IeOptions class is now imported to selenium.webdriver
|
||||||
|
* Remove the beta `authenticate` methods from `Alert`
|
||||||
|
|
||||||
|
Selenium 3.11.0
|
||||||
|
|
||||||
|
No changes just keeping python version in step with the rest of the project.
|
||||||
|
|
||||||
|
Selenium 3.10.0
|
||||||
|
|
||||||
|
* make tests to check clicking on disabled element work for w3c compliant drivers (#5561)
|
||||||
|
* add docstring for InvalidElementStateException. Fixes #5520
|
||||||
|
* Deleting unused imports
|
||||||
|
* Making python specification in IDEA project more generic
|
||||||
|
* It should be possible to use a custom safaridriver executable to run Selenium's test suite.
|
||||||
|
|
||||||
|
Selenium 3.9.0
|
||||||
|
|
||||||
|
* Add docstrings to WebElement find methods (#5384)
|
||||||
|
* Additional data in unexpected alert error is now handled for w3c drivers (#5416)
|
||||||
|
* Allow service_args to be passed into Firefox WebDriver (#5421)
|
||||||
|
* Fix bug introduced with response logging in 3.8.1 (#5362)
|
||||||
|
|
||||||
|
Selenium 3.8.1
|
||||||
|
|
||||||
|
* Fix bug when creating an Opera driver (#5266)
|
||||||
|
* Stop sending sessionId in w3c payload. (#4620)
|
||||||
|
* Fix issue with w3c actions releasing on element (#5180)
|
||||||
|
* A more descriptive log message is displayed if the port cannot be connected (#2913)
|
||||||
|
* Initialize Alert object by calling alert.text (#1863)
|
||||||
|
* PhantomJS is now deprecated, please use either Chrome or Firefox in headless mode
|
||||||
|
* Legacy Firefox driver: ensuring copy of profile dir, its 'extensions' subdir and 'user.js' file are writable. (#1466)
|
||||||
|
|
||||||
|
Selenium 3.8.0
|
||||||
|
|
||||||
|
* Firefox options can now be imported from selenium.webdriver as FirefoxOptions (#5120)
|
||||||
|
* Headless mode can now be set in Chrome Options using `set_headless`
|
||||||
|
* Headless mode can now be set in Firefox Options using `set_headless`
|
||||||
|
* Add the WebKitGTK WebDriver and options class (#4635)
|
||||||
|
* Browser options can now be passed to remote WebDriver via the `options` parameter
|
||||||
|
* Browser option parameters are now standardized across drivers as `options`. `firefox_options`,
|
||||||
|
`chrome_options`, and `ie_options` are now deprecated
|
||||||
|
* Added missing W3C Error Codes (#4556)
|
||||||
|
* Support has been removed for Python versions 2.6 and 3.3
|
||||||
|
|
||||||
|
Selenium 3.7.0
|
||||||
|
|
||||||
|
* need to pass applicable environment variables to tox
|
||||||
|
* Fix active_element for w3c drivers (#3979)
|
||||||
|
* add support for minimize command
|
||||||
|
* add support for fullscreen command
|
||||||
|
* window rect commands should fail on firefox and remote (legacy)
|
||||||
|
* Fix python backward compatibility for window commands (#4937)
|
||||||
|
* Update docstrings to specify the type of path needed to install firefox addons. (#4881)
|
||||||
|
* Update python chromeOptions key for capabilities (#4622)
|
||||||
|
* Fix python pause action implementation (#4795)
|
||||||
|
|
||||||
|
Selenium 3.6.0
|
||||||
|
|
||||||
|
* Fix package name in python webelement module (#4670)
|
||||||
|
* Fix python driver examples (#3872)
|
||||||
|
* No need to multiply pause by 1000
|
||||||
|
* Add pause to action chains
|
||||||
|
* only check for proxyType once
|
||||||
|
* lowercase proxy type for w3c payload in python #4574
|
||||||
|
* guarding against null return value from find_elements in python #4555
|
||||||
|
* remove unnecessary pytest marking, address flake8 issues
|
||||||
|
* allow IE WebDriver to accept IE Options
|
||||||
|
* add IE Options class
|
||||||
|
* convert OSS capabilities to W3C equivalent for W3C payload
|
||||||
|
* Add Safari to API docs
|
||||||
|
|
||||||
|
Selenium 3.5.0
|
||||||
|
|
||||||
|
* Numerous test fixes
|
||||||
|
*Iterate over capabilities in a way to support py2.7 and py3
|
||||||
|
* Fix W3C switching to window by name.
|
||||||
|
* Support GeckoDriver addon install/uninstall commands #4215.
|
||||||
|
* Move firefox_profile into moz:firefoxOptions.
|
||||||
|
* Filter non-W3C capability names out of alwaysMatch.
|
||||||
|
* Honor cmd line args passed to Service ctor (#4167)
|
||||||
|
* Add expected conditions based on URL to Python Expected Conditions #4160
|
||||||
|
* Add network emulation to Chrome Python bindings (#4011)
|
||||||
|
* add warning when saving incorrectly named screenshot (#4141)
|
||||||
|
|
||||||
|
Selenium 3.4.3
|
||||||
|
* Fix EventFiringWebdriver and WebElement to raise AttributeError on missing attributes. (#4107)
|
||||||
|
* unwrap WebElements inside dicts
|
||||||
|
|
||||||
|
Selenium 3.4.2
|
||||||
|
|
||||||
|
* translate move_by_offset command to w3c
|
||||||
|
* Update capabilities properly instead of assuming dict structure. Fixes #3927
|
||||||
|
* Add missing file for Chrome options to API docs.
|
||||||
|
* Add Chrome options module to API docs.
|
||||||
|
|
||||||
|
Selenium 3.4.1
|
||||||
|
* Add back the ability to set profile when using Firefox 45ESR. Fixes #3897
|
||||||
|
|
||||||
|
Selenium 3.4.0
|
||||||
|
* Correct usage of newSession around `firstMatch` and `alwaysMatch`
|
||||||
|
* Remove superfluous capabilities that are not needed
|
||||||
|
* Add expected condition that waits for all found elements to be visible (#3532)
|
||||||
|
* Allow methods wrapped by EventFiringWebDriver and EventFiringWebElement (#806)
|
||||||
|
* Dropping `javascriptEnabled` capability for real browsers
|
||||||
|
* Use W3C check from parent object instead of assuming from capabilities
|
||||||
|
* Bump example source distribution to match latest release.
|
||||||
|
* Replace TypeError with KeyError in remote webdriver error handler code (#3826)
|
||||||
|
* When testing Marionette use default capabilities in testing
|
||||||
|
* Conform to the api of urllib2 for adding header for a request (#3803)
|
||||||
|
* Add `text` key to alert#sendKeys parameters for W3C Endpoint
|
||||||
|
* Location once scrolled into view should use W3C executeScript endpoint not JSONWP
|
||||||
|
* Fixed the usage information in documentation of "save_screenshot". (#3804)
|
||||||
|
* Add Element Not Interactable exception
|
||||||
|
* Clean up imports in error handler
|
||||||
|
* flake8 cleanup
|
||||||
|
|
||||||
|
Selenium 3.3.3
|
||||||
|
|
||||||
|
* make w3c execute_script commands unique
|
||||||
|
|
||||||
|
Selenium 3.3.2
|
||||||
|
|
||||||
|
* Update window commands to use W3C End points
|
||||||
|
* Update Alert when in W3C mode to use W3C Endpoints
|
||||||
|
* Update to new W3C Execute Script end points
|
||||||
|
* Add setting/getting proxy details to Firefox Options
|
||||||
|
* Deprecate the use of browser profile when instantiating a session
|
||||||
|
* Update start session to handle the W3C New Session
|
||||||
|
* Add get/set window rect commands
|
||||||
|
* Add InvalidArgumentException
|
||||||
|
* When passing in `text` to send_keys, make sure we send a string not array
|
||||||
|
* Fix string decoding in remote connection (#3663)
|
||||||
|
* Fix indentation to satisfy PEP8
|
||||||
|
* Try use old way of setting page load timeout if new way fails. Fixes #3654
|
||||||
|
* fix file uploads for Firefox
|
||||||
|
* Run unit tests on Python 3.3, 3.4, and 3.5 (#3638)
|
||||||
|
* Fix indentation in double_click.
|
||||||
|
* Fix non-W3C page load timeout setting.
|
||||||
|
|
||||||
|
Selenium 3.3.1
|
||||||
|
* Fix encoding of basic auth header when using Python 3 Fixes #3622
|
||||||
|
* Add initial unit test suite
|
||||||
|
* Update W3C Timeout setting to be in line with the specification
|
||||||
|
* support.ui.Select class inherits from object (#3067)
|
||||||
|
* fix bug in proxy constructor that some properties are not proper set (#3459)
|
||||||
|
* Fix flake8 issues (#3628)
|
||||||
|
|
||||||
|
Selenium 3.3.0
|
||||||
|
** Note ** If you are updating to this version, please also update GeckoDriver to v0.15.0
|
||||||
|
* Fix python HTTPS encoding for python driver (#3379)
|
||||||
|
* Allow Firefox preferences to be set directly in Options
|
||||||
|
* Fix shutdown and process termination (#3263)
|
||||||
|
* Preventing exception if log_path is none or empty. Fixes #3128
|
||||||
|
* Add the W3C capability to Firefox for accepting insecure certificates
|
||||||
|
* Initial implementation of Pointer Actions
|
||||||
|
* Only skip tests if driver name matches a directory name.
|
||||||
|
* Update calls that return a pure object with keys to look for 'value' key
|
||||||
|
* Initial W3C Actions support
|
||||||
|
* fix docs output directory
|
||||||
|
|
||||||
|
Selenium 3.0.2
|
||||||
|
* Add support for W3C Get Active Element
|
||||||
|
* Return when we use executeScript for faking WebElement.get_property
|
||||||
|
* Expand user paths and resolve absolute path for Chrome extensions
|
||||||
|
* Add support for verbose logging and custom path to EdgeDriver
|
||||||
|
* Update TakeElementScreenshot to match WebDriver specification
|
||||||
|
* Raise WebDriverException when FirefoxBinary fails to locate binary
|
||||||
|
* Fix getting IP for python 3
|
||||||
|
* Write Service log to DEVNULL by default
|
||||||
|
* Only attempt to remove the Firefox profile path if one was specified
|
||||||
|
* Added context manager for chrome/content in Firefox
|
||||||
|
|
||||||
|
Selenium 3.0.1
|
||||||
|
* Fix regressions with python 3
|
||||||
|
* Add support for Safari Technology Preview
|
||||||
|
|
||||||
|
Selenium 3.0.0
|
||||||
|
* new FirefoxDriver ctor precedence logic and moz:firefoxOptions support (#2882)
|
||||||
|
* Add W3C Set Window Position and W3C Get Window Position
|
||||||
|
* enable log path setting from firefox webdriver (#2700)
|
||||||
|
* Correct encoding of getAttribute.js and only load it once. Fixes #2785
|
||||||
|
* Encode the isDisplayed atom and only load it once
|
||||||
|
|
||||||
|
Selenium 3.0.0.b3
|
||||||
|
* Use atoms for get_attribute and is_displayed when communicating with
|
||||||
|
a w3c compliant remote end.
|
||||||
|
* Make it possible to specialise web element
|
||||||
|
|
||||||
|
Selenium 3.0.0.b2
|
||||||
|
* Updated Marionette port argument to match other drivers.
|
||||||
|
|
||||||
|
Selenium 3.0.0.b1
|
||||||
|
* Fix basestring reference to work with python 3. Fixes #1820
|
||||||
|
* Correct Length conditional when filtering in PhantomJS. Fixes #1817
|
||||||
|
* Fix send keys when using PUA keys e.g. Keys.RIGHT #1839
|
||||||
|
* Fix cookie file leak in PhantomJS #1854
|
||||||
|
* Use the correct binary path when using Marionette
|
||||||
|
* Fixed: Unhelpful error message when PhantomJS exits. (#2173 #2168)
|
||||||
|
* Fix broken link to python documentation (#2159)
|
||||||
|
* Attempt to remove Firefox profile when using Marionette
|
||||||
|
* Ensure all capabilities are either within desiredCapabilities or requiredCapabilities
|
||||||
|
* Correct the expected capability name for the Firefox profile
|
||||||
|
* Add Firefox options to capabilities
|
||||||
|
* Visibility_of_all implies it only returns elements if all visible (#2052)
|
||||||
|
* Find visible elements (#2041)
|
||||||
|
* Pass the firefox_profile as a desired capability in the Python client when using a remote server
|
||||||
|
* Avoid checking exception details for invalid locators due to differences in server implementations
|
||||||
|
* Handle capabilities better with Marionette and GeckoDriver
|
||||||
|
* Updated the maxVersion of FirefoxDriver xpi maxVersion to work with Firefox 47.0.1
|
||||||
|
* Remove Selenium RC support
|
||||||
|
|
||||||
|
Selenium 2.53.0
|
||||||
|
* Adding Options object for use with Python FirefoxDriver
|
||||||
|
* Fixed improper usage of super in exceptions module
|
||||||
|
* create a temp file for cookies in phantomjs if not specified
|
||||||
|
* Pass in the executable that FirefoxBinary finds to the service if there isnt one passed in as a kwarg or capability
|
||||||
|
* Applied some DRY and extracted out the keys_to_typing()
|
||||||
|
* Fix deselecting options in <select>
|
||||||
|
|
||||||
|
|
||||||
|
Selenium 2.52.0
|
||||||
|
* Fixing case where UnexpectedAlertException doesn't get the alert_text in the error object
|
||||||
|
* Firefox: Actually use launch_browser timeout Fixes #1300
|
||||||
|
|
||||||
|
Selenium 2.51.1
|
||||||
|
* correcting bundling issue missing README.rst file
|
||||||
|
|
||||||
|
Selenium 2.51.0
|
||||||
|
* Firefox updates (see java changelog)
|
||||||
|
|
||||||
|
Selenium 2.50.1
|
||||||
|
* Fixing error message handling. Fixes issue #1497
|
||||||
|
* Fixing error message handling. Fixes issue #1507
|
||||||
|
* Update webelement to handle W3C commands for size/location and rect
|
||||||
|
* rewrite click scrolling tests to match the Java ones
|
||||||
|
|
||||||
|
Selenium 2.50.0
|
||||||
|
* handle potential URLError from sending shutdown, set self.process to None after it's already been quit
|
||||||
|
* Add support for submit() with W3C compliant endpoint
|
||||||
|
|
||||||
|
Selenium 2.49.1
|
||||||
|
* Ensure you can close stream before attempting to close it.
|
||||||
|
* message response may cause json loads ValueError when it's not actually json
|
||||||
|
and just a string (like the message that occurs when firefox driver thinks
|
||||||
|
another element will receive the click)
|
||||||
|
* Cleanup some error handling when sniffing what protocol you are speaking
|
||||||
|
|
||||||
|
Selenium 2.49.0
|
||||||
|
* Have Firefox service write to a file instead of PIPE
|
||||||
|
* on osx for firefox, fallback to checking homebrew install, if the default isn't there
|
||||||
|
* Added Firefox path variable for string placeholder
|
||||||
|
* Update README to show Python 3.2+
|
||||||
|
* refactoring all the service classes to use a common one.
|
||||||
|
* Add Firefox specific command to switch context between Browser content and Browser chrome
|
||||||
|
* updating files after go copyright:update
|
||||||
|
* Use specificationLevel to know that we are speaking GeckoDriver
|
||||||
|
* Bug fixes: #1294, #1186
|
||||||
|
|
||||||
|
Selenium 2.48.0
|
||||||
|
* Update error pulling to match spec when we encounter a spec compliant browser.
|
||||||
|
* Disable tests that are not working with Marionette when running Marionette tests
|
||||||
|
* Add the ability to run python marionette tests
|
||||||
|
* Python 3 compatibility for remote Authorization
|
||||||
|
* changing casing of children finding tests
|
||||||
|
|
||||||
|
Selenium 2.47.3
|
||||||
|
* Bring back py 3 support
|
||||||
|
|
||||||
|
Selenium 2.47.2
|
||||||
|
* Fix running Edge driver locally on win10
|
||||||
|
* adding repr to WebDriver and WebElement
|
||||||
|
|
||||||
|
Selenium 2.47.1
|
||||||
|
* Fix the issue of deleting the profile when shutting down Firefox
|
||||||
|
* WebElement __eq__ compares against more types
|
||||||
|
* Issues fixed: 850
|
||||||
|
|
||||||
|
Selenium 2.47.0
|
||||||
|
* Add in support for when communicating with a Spec compliant browsers
|
||||||
|
* Initial support for Edge using EdgeDriver
|
||||||
|
* Issues fixed: 818
|
||||||
|
|
||||||
|
Selenium 2.46.1
|
||||||
|
* Adding ability to make remote call for webelement screenshots in accordance to the W3C spec
|
||||||
|
* Adding api to authenticate HTTP Auth modal dialogs via driver.switch_to.alert (beta)
|
||||||
|
* Add rebeccapurple to Color Object
|
||||||
|
* Add element screenshot
|
||||||
|
* Add service handler and minimal update to driver to use service for Marionette
|
||||||
|
* Add the ability to start FirefoxDriver backed with Marionette via a capability
|
||||||
|
* support socket timeout for connections
|
||||||
|
* free_port checks if port is available on all interfaces
|
||||||
|
* Allow error handling to handle both current errors and w3c errors
|
||||||
|
* Update find_elements to match spec
|
||||||
|
* phantomjs: service: remove unused import of signal
|
||||||
|
* phantomjs: add port information to WebDriverException
|
||||||
|
* Issues fixed (Github): 478, 612, 734, 780
|
||||||
|
|
||||||
|
Selenium 2.46.0
|
||||||
|
* Firefox support up to 38
|
||||||
|
* BlackBerry browser support
|
||||||
|
* remove Presto-Opera support
|
||||||
|
* firefox extension extraction fixes
|
||||||
|
* process management fixes with phantomjs
|
||||||
|
* Comparing remote web element for equality does not require a remote command
|
||||||
|
* Issues Fixed: (gcode) 8493, 8521, 8498, 8274, 8497, 5923
|
||||||
|
* Issues Fixed: (github) 401
|
||||||
|
|
||||||
|
Selenium 2.45.0
|
||||||
|
* Firefox support up to 35, support for native events up to 34.
|
||||||
|
* Make Opera driver support also the new Blink based Opera
|
||||||
|
* README: Fix the Yahoo example
|
||||||
|
* WebElement docstring fixes
|
||||||
|
* Add debugger_address option to the ChromeDriver options list to optionally instruct ChromeDriver to wait for the target devtools instance to be started at a given host:ip
|
||||||
|
* Set default value for PhantomJS process reference
|
||||||
|
* Allow setting of FileDetector for send_keys
|
||||||
|
* Pass info to TimeoutException in WebDriverWait
|
||||||
|
* Issues Fixed: 8065, 8310, 8539
|
||||||
|
|
||||||
|
Selenium 2.44.0
|
||||||
|
* (previous release person forgot to add release notes! DAVID!)
|
||||||
|
|
||||||
|
Selenium 2.43.0
|
||||||
|
* Expand WebElement.get_attribute API docs
|
||||||
|
* firefox may be installed without admininstrator privileges
|
||||||
|
and therefore there may be no HKEY_LOCAL_MACHINE entry. Issue #7784
|
||||||
|
* UnexpectedAlertPresentException should contain the alert text in python too. Issue #7745
|
||||||
|
* don't mutate the global 'ignored exceptions', take a copy of the globally specified ones, change the
|
||||||
|
global to be a tuple instead. Issue #7725
|
||||||
|
* raise exception when the firefox binary isn't actually found, which usually implies the upgrade failed (on windows) Issue #6092 ,#6847
|
||||||
|
* Fixing NameError: global name 'options' is not defined.
|
||||||
|
* Remove unused import subprocess.PIPE
|
||||||
|
* Redirect Firefox output to /dev/null by default Fixes Issue #7677
|
||||||
|
* More flexible management of http timeouts in Selenium RC python client
|
||||||
|
* Generate Python API docs for selenium.webdriver.chrome.options. Fixes issue #7471
|
||||||
|
* Use 127.0.0.1 as localhost name resolving might fail on some systems
|
||||||
|
|
||||||
|
Selenium 2.42.1
|
||||||
|
* Fixed Py3 issues
|
||||||
|
* Make firefox_binary.py and firefox_profile.py not executable
|
||||||
|
* Make exceptions Python 3 compatible
|
||||||
|
|
||||||
|
Selenium 2.42
|
||||||
|
* Support for Firefox 29 Native Events
|
||||||
|
* "remote_url" and "remote_browser" parameters for "./go test_remote".
|
||||||
|
* missing __init__ in new android module
|
||||||
|
* issue #7304 Fix memory leak caused by __del__ in PhantomJS
|
||||||
|
* File upload using remotedriver on python3
|
||||||
|
* Updating xpi install to align with mozprofile
|
||||||
|
* command_executor should also support unicode strings as well.
|
||||||
|
|
||||||
|
Selenium 2.41
|
||||||
|
* Support for Firefox 28
|
||||||
|
* deprecating switch_to_* in favour of driver.switch_to.*
|
||||||
|
|
||||||
|
Selenium 2.40
|
||||||
|
* Support for Firefox 27
|
||||||
|
* Fixes related to http connection
|
||||||
|
* Fix for phantomjs running on windows #6736
|
||||||
|
|
||||||
|
Selenium 2.39
|
||||||
|
* Support for Firefox 26
|
||||||
|
|
||||||
|
Selenium 2.38.4
|
||||||
|
* keep-alive can't be used for phantomjs / IE, fix for that and tested for py3 :)
|
||||||
|
|
||||||
|
Selenium 2.38.3
|
||||||
|
* really supporting py3 :)
|
||||||
|
|
||||||
|
Selenium 2.38.2
|
||||||
|
* py3 support (once again)
|
||||||
|
|
||||||
|
Selenium 2.38.1
|
||||||
|
* fix packaging problem where firefox/webdriver_prefs.json was missing
|
||||||
|
|
||||||
|
Selenium 2.38
|
||||||
|
* Support for Firefox 25
|
||||||
|
* FirefoxProfile now using common webdriver.json instead of having our own copy in py
|
||||||
|
- behavior change to the preferences is that they now should be treated
|
||||||
|
like raw types rather than strings and allow the json library to translate
|
||||||
|
the types appropriated (e.g. True => true)
|
||||||
|
|
||||||
|
* Set proper 'Accept' request header so that Python bindings work with some old WebDriver implementations that reply 404 to requests with no 'Accept' set.
|
||||||
|
* handle redirect response explicitly (since switching to using keep-alive)
|
||||||
|
* phantomjs service needs to really kill the spawned process Issue #5921
|
||||||
|
* removing old api endpoints from command listing
|
||||||
|
* using keep-alive for remote connection
|
||||||
|
* adjusting phantomjs subprocess.Popen
|
||||||
|
* ActionsChains.send_keys should use <session>/keys endpoint Issue #6348
|
||||||
|
* fix TypeError in chrome_options.extensions for Python3.x
|
||||||
|
|
||||||
|
* Other Bugs Fixed: #6531, #6513, #4569, #6454
|
||||||
|
|
||||||
|
|
||||||
|
Selenium 2.37.2
|
||||||
|
* fix regression added with unicode fix
|
||||||
|
* Bug fix #6360
|
||||||
|
|
||||||
|
Selenium 2.37.1
|
||||||
|
* fix find_elements on webelement using unicode locators and py 2.7
|
||||||
|
|
||||||
|
Selenium 2.37
|
||||||
|
* repackage with fix for Firefox native events on Linux
|
||||||
|
* fix issue with unicode By locators w/ python 2.7 #6430
|
||||||
|
|
||||||
|
Selenium 2.36
|
||||||
|
* Added Safari WebDriver. Fixes issue 5352.
|
||||||
|
* fix platform for safari caps
|
||||||
|
* Convert all offsets/coordinates/speeds into integers
|
||||||
|
* Fix drag and drop by offset behaviour
|
||||||
|
* Fix initialization of Proxy by capabilities when proxyType is set
|
||||||
|
* Enable SOCKS proxy support
|
||||||
|
* Validation of passed locator for find_element(s) methods #5690
|
||||||
|
* Adding support for /status, /sessions commands
|
||||||
|
* Doc fixes
|
||||||
|
* ability to set Chrome extensions by using base64 encoded strings #4013
|
||||||
|
* fix logic regarding Select.select_by_visible_text #3910
|
||||||
|
* Bugs fixed: #6165, #6231
|
||||||
|
|
||||||
|
Selenium 2.35
|
||||||
|
* Remove duplicate 'get screenshot as file' methods. Add method 'get_screenshot_as_png'
|
||||||
|
* fixing UnicodeEncodeError on get attribute of webelement
|
||||||
|
|
||||||
|
Selenium 2.34
|
||||||
|
* Corrected webdriverbackedselenium session handling. Fixes issue 4283
|
||||||
|
* Corrected use of basestring for python 3. Fixes issue 5924
|
||||||
|
* Support for Firefox 22
|
||||||
|
* Added support for logging from the browser
|
||||||
|
* corrected proxy handling on FirefoxProfile
|
||||||
|
* Corrected handling of chrome extensions. Fixes issue 5762
|
||||||
|
|
||||||
|
Selenium 2.33
|
||||||
|
* getText() ignores elements in the <head>
|
||||||
|
* Adding both official and informal string representations to Color object.
|
||||||
|
* Replace distutils.dir_util by shutil
|
||||||
|
* Allow finding firefox binary at ProgramFiles(x86) on windows(64 bit)
|
||||||
|
* Py3 compatible winreg import and content-type access
|
||||||
|
|
||||||
|
Selenium 2.32
|
||||||
|
* Support for FF20 Native Events
|
||||||
|
* Python 3 support
|
||||||
|
* Misc Python 3 patches
|
||||||
|
* Allow easy FirefoxBinary subclassing
|
||||||
|
|
||||||
|
Selenium 2.31
|
||||||
|
* Support for FF19 native events
|
||||||
|
* web element equality is now in conformance with other language bindings
|
||||||
|
|
||||||
|
Selenium 2.30
|
||||||
|
* Allow env to be specified for the chromedriver service
|
||||||
|
* Allow log path to be specified for phantomjs driver service.
|
||||||
|
* Bug Fixes: 4608 4940 4974 5034 5075
|
||||||
|
|
||||||
|
Selenium 2.29
|
||||||
|
* Allow subclassing of driver and have the ability to send_keys Issue 4877, 5017
|
||||||
|
* Simplifying save_screenshot and allow phantomjs to take screenshots
|
||||||
|
|
||||||
|
Selenium 2.28
|
||||||
|
* "null" can now be passed to executeScript
|
||||||
|
* Add transparent and extended colour keywords to color support module. Fixes issue 4866
|
||||||
|
|
||||||
|
Selenium 2.27
|
||||||
|
* Added support for phantomjs / ghostdriver
|
||||||
|
* Fix python client, avoid duplicate chrome option items after reusing options class. Fixes Issue 4744.
|
||||||
|
* adding colour support to Python. fixes issue 4623
|
||||||
|
* Adding log_path/service_log_path as named kwargs for chrome
|
||||||
|
|
||||||
|
Selenium 2.26
|
||||||
|
* Added location_when_scrolled_into_view - Bug 4357
|
||||||
|
* Added new expected_conditions support module to be used with WebDriverWait
|
||||||
|
|
||||||
|
Selenium 2.25
|
||||||
|
* Jython 2.7 Support - Bug 3988
|
||||||
|
* EventFiringWebDriver added to Support module - Bug 2267
|
||||||
|
* Added IEDriverServer logging that can be accessed via desired capabilities
|
||||||
|
* Fixed by data being passed into find_elements - bug 3735
|
||||||
|
* Removed deprecated ChromeDriver items around desiredcapabilites in favour of chrome options
|
||||||
|
* Added default values for a number of action_chains calls
|
||||||
|
|
||||||
|
Selenium 2.24
|
||||||
|
* Removing the ctypes approach of invoking IEDriver, you will need to download the IEDriverServer from
|
||||||
|
https://code.google.com/p/selenium/downloads/list
|
||||||
|
|
||||||
|
Selenium 2.23
|
||||||
|
* Support for FF13 native events
|
||||||
|
|
||||||
|
Selenium 2.22
|
||||||
|
* Moving IEDriver to be able to use IEDriverServer
|
||||||
|
|
||||||
|
Selenium 2.21.3
|
||||||
|
* Fix for File Upload to remote servers
|
||||||
|
* Better handling of typing in input=file. Bug 3831, 3736
|
||||||
|
* Better handling of unicode URLS Bug 3740
|
||||||
|
|
||||||
|
Selenium 2.21.2
|
||||||
|
* Fix typing to file input when not using Selenium Server. Bug 3736
|
||||||
|
|
||||||
|
Selenium 2.21.1
|
||||||
|
* focusmanager.testmode messes with native events, removing it.
|
||||||
|
|
||||||
|
Selenium 2.21
|
||||||
|
* Local File upload capabilities for non-remote browser
|
||||||
|
* Adding maximize_window api call
|
||||||
|
* Updating default firefox profile to set focusmanager.testmode to true
|
||||||
|
see https://bugzilla.mozilla.org/show_bug.cgi?id=704583
|
||||||
|
* bugs fixed: 3506, 3528, 3607
|
||||||
|
|
||||||
|
Selenium 2.20
|
||||||
|
* disable native events for FF on Mac by default
|
||||||
|
* fix webdriverwait to execute at least once when using 0 timeout
|
||||||
|
* Fixed Issue 3438
|
||||||
|
|
||||||
|
Selenium 2.19
|
||||||
|
* WebDriverBackedSelenium is now avalaible to all languages
|
||||||
|
* Addon installation fixes
|
||||||
|
|
||||||
|
Selenium 2.18
|
||||||
|
* Proxy capabilities passing
|
||||||
|
|
||||||
|
Selenium 2.17
|
||||||
|
* OperaDriver can now be invoked by webdriver.Opera()
|
||||||
|
* Support has been added for ChomeOptions. This deprecates support passing in DesiredCapabilities
|
||||||
|
* Proxy class to tell the browser a proxy is in use. Currently only for Firefox
|
||||||
|
|
||||||
|
Selenium 2.16
|
||||||
|
* bug fixes
|
||||||
|
|
||||||
|
Selenium 2.15
|
||||||
|
* bug fixes
|
||||||
|
|
||||||
|
Selenium 2.14
|
||||||
|
* Fix for LD_PRELOAD being polluted by WebDriver
|
||||||
|
* Added Orientation API
|
||||||
|
* A fix for Error Handling
|
||||||
|
|
||||||
|
Selenium 2.13
|
||||||
|
* Fixed switch_to_window so that it didnt crash Firefox Bug 2633
|
||||||
|
* Fixed Screenshot handling to work in all browsers. Bug 2829
|
||||||
|
* Force Firefox to the Foreground
|
||||||
|
|
||||||
|
Selenium 2.12
|
||||||
|
* Added Select as a support pacakge
|
||||||
|
* Added Beta window size / position api's
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2.11.0 2.11.1
|
||||||
|
* no changes just packaging
|
||||||
|
|
||||||
|
Selenum 2.10
|
||||||
|
* "Choose which apps" dialog has been disabled
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2.9
|
||||||
|
* Bug Fixes
|
||||||
|
* Documentation
|
||||||
|
|
||||||
|
Selenium 2.8
|
||||||
|
* Actions updates
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2.6
|
||||||
|
* Documentation fixes
|
||||||
|
|
||||||
|
Selenium 2.5
|
||||||
|
* Fixed x64 IE Support
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2.4
|
||||||
|
* Bug Fixes
|
||||||
|
* x64 IE Support
|
||||||
|
* Added WebDriverWait as a support package
|
||||||
|
|
||||||
|
Selenium 2.3
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2.2
|
||||||
|
* Ability to get screenshots from Exceptions if they are given
|
||||||
|
* Access to Remote StackTrace on error
|
||||||
|
|
||||||
|
Selenium 2.1
|
||||||
|
* Bug Fixes
|
||||||
|
|
||||||
|
Selenium 2
|
||||||
|
* Removed toggle() and select()
|
||||||
|
|
||||||
|
Selenium 2 RC 3
|
||||||
|
* Added Opera to Desired Capabilities
|
||||||
|
* Removed deprecrated methods
|
||||||
|
* Deprecated toggle() and select() methods. This will be removed in the next release
|
||||||
|
|
||||||
|
Selenium 2 Beta 4
|
||||||
|
* Fix for using existing Firefox Profiles
|
||||||
|
* Alerts Support in IE
|
||||||
|
* Fix to dictionary returned from size
|
||||||
|
* Deprecated value property. Use the get_attribute("value") method
|
||||||
|
* Deprecated get_page_source method. Use page_source property
|
||||||
|
* Deprecated get_current_window_handle. Use current_window_handle property
|
||||||
|
* Deprecated get_window_handles. Use window_handles property
|
||||||
|
* Ability to install extensions into profiles
|
||||||
|
* Added Location to the WebElement
|
||||||
|
* ChromeDriver rewritten to use new built in mechanism
|
||||||
|
* Added Advanced User Interaction API. Only Available for HTMLUnit at the moment
|
||||||
|
* Profiles now delete their temp folders when driver.quit() is called
|
||||||
|
|
||||||
|
Selenium 2 Beta 3
|
||||||
|
* Accept Untrusted Certificates in Firefox
|
||||||
|
* Fixed Screenshots
|
||||||
|
* Added DesiredCapabilities to simplify choosing Drivers
|
||||||
|
* Fixed Firefox Profile creation
|
||||||
|
* Added Firefox 4 support
|
||||||
|
* DocStrings Improvements
|
||||||
|
|
||||||
|
Selenium 2 Beta 2
|
||||||
|
|
||||||
|
* New bindings landed. Change webdriver namespace to "selenium.webdriver"
|
||||||
|
* Ability to move to default content
|
||||||
|
* Implicit Waits
|
||||||
|
* Change the API to use properties instead of get_x
|
||||||
|
* Changed the Element Finding to match other languages
|
||||||
|
* Added ability to execute asynchronous scripts from the driver
|
||||||
|
* Ability to get rendered element size
|
||||||
|
* Ability to get CSS Value on a webelement
|
||||||
|
* Corrected Element finding from the element
|
||||||
|
* Alert and Prompt handling
|
||||||
|
* Improved IEDriver
|
||||||
|
* Basic Authentication support for Selenium 2
|
||||||
|
* Ability to have multiple Firefox instances
|
20
MANIFEST.in
Normal file
20
MANIFEST.in
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
prune *
|
||||||
|
recursive-include selenium/webdriver *.py
|
||||||
|
recursive-include selenium/webdriver/common *.py
|
||||||
|
recursive-include selenium/webdriver/common/actions *.py
|
||||||
|
recursive-include selenium/webdriver/common/html5 *.py
|
||||||
|
recursive-include selenium/common *.py
|
||||||
|
recursive-include selenium/webdriver/chrome *.py
|
||||||
|
recursive-include selenium/webdriver/opera *.py
|
||||||
|
recursive-include selenium/webdriver/phantomjs *.py
|
||||||
|
recursive-include selenium/webdriver/firefox *.py *.xpi *.json
|
||||||
|
recursive-include selenium/webdriver/ie *.py
|
||||||
|
recursive-include selenium/webdriver/edge *.py
|
||||||
|
recursive-include selenium/webdriver/remote *.py *.js
|
||||||
|
recursive-include selenium/webdriver/support *.py
|
||||||
|
include selenium/selenium.py
|
||||||
|
include selenium/__init__.py
|
||||||
|
include CHANGES
|
||||||
|
include README.rst
|
||||||
|
include LICENSE
|
||||||
|
recursive-include selenium.egg-info *
|
1
README.rst
Symbolic link
1
README.rst
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
docs/source/index.rst
|
85
build.desc
Normal file
85
build.desc
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# py_test targets emplicitly get the following extra targets added:
|
||||||
|
# for each browser in browsers:
|
||||||
|
# :name_B to gather the sources of the tests for browser B
|
||||||
|
# :name_B:run to run the tests for browser B
|
||||||
|
# Also, if only one browser, B, is listed in browsers:
|
||||||
|
# :name:run is created as a synonym for :name_B:run
|
||||||
|
#
|
||||||
|
# Currently, pulling in other tests through deps
|
||||||
|
# is limited to deps declared in the same build.desc file
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "firefox_test",
|
||||||
|
deps = [ ":test_ff" ],
|
||||||
|
resources = [
|
||||||
|
{ "//third_party/js/selenium:webdriver_prefs" : "selenium/webdriver/firefox/webdriver_prefs.json" },
|
||||||
|
{ "//third_party/js/selenium:webdriver" : "selenium/webdriver/firefox/" }
|
||||||
|
],
|
||||||
|
browsers = [ "ff" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "marionette_test",
|
||||||
|
deps = [ ":test_marionette" ],
|
||||||
|
browsers = [ "marionette" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "blackberry_test",
|
||||||
|
deps = [ ":test_blackberry" ],
|
||||||
|
browsers = [ "blackberry" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "chrome_test",
|
||||||
|
deps = [ ":test_chrome" ],
|
||||||
|
browsers = [ "chrome" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "ie_test",
|
||||||
|
deps = [ ":test_ie" ],
|
||||||
|
browsers = [ "ie" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "edge_test",
|
||||||
|
deps = [":test_edge"],
|
||||||
|
browsers = [ "edge"])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "remote_firefox_test",
|
||||||
|
deps = [ ":test_remote_firefox" ],
|
||||||
|
browsers = [ "remote_firefox" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "safari_test",
|
||||||
|
deps = [ ":test_safari" ],
|
||||||
|
browsers = [ "safari" ])
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "test",
|
||||||
|
browsers = [
|
||||||
|
"chrome",
|
||||||
|
"ff",
|
||||||
|
"marionette",
|
||||||
|
"ie",
|
||||||
|
"edge",
|
||||||
|
"blackberry",
|
||||||
|
"remote_firefox",
|
||||||
|
"safari",
|
||||||
|
])
|
||||||
|
|
||||||
|
py_docs(
|
||||||
|
name = "docs"
|
||||||
|
)
|
||||||
|
|
||||||
|
py_install(
|
||||||
|
name = "install"
|
||||||
|
)
|
||||||
|
|
||||||
|
py_prep(
|
||||||
|
name = "prep",
|
||||||
|
deps = [
|
||||||
|
"//cpp:noblur",
|
||||||
|
"//cpp:noblur64",
|
||||||
|
"//javascript/atoms/fragments:is-displayed",
|
||||||
|
"//third_party/js/selenium:webdriver",
|
||||||
|
"//third_party/js/selenium:webdriver_prefs",
|
||||||
|
"//javascript/webdriver/atoms:get-attribute"
|
||||||
|
])
|
219
conftest.py
Normal file
219
conftest.py
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from _pytest.skipping import MarkEvaluator
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver import DesiredCapabilities
|
||||||
|
from test.selenium.webdriver.common.webserver import SimpleWebServer
|
||||||
|
from test.selenium.webdriver.common.network import get_lan_ip
|
||||||
|
|
||||||
|
if sys.version_info[0] == 3:
|
||||||
|
from urllib.request import urlopen
|
||||||
|
else:
|
||||||
|
from urllib import urlopen
|
||||||
|
|
||||||
|
drivers = (
|
||||||
|
'BlackBerry',
|
||||||
|
'Chrome',
|
||||||
|
'Edge',
|
||||||
|
'Firefox',
|
||||||
|
'Ie',
|
||||||
|
'Marionette',
|
||||||
|
'Remote',
|
||||||
|
'Safari',
|
||||||
|
'WebKitGTK',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption('--driver', action='append', choices=drivers, dest='drivers',
|
||||||
|
metavar='DRIVER',
|
||||||
|
help='driver to run tests against ({})'.format(', '.join(drivers)))
|
||||||
|
parser.addoption('--browser-binary', action='store', dest='binary',
|
||||||
|
help='location of the browser binary')
|
||||||
|
parser.addoption('--driver-binary', action='store', dest='executable',
|
||||||
|
help='location of the service executable binary')
|
||||||
|
parser.addoption('--browser-args', action='store', dest='args',
|
||||||
|
help='arguments to start the browser with')
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_ignore_collect(path, config):
|
||||||
|
drivers_opt = config.getoption('drivers')
|
||||||
|
_drivers = set(drivers).difference(drivers_opt or drivers)
|
||||||
|
if drivers_opt:
|
||||||
|
_drivers.add('unit')
|
||||||
|
parts = path.dirname.split(os.path.sep)
|
||||||
|
return len([d for d in _drivers if d.lower() in parts]) > 0
|
||||||
|
|
||||||
|
|
||||||
|
driver_instance = None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def driver(request):
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
driver_class = request.param
|
||||||
|
except AttributeError:
|
||||||
|
raise Exception('This test requires a --driver to be specified.')
|
||||||
|
|
||||||
|
# conditionally mark tests as expected to fail based on driver
|
||||||
|
request.node._evalxfail = request.node._evalxfail or MarkEvaluator(
|
||||||
|
request.node, 'xfail_{0}'.format(driver_class.lower()))
|
||||||
|
if request.node._evalxfail.istrue():
|
||||||
|
def fin():
|
||||||
|
global driver_instance
|
||||||
|
if driver_instance is not None:
|
||||||
|
driver_instance.quit()
|
||||||
|
driver_instance = None
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
|
||||||
|
# skip driver instantiation if xfail(run=False)
|
||||||
|
if not request.config.getoption('runxfail'):
|
||||||
|
if request.node._evalxfail.istrue():
|
||||||
|
if request.node._evalxfail.get('run') is False:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
driver_path = request.config.option.executable
|
||||||
|
options = None
|
||||||
|
|
||||||
|
global driver_instance
|
||||||
|
if driver_instance is None:
|
||||||
|
if driver_class == 'BlackBerry':
|
||||||
|
kwargs.update({'device_password': 'password'})
|
||||||
|
if driver_class == 'Firefox':
|
||||||
|
kwargs.update({'capabilities': {'marionette': False}})
|
||||||
|
options = get_options(driver_class, request.config)
|
||||||
|
if driver_class == 'Marionette':
|
||||||
|
driver_class = 'Firefox'
|
||||||
|
options = get_options(driver_class, request.config)
|
||||||
|
if driver_class == 'Remote':
|
||||||
|
capabilities = DesiredCapabilities.FIREFOX.copy()
|
||||||
|
kwargs.update({'desired_capabilities': capabilities})
|
||||||
|
options = get_options('Firefox', request.config)
|
||||||
|
if driver_class == 'WebKitGTK':
|
||||||
|
options = get_options(driver_class, request.config)
|
||||||
|
if driver_path is not None:
|
||||||
|
kwargs['executable_path'] = driver_path
|
||||||
|
if options is not None:
|
||||||
|
kwargs['options'] = options
|
||||||
|
driver_instance = getattr(webdriver, driver_class)(**kwargs)
|
||||||
|
yield driver_instance
|
||||||
|
if MarkEvaluator(request.node, 'no_driver_after_test').istrue():
|
||||||
|
driver_instance = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_options(driver_class, config):
|
||||||
|
browser_path = config.option.binary
|
||||||
|
browser_args = config.option.args
|
||||||
|
options = None
|
||||||
|
if browser_path or browser_args:
|
||||||
|
options = getattr(webdriver, '{}Options'.format(driver_class))()
|
||||||
|
if driver_class == 'WebKitGTK':
|
||||||
|
options.overlay_scrollbars_enabled = False
|
||||||
|
if browser_path is not None:
|
||||||
|
options.binary_location = browser_path
|
||||||
|
if browser_args is not None:
|
||||||
|
for arg in browser_args.split():
|
||||||
|
options.add_argument(arg)
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session', autouse=True)
|
||||||
|
def stop_driver(request):
|
||||||
|
def fin():
|
||||||
|
global driver_instance
|
||||||
|
if driver_instance is not None:
|
||||||
|
driver_instance.quit()
|
||||||
|
driver_instance = None
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_exception_interact(node, call, report):
|
||||||
|
if report.failed:
|
||||||
|
global driver_instance
|
||||||
|
if driver_instance is not None:
|
||||||
|
driver_instance.quit()
|
||||||
|
driver_instance = None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pages(driver, webserver):
|
||||||
|
class Pages(object):
|
||||||
|
def url(self, name):
|
||||||
|
return webserver.where_is(name)
|
||||||
|
|
||||||
|
def load(self, name):
|
||||||
|
driver.get(self.url(name))
|
||||||
|
return Pages()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope='session')
|
||||||
|
def server(request):
|
||||||
|
drivers = request.config.getoption('drivers')
|
||||||
|
if drivers is None or 'Remote' not in drivers:
|
||||||
|
yield None
|
||||||
|
return
|
||||||
|
|
||||||
|
_host = 'localhost'
|
||||||
|
_port = 4444
|
||||||
|
_path = '../buck-out/gen/java/server/src/org/openqa/grid/selenium/selenium.jar'
|
||||||
|
|
||||||
|
def wait_for_server(url, timeout):
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < timeout:
|
||||||
|
try:
|
||||||
|
urlopen(url)
|
||||||
|
return 1
|
||||||
|
except IOError:
|
||||||
|
time.sleep(0.2)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
url = 'http://{}:{}/wd/hub'.format(_host, _port)
|
||||||
|
try:
|
||||||
|
_socket.connect((_host, _port))
|
||||||
|
print('The remote driver server is already running or something else'
|
||||||
|
'is using port {}, continuing...'.format(_port))
|
||||||
|
except Exception:
|
||||||
|
print('Starting the Selenium server')
|
||||||
|
process = subprocess.Popen(['java', '-jar', _path])
|
||||||
|
print('Selenium server running as process: {}'.format(process.pid))
|
||||||
|
assert wait_for_server(url, 10), 'Timed out waiting for Selenium server at {}'.format(url)
|
||||||
|
print('Selenium server is ready')
|
||||||
|
yield process
|
||||||
|
process.terminate()
|
||||||
|
process.wait()
|
||||||
|
print('Selenium server has been terminated')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope='session')
|
||||||
|
def webserver():
|
||||||
|
webserver = SimpleWebServer(host=get_lan_ip())
|
||||||
|
webserver.start()
|
||||||
|
yield webserver
|
||||||
|
webserver.stop()
|
131
docs/Makefile
Normal file
131
docs/Makefile
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
HTML_DESTINATION = ../../docs/api/py
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html: clean
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(HTML_DESTINATION)
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(HTML_DESTINATION)."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Selenium.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Selenium.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/Selenium"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Selenium"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
make -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
149
docs/source/api.rst
Normal file
149
docs/source/api.rst
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
:orphan:
|
||||||
|
|
||||||
|
======================
|
||||||
|
Selenium Documentation
|
||||||
|
======================
|
||||||
|
|
||||||
|
Common
|
||||||
|
------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.common
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: common
|
||||||
|
|
||||||
|
selenium.common.exceptions
|
||||||
|
|
||||||
|
Webdriver.common
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.common
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver
|
||||||
|
|
||||||
|
selenium.webdriver.common.action_chains
|
||||||
|
selenium.webdriver.common.alert
|
||||||
|
selenium.webdriver.common.by
|
||||||
|
selenium.webdriver.common.desired_capabilities
|
||||||
|
selenium.webdriver.common.keys
|
||||||
|
selenium.webdriver.common.touch_actions
|
||||||
|
selenium.webdriver.common.utils
|
||||||
|
selenium.webdriver.common.proxy
|
||||||
|
selenium.webdriver.common.service
|
||||||
|
selenium.webdriver.common.html5.application_cache
|
||||||
|
|
||||||
|
Webdriver.support
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.support
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_support
|
||||||
|
|
||||||
|
selenium.webdriver.support.abstract_event_listener
|
||||||
|
selenium.webdriver.support.color
|
||||||
|
selenium.webdriver.support.event_firing_webdriver
|
||||||
|
selenium.webdriver.support.expected_conditions
|
||||||
|
selenium.webdriver.support.select
|
||||||
|
selenium.webdriver.support.wait
|
||||||
|
|
||||||
|
Webdriver.android
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.android
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_android
|
||||||
|
|
||||||
|
selenium.webdriver.android.webdriver
|
||||||
|
|
||||||
|
Webdriver.chrome
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.chrome
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_chrome
|
||||||
|
|
||||||
|
selenium.webdriver.chrome.options
|
||||||
|
selenium.webdriver.chrome.service
|
||||||
|
selenium.webdriver.chrome.webdriver
|
||||||
|
|
||||||
|
Webdriver.firefox
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.firefox
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_firefox
|
||||||
|
|
||||||
|
selenium.webdriver.firefox.extension_connection
|
||||||
|
selenium.webdriver.firefox.firefox_binary
|
||||||
|
selenium.webdriver.firefox.options
|
||||||
|
selenium.webdriver.firefox.firefox_profile
|
||||||
|
selenium.webdriver.firefox.webdriver
|
||||||
|
|
||||||
|
Webdriver.ie
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.ie
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_ie
|
||||||
|
|
||||||
|
selenium.webdriver.ie.webdriver
|
||||||
|
|
||||||
|
Webdriver.opera
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.opera
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_opera
|
||||||
|
|
||||||
|
selenium.webdriver.opera.webdriver
|
||||||
|
|
||||||
|
Webdriver.phantomjs
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.phantomjs
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_phantomjs
|
||||||
|
|
||||||
|
selenium.webdriver.phantomjs.service
|
||||||
|
selenium.webdriver.phantomjs.webdriver
|
||||||
|
|
||||||
|
Webdriver.remote
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.remote
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_remote
|
||||||
|
|
||||||
|
selenium.webdriver.remote.command
|
||||||
|
selenium.webdriver.remote.errorhandler
|
||||||
|
selenium.webdriver.remote.mobile
|
||||||
|
selenium.webdriver.remote.remote_connection
|
||||||
|
selenium.webdriver.remote.utils
|
||||||
|
selenium.webdriver.remote.webdriver
|
||||||
|
selenium.webdriver.remote.webelement
|
||||||
|
|
||||||
|
Webdriver.safari
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.safari
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_safari
|
||||||
|
|
||||||
|
selenium.webdriver.safari.service
|
||||||
|
selenium.webdriver.safari.webdriver
|
||||||
|
|
||||||
|
Webdriver.webkitgtk
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. currentmodule:: selenium.webdriver.webkitgtk
|
||||||
|
.. autosummary::
|
||||||
|
:toctree: webdriver_webkitgtk
|
||||||
|
|
||||||
|
selenium.webdriver.webkitgtk.options
|
||||||
|
selenium.webdriver.webkitgtk.service
|
||||||
|
selenium.webdriver.webkitgtk.webdriver
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
4
docs/source/common/selenium.common.exceptions.rst
Normal file
4
docs/source/common/selenium.common.exceptions.rst
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.common.exceptions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: selenium.common.exceptions
|
274
docs/source/conf.py
Normal file
274
docs/source/conf.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import sys, os, os.path
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
sys.path.insert(0, os.path.join(os.getcwd(), "..", ".."))
|
||||||
|
|
||||||
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = 'Selenium'
|
||||||
|
copyright = '2011, plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = '3.141'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = version
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = []
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
html_show_sphinx = False
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'Seleniumdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'Selenium.tex', 'Selenium Documentation',
|
||||||
|
'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output --------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'selenium', 'Selenium Documentation',
|
||||||
|
['plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Epub output ---------------------------------------------------
|
||||||
|
|
||||||
|
# Bibliographic Dublin Core info.
|
||||||
|
epub_title = 'Selenium'
|
||||||
|
epub_author = 'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
|
||||||
|
epub_publisher = 'plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
|
||||||
|
epub_copyright = '2011, plightbo, simon.m.stewart, hbchai, jrhuggins, et al.'
|
||||||
|
|
||||||
|
# The language of the text. It defaults to the language option
|
||||||
|
# or en if the language is not set.
|
||||||
|
#epub_language = ''
|
||||||
|
|
||||||
|
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||||
|
#epub_scheme = ''
|
||||||
|
|
||||||
|
# The unique identifier of the text. This can be a ISBN number
|
||||||
|
# or the project homepage.
|
||||||
|
#epub_identifier = ''
|
||||||
|
|
||||||
|
# A unique identification for the text.
|
||||||
|
#epub_uid = ''
|
||||||
|
|
||||||
|
# HTML files that should be inserted before the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
#epub_pre_files = []
|
||||||
|
|
||||||
|
# HTML files shat should be inserted after the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
#epub_post_files = []
|
||||||
|
|
||||||
|
# A list of files that should not be packed into the epub file.
|
||||||
|
#epub_exclude_files = []
|
||||||
|
|
||||||
|
# The depth of the table of contents in toc.ncx.
|
||||||
|
#epub_tocdepth = 3
|
||||||
|
|
||||||
|
# Allow duplicate toc entries.
|
||||||
|
#epub_tocdup = True
|
||||||
|
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||||
|
|
||||||
|
# 'members' includes anything that has a docstring, 'undoc-members' includes
|
||||||
|
# functions without docstrings.
|
||||||
|
autodoc_default_flags = ['members', 'undoc-members']
|
||||||
|
|
||||||
|
# Include __init__ comments
|
||||||
|
autoclass_content = "both"
|
145
docs/source/index.rst
Normal file
145
docs/source/index.rst
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
======================
|
||||||
|
Selenium Client Driver
|
||||||
|
======================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
Python language bindings for Selenium WebDriver.
|
||||||
|
|
||||||
|
The `selenium` package is used to automate web browser interaction from Python.
|
||||||
|
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
| **Home**: | http://www.seleniumhq.org |
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
| **Docs**: | `selenium package API <https://seleniumhq.github.io/selenium/docs/api/py/api.html>`_ |
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
| **Dev**: | https://github.com/SeleniumHQ/Selenium |
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
| **PyPI**: | https://pypi.org/project/selenium/ |
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
| **IRC**: | **#selenium** channel on freenode |
|
||||||
|
+-----------+--------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
Several browsers/drivers are supported (Firefox, Chrome, Internet Explorer), as well as the Remote protocol.
|
||||||
|
|
||||||
|
Supported Python Versions
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* Python 2.7, 3.4+
|
||||||
|
|
||||||
|
Installing
|
||||||
|
==========
|
||||||
|
|
||||||
|
If you have `pip <https://pip.pypa.io/>`_ on your system, you can simply install or upgrade the Python bindings::
|
||||||
|
|
||||||
|
pip install -U selenium
|
||||||
|
|
||||||
|
Alternately, you can download the source distribution from `PyPI <https://pypi.org/project/selenium/#files>`_ (e.g. selenium-4.0.0a1.tar.gz), unarchive it, and run::
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
Note: You may want to consider using `virtualenv <http://www.virtualenv.org/>`_ to create isolated Python environments.
|
||||||
|
|
||||||
|
Drivers
|
||||||
|
=======
|
||||||
|
|
||||||
|
Selenium requires a driver to interface with the chosen browser. Firefox,
|
||||||
|
for example, requires `geckodriver <https://github.com/mozilla/geckodriver/releases>`_, which needs to be installed before the below examples can be run. Make sure it's in your `PATH`, e. g., place it in `/usr/bin` or `/usr/local/bin`.
|
||||||
|
|
||||||
|
Failure to observe this step will give you an error `selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.`
|
||||||
|
|
||||||
|
Other supported browsers will have their own drivers available. Links to some of the more popular browser drivers follow.
|
||||||
|
|
||||||
|
+--------------+-----------------------------------------------------------------------+
|
||||||
|
| **Chrome**: | https://sites.google.com/a/chromium.org/chromedriver/downloads |
|
||||||
|
+--------------+-----------------------------------------------------------------------+
|
||||||
|
| **Edge**: | https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ |
|
||||||
|
+--------------+-----------------------------------------------------------------------+
|
||||||
|
| **Firefox**: | https://github.com/mozilla/geckodriver/releases |
|
||||||
|
+--------------+-----------------------------------------------------------------------+
|
||||||
|
| **Safari**: | https://webkit.org/blog/6900/webdriver-support-in-safari-10/ |
|
||||||
|
+--------------+-----------------------------------------------------------------------+
|
||||||
|
|
||||||
|
Example 0:
|
||||||
|
==========
|
||||||
|
|
||||||
|
* open a new Firefox browser
|
||||||
|
* load the page at the given URL
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
|
||||||
|
browser = webdriver.Firefox()
|
||||||
|
browser.get('http://seleniumhq.org/')
|
||||||
|
|
||||||
|
Example 1:
|
||||||
|
==========
|
||||||
|
|
||||||
|
* open a new Firefox browser
|
||||||
|
* load the Yahoo homepage
|
||||||
|
* search for "seleniumhq"
|
||||||
|
* close the browser
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
browser = webdriver.Firefox()
|
||||||
|
|
||||||
|
browser.get('http://www.yahoo.com')
|
||||||
|
assert 'Yahoo' in browser.title
|
||||||
|
|
||||||
|
elem = browser.find_element_by_name('p') # Find the search box
|
||||||
|
elem.send_keys('seleniumhq' + Keys.RETURN)
|
||||||
|
|
||||||
|
browser.quit()
|
||||||
|
|
||||||
|
Example 2:
|
||||||
|
==========
|
||||||
|
|
||||||
|
Selenium WebDriver is often used as a basis for testing web applications. Here is a simple example using Python's standard `unittest <http://docs.python.org/3/library/unittest.html>`_ library:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from selenium import webdriver
|
||||||
|
|
||||||
|
class GoogleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.browser = webdriver.Firefox()
|
||||||
|
self.addCleanup(self.browser.quit)
|
||||||
|
|
||||||
|
def testPageTitle(self):
|
||||||
|
self.browser.get('http://www.google.com')
|
||||||
|
self.assertIn('Google', self.browser.title)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(verbosity=2)
|
||||||
|
|
||||||
|
Selenium Server (optional)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
For normal WebDriver scripts (non-Remote), the Java server is not needed.
|
||||||
|
|
||||||
|
However, to use Selenium Webdriver Remote or the legacy Selenium API (Selenium-RC), you need to also run the Selenium server. The server requires a Java Runtime Environment (JRE).
|
||||||
|
|
||||||
|
Download the server separately, from: http://selenium-release.storage.googleapis.com/4.0/selenium-server-standalone-4.0.0.jar
|
||||||
|
|
||||||
|
Run the server from the command line::
|
||||||
|
|
||||||
|
java -jar selenium-server-standalone-3.141.0.jar
|
||||||
|
|
||||||
|
Then run your Python client scripts.
|
||||||
|
|
||||||
|
Use The Source Luke!
|
||||||
|
====================
|
||||||
|
|
||||||
|
View source code online:
|
||||||
|
|
||||||
|
+-----------+-------------------------------------------------------+
|
||||||
|
| official: | https://github.com/SeleniumHQ/selenium/tree/master/py |
|
||||||
|
+-----------+-------------------------------------------------------+
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.action_chains
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.action_chains
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.alert
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.alert
|
4
docs/source/webdriver/selenium.webdriver.common.by.rst
Normal file
4
docs/source/webdriver/selenium.webdriver.common.by.rst
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.by
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.by
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.desired_capabilities
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.desired_capabilities
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.html5.application_cache
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.html5.application_cache
|
4
docs/source/webdriver/selenium.webdriver.common.keys.rst
Normal file
4
docs/source/webdriver/selenium.webdriver.common.keys.rst
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.keys
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.keys
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.proxy
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.proxy
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.service
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.service
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.touch_actions
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.touch_actions
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.common.utils
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.common.utils
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.android.webdriver
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.android.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.chrome.options
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.chrome.options
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.chrome.service
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.chrome.service
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.chrome.webdriver
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.chrome.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.firefox.extension_connection
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.firefox.extension_connection
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.firefox.firefox_binary
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.firefox.firefox_binary
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.firefox.firefox_profile
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.firefox.firefox_profile
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.firefox.options
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.firefox.options
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.firefox.webdriver
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.firefox.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.ie.webdriver
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.ie.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.opera.webdriver
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.opera.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.phantomjs.service
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.phantomjs.service
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.phantomjs.webdriver
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.phantomjs.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.command
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.command
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.errorhandler
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.errorhandler
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.mobile
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.mobile
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.remote_connection
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.remote_connection
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.utils
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.utils
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.webdriver
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.remote.webelement
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.remote.webelement
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.safari.service
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.safari.service
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.safari.webdriver
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.safari.webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.abstract_event_listener
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.abstract_event_listener
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.color
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.color
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.event_firing_webdriver
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.event_firing_webdriver
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.expected_conditions
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.expected_conditions
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.select
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.select
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.support.wait
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.support.wait
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.webkitgtk.options
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.webkitgtk.options
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.webkitgtk.service
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.webkitgtk.service
|
@@ -0,0 +1,4 @@
|
|||||||
|
selenium.webdriver.webkitgtk.webdriver
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: selenium.webdriver.webkitgtk.webdriver
|
24
python.iml
Normal file
24
python.iml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="Python" name="Python">
|
||||||
|
<configuration sdkName="" />
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/selenium" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 2.7" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="sonarModuleSettings">
|
||||||
|
<option name="alternativeWorkingDirPath" value="" />
|
||||||
|
<option name="localAnalysisScripName" value="<PROJECT>" />
|
||||||
|
<option name="serverName" value="<PROJECT>" />
|
||||||
|
<option name="useAlternativeWorkingDir" value="false" />
|
||||||
|
<option name="workingDirSelection" value="<MODULE>" />
|
||||||
|
</component>
|
||||||
|
</module>
|
19
selenium/__init__.py
Normal file
19
selenium/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = "4.0.0a1"
|
18
selenium/common/__init__.py
Normal file
18
selenium/common/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from . import exceptions # noqa
|
315
selenium/common/exceptions.py
Normal file
315
selenium/common/exceptions.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exceptions that may happen in all the webdriver code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class WebDriverException(Exception):
|
||||||
|
"""
|
||||||
|
Base webdriver exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg=None, screen=None, stacktrace=None):
|
||||||
|
self.msg = msg
|
||||||
|
self.screen = screen
|
||||||
|
self.stacktrace = stacktrace
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
exception_msg = "Message: %s\n" % self.msg
|
||||||
|
if self.screen is not None:
|
||||||
|
exception_msg += "Screenshot: available via screen\n"
|
||||||
|
if self.stacktrace is not None:
|
||||||
|
stacktrace = "\n".join(self.stacktrace)
|
||||||
|
exception_msg += "Stacktrace:\n%s" % stacktrace
|
||||||
|
return exception_msg
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSwitchToTargetException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when frame or window target to be switched doesn't exist.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchFrameException(InvalidSwitchToTargetException):
|
||||||
|
"""
|
||||||
|
Thrown when frame target to be switched doesn't exist.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchWindowException(InvalidSwitchToTargetException):
|
||||||
|
"""
|
||||||
|
Thrown when window target to be switched doesn't exist.
|
||||||
|
|
||||||
|
To find the current set of active window handles, you can get a list
|
||||||
|
of the active window handles in the following way::
|
||||||
|
|
||||||
|
print driver.window_handles
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchElementException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when element could not be found.
|
||||||
|
|
||||||
|
If you encounter this exception, you may want to check the following:
|
||||||
|
* Check your selector used in your find_by...
|
||||||
|
* Element may not yet be on the screen at the time of the find operation,
|
||||||
|
(webpage is still loading) see selenium.webdriver.support.wait.WebDriverWait()
|
||||||
|
for how to write a wait wrapper to wait for an element to appear.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchAttributeException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when the attribute of element could not be found.
|
||||||
|
|
||||||
|
You may want to check if the attribute exists in the particular browser you are
|
||||||
|
testing against. Some browsers may have different property names for the same
|
||||||
|
property. (IE8's .innerText vs. Firefox .textContent)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StaleElementReferenceException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when a reference to an element is now "stale".
|
||||||
|
|
||||||
|
Stale means the element no longer appears on the DOM of the page.
|
||||||
|
|
||||||
|
|
||||||
|
Possible causes of StaleElementReferenceException include, but not limited to:
|
||||||
|
* You are no longer on the same page, or the page may have refreshed since the element
|
||||||
|
was located.
|
||||||
|
* The element may have been removed and re-added to the screen, since it was located.
|
||||||
|
Such as an element being relocated.
|
||||||
|
This can happen typically with a javascript framework when values are updated and the
|
||||||
|
node is rebuilt.
|
||||||
|
* Element may have been inside an iframe or another context which was refreshed.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidElementStateException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when a command could not be completed because the element is in an invalid state.
|
||||||
|
|
||||||
|
This can be caused by attempting to clear an element that isn't both editable and resettable.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedAlertPresentException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when an unexpected alert has appeared.
|
||||||
|
|
||||||
|
Usually raised when an unexpected modal is blocking the webdriver from executing
|
||||||
|
commands.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=None, screen=None, stacktrace=None, alert_text=None):
|
||||||
|
super(UnexpectedAlertPresentException, self).__init__(msg, screen, stacktrace)
|
||||||
|
self.alert_text = alert_text
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Alert Text: %s\n%s" % (self.alert_text, super(UnexpectedAlertPresentException, self).__str__())
|
||||||
|
|
||||||
|
|
||||||
|
class NoAlertPresentException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when switching to no presented alert.
|
||||||
|
|
||||||
|
This can be caused by calling an operation on the Alert() class when an alert is
|
||||||
|
not yet on the screen.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ElementNotVisibleException(InvalidElementStateException):
|
||||||
|
"""
|
||||||
|
Thrown when an element is present on the DOM, but
|
||||||
|
it is not visible, and so is not able to be interacted with.
|
||||||
|
|
||||||
|
Most commonly encountered when trying to click or read text
|
||||||
|
of an element that is hidden from view.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ElementNotInteractableException(InvalidElementStateException):
|
||||||
|
"""
|
||||||
|
Thrown when an element is present in the DOM but interactions
|
||||||
|
with that element will hit another element do to paint order
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ElementNotSelectableException(InvalidElementStateException):
|
||||||
|
"""
|
||||||
|
Thrown when trying to select an unselectable element.
|
||||||
|
|
||||||
|
For example, selecting a 'script' element.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidCookieDomainException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when attempting to add a cookie under a different domain
|
||||||
|
than the current URL.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnableToSetCookieException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when a driver fails to set a cookie.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteDriverServerException(WebDriverException):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when a command does not complete in enough time.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MoveTargetOutOfBoundsException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when the target provided to the `ActionsChains` move()
|
||||||
|
method is invalid, i.e. out of document.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedTagNameException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when a support class did not get an expected web element.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSelectorException(NoSuchElementException):
|
||||||
|
"""
|
||||||
|
Thrown when the selector which is used to find an element does not return
|
||||||
|
a WebElement. Currently this only happens when the selector is an xpath
|
||||||
|
expression and it is either syntactically invalid (i.e. it is not a
|
||||||
|
xpath expression) or the expression does not select WebElements
|
||||||
|
(e.g. "count(//input)").
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImeNotAvailableException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when IME support is not available. This exception is thrown for every IME-related
|
||||||
|
method call if IME support is not available on the machine.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImeActivationFailedException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Thrown when activating an IME engine has failed.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidArgumentException(WebDriverException):
|
||||||
|
"""
|
||||||
|
The arguments passed to a command are either invalid or malformed.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JavascriptException(WebDriverException):
|
||||||
|
"""
|
||||||
|
An error occurred while executing JavaScript supplied by the user.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchCookieException(WebDriverException):
|
||||||
|
"""
|
||||||
|
No cookie matching the given path name was found amongst the associated cookies of the
|
||||||
|
current browsing context's active document.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenshotException(WebDriverException):
|
||||||
|
"""
|
||||||
|
A screen capture was made impossible.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ElementClickInterceptedException(WebDriverException):
|
||||||
|
"""
|
||||||
|
The Element Click command could not be completed because the element receiving the events
|
||||||
|
is obscuring the element that was requested clicked.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InsecureCertificateException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Navigation caused the user agent to hit a certificate warning, which is usually the result
|
||||||
|
of an expired or invalid TLS certificate.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidCoordinatesException(WebDriverException):
|
||||||
|
"""
|
||||||
|
The coordinates provided to an interactions operation are invalid.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSessionIdException(WebDriverException):
|
||||||
|
"""
|
||||||
|
Occurs if the given session id is not in the list of active sessions, meaning the session
|
||||||
|
either does not exist or that it's not active.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SessionNotCreatedException(WebDriverException):
|
||||||
|
"""
|
||||||
|
A new session could not be created.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownMethodException(WebDriverException):
|
||||||
|
"""
|
||||||
|
The requested command matched a known URL but did not match an method for that URL.
|
||||||
|
"""
|
||||||
|
pass
|
39
selenium/webdriver/__init__.py
Normal file
39
selenium/webdriver/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from .firefox.webdriver import WebDriver as Firefox # noqa
|
||||||
|
from .firefox.firefox_profile import FirefoxProfile # noqa
|
||||||
|
from .firefox.options import Options as FirefoxOptions # noqa
|
||||||
|
from .chrome.webdriver import WebDriver as Chrome # noqa
|
||||||
|
from .chrome.options import Options as ChromeOptions # noqa
|
||||||
|
from .ie.webdriver import WebDriver as Ie # noqa
|
||||||
|
from .ie.options import Options as IeOptions # noqa
|
||||||
|
from .edge.webdriver import WebDriver as Edge # noqa
|
||||||
|
from .opera.webdriver import WebDriver as Opera # noqa
|
||||||
|
from .safari.webdriver import WebDriver as Safari # noqa
|
||||||
|
from .blackberry.webdriver import WebDriver as BlackBerry # noqa
|
||||||
|
from .phantomjs.webdriver import WebDriver as PhantomJS # noqa
|
||||||
|
from .android.webdriver import WebDriver as Android # noqa
|
||||||
|
from .webkitgtk.webdriver import WebDriver as WebKitGTK # noqa
|
||||||
|
from .webkitgtk.options import Options as WebKitGTKOptions # noqa
|
||||||
|
from .remote.webdriver import WebDriver as Remote # noqa
|
||||||
|
from .common.desired_capabilities import DesiredCapabilities # noqa
|
||||||
|
from .common.action_chains import ActionChains # noqa
|
||||||
|
from .common.touch_actions import TouchActions # noqa
|
||||||
|
from .common.proxy import Proxy # noqa
|
||||||
|
|
||||||
|
__version__ = '4.0.0a1'
|
16
selenium/webdriver/android/__init__.py
Normal file
16
selenium/webdriver/android/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
42
selenium/webdriver/android/webdriver.py
Normal file
42
selenium/webdriver/android/webdriver.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
|
||||||
|
|
||||||
|
class WebDriver(RemoteWebDriver):
|
||||||
|
"""
|
||||||
|
Simple RemoteWebDriver wrapper to start connect to Selendroid's WebView app
|
||||||
|
|
||||||
|
For more info on getting started with Selendroid
|
||||||
|
http://selendroid.io/mobileWeb.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host="localhost", port=4444, desired_capabilities=DesiredCapabilities.ANDROID):
|
||||||
|
"""
|
||||||
|
Creates a new instance of Selendroid using the WebView app
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- host - location of where selendroid is running
|
||||||
|
- port - port that selendroid is running on
|
||||||
|
- desired_capabilities: Dictionary object with capabilities
|
||||||
|
"""
|
||||||
|
RemoteWebDriver.__init__(
|
||||||
|
self,
|
||||||
|
command_executor="http://%s:%d/wd/hub" % (host, port),
|
||||||
|
desired_capabilities=desired_capabilities)
|
16
selenium/webdriver/blackberry/__init__.py
Normal file
16
selenium/webdriver/blackberry/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
121
selenium/webdriver/blackberry/webdriver.py
Normal file
121
selenium/webdriver/blackberry/webdriver.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
try:
|
||||||
|
import http.client as http_client
|
||||||
|
except ImportError:
|
||||||
|
import httplib as http_client
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
|
||||||
|
LOAD_TIMEOUT = 5
|
||||||
|
|
||||||
|
|
||||||
|
class WebDriver(RemoteWebDriver):
|
||||||
|
"""
|
||||||
|
Controls the BlackBerry Browser and allows you to drive it.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- device_password - password for the BlackBerry device or emulator you are
|
||||||
|
trying to drive
|
||||||
|
- bb_tools_dir path to the blackberry-deploy executable. If the default
|
||||||
|
is used it assumes it is in the $PATH
|
||||||
|
- hostip - the ip for the device you are trying to drive. Falls back to
|
||||||
|
169.254.0.1 which is the default ip used
|
||||||
|
- port - the port being used for WebDriver on device. defaults to 1338
|
||||||
|
- desired_capabilities: Dictionary object with non-browser specific
|
||||||
|
capabilities only, such as "proxy" or "loggingPref".
|
||||||
|
|
||||||
|
Note: To get blackberry-deploy you will need to install the BlackBerry
|
||||||
|
WebWorks SDK - the default install will put it in the $PATH for you.
|
||||||
|
Download at https://developer.blackberry.com/html5/downloads/
|
||||||
|
"""
|
||||||
|
def __init__(self, device_password, bb_tools_dir=None,
|
||||||
|
hostip='169.254.0.1', port=1338, desired_capabilities={}):
|
||||||
|
import warnings
|
||||||
|
warnings.warn('BlackBerry Driver is no longer supported and will be '
|
||||||
|
'removed in future versions',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
|
remote_addr = 'http://{}:{}'.format(hostip, port)
|
||||||
|
|
||||||
|
filename = 'blackberry-deploy'
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
filename += '.bat'
|
||||||
|
|
||||||
|
if bb_tools_dir is not None:
|
||||||
|
if os.path.isdir(bb_tools_dir):
|
||||||
|
bb_deploy_location = os.path.join(bb_tools_dir, filename)
|
||||||
|
if not os.path.isfile(bb_deploy_location):
|
||||||
|
raise WebDriverException('Invalid blackberry-deploy location: {}'.format(bb_deploy_location))
|
||||||
|
else:
|
||||||
|
raise WebDriverException('Invalid blackberry tools location, must be a directory: {}'.format(bb_tools_dir))
|
||||||
|
else:
|
||||||
|
bb_deploy_location = filename
|
||||||
|
|
||||||
|
"""
|
||||||
|
Now launch the BlackBerry browser before allowing anything else to run.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
launch_args = [bb_deploy_location,
|
||||||
|
'-launchApp',
|
||||||
|
str(hostip),
|
||||||
|
'-package-name', 'sys.browser',
|
||||||
|
'-package-id', 'gYABgJYFHAzbeFMPCCpYWBtHAm0',
|
||||||
|
'-password', str(device_password)]
|
||||||
|
|
||||||
|
with open(os.devnull, 'w') as fp:
|
||||||
|
p = subprocess.Popen(launch_args, stdout=fp)
|
||||||
|
|
||||||
|
returncode = p.wait()
|
||||||
|
|
||||||
|
if returncode == 0:
|
||||||
|
# wait for the BlackBerry10 browser to load.
|
||||||
|
is_running_args = [bb_deploy_location,
|
||||||
|
'-isAppRunning',
|
||||||
|
str(hostip),
|
||||||
|
'-package-name', 'sys.browser',
|
||||||
|
'-package-id', 'gYABgJYFHAzbeFMPCCpYWBtHAm0',
|
||||||
|
'-password', str(device_password)]
|
||||||
|
|
||||||
|
WebDriverWait(None, LOAD_TIMEOUT)\
|
||||||
|
.until(lambda x: subprocess.check_output(is_running_args)
|
||||||
|
.find('result::true'),
|
||||||
|
message='waiting for BlackBerry10 browser to load')
|
||||||
|
|
||||||
|
RemoteWebDriver.__init__(self,
|
||||||
|
command_executor=remote_addr,
|
||||||
|
desired_capabilities=desired_capabilities)
|
||||||
|
else:
|
||||||
|
raise WebDriverException('blackberry-deploy failed to launch browser')
|
||||||
|
except Exception as e:
|
||||||
|
raise WebDriverException('Something went wrong launching blackberry-deploy', stacktrace=getattr(e, 'stacktrace', None))
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
"""
|
||||||
|
Closes the browser and shuts down the
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
RemoteWebDriver.quit(self)
|
||||||
|
except http_client.BadStatusLine:
|
||||||
|
pass
|
16
selenium/webdriver/chrome/__init__.py
Normal file
16
selenium/webdriver/chrome/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
177
selenium/webdriver/chrome/options.py
Normal file
177
selenium/webdriver/chrome/options.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
from selenium.webdriver.common.options import ArgOptions
|
||||||
|
|
||||||
|
|
||||||
|
class Options(ArgOptions):
|
||||||
|
KEY = "goog:chromeOptions"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Options, self).__init__()
|
||||||
|
self._binary_location = ''
|
||||||
|
self._extension_files = []
|
||||||
|
self._extensions = []
|
||||||
|
self._experimental_options = {}
|
||||||
|
self._debugger_address = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def binary_location(self):
|
||||||
|
"""
|
||||||
|
:Returns: The location of the binary, otherwise an empty string
|
||||||
|
"""
|
||||||
|
return self._binary_location
|
||||||
|
|
||||||
|
@binary_location.setter
|
||||||
|
def binary_location(self, value):
|
||||||
|
"""
|
||||||
|
Allows you to set where the chromium binary lives
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: path to the Chromium binary
|
||||||
|
"""
|
||||||
|
self._binary_location = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debugger_address(self):
|
||||||
|
"""
|
||||||
|
:Returns: The address of the remote devtools instance
|
||||||
|
"""
|
||||||
|
return self._debugger_address
|
||||||
|
|
||||||
|
@debugger_address.setter
|
||||||
|
def debugger_address(self, value):
|
||||||
|
"""
|
||||||
|
Allows you to set the address of the remote devtools instance
|
||||||
|
that the ChromeDriver instance will try to connect to during an
|
||||||
|
active wait.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: address of remote devtools instance if any (hostname[:port])
|
||||||
|
"""
|
||||||
|
self._debugger_address = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extensions(self):
|
||||||
|
"""
|
||||||
|
:Returns: A list of encoded extensions that will be loaded into chrome
|
||||||
|
"""
|
||||||
|
encoded_extensions = []
|
||||||
|
for ext in self._extension_files:
|
||||||
|
file_ = open(ext, 'rb')
|
||||||
|
# Should not use base64.encodestring() which inserts newlines every
|
||||||
|
# 76 characters (per RFC 1521). Chromedriver has to remove those
|
||||||
|
# unnecessary newlines before decoding, causing performance hit.
|
||||||
|
encoded_extensions.append(base64.b64encode(file_.read()).decode('UTF-8'))
|
||||||
|
|
||||||
|
file_.close()
|
||||||
|
return encoded_extensions + self._extensions
|
||||||
|
|
||||||
|
def add_extension(self, extension):
|
||||||
|
"""
|
||||||
|
Adds the path to the extension to a list that will be used to extract it
|
||||||
|
to the ChromeDriver
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- extension: path to the \\*.crx file
|
||||||
|
"""
|
||||||
|
if extension:
|
||||||
|
extension_to_add = os.path.abspath(os.path.expanduser(extension))
|
||||||
|
if os.path.exists(extension_to_add):
|
||||||
|
self._extension_files.append(extension_to_add)
|
||||||
|
else:
|
||||||
|
raise IOError("Path to the extension doesn't exist")
|
||||||
|
else:
|
||||||
|
raise ValueError("argument can not be null")
|
||||||
|
|
||||||
|
def add_encoded_extension(self, extension):
|
||||||
|
"""
|
||||||
|
Adds Base64 encoded string with extension data to a list that will be used to extract it
|
||||||
|
to the ChromeDriver
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- extension: Base64 encoded string with extension data
|
||||||
|
"""
|
||||||
|
if extension:
|
||||||
|
self._extensions.append(extension)
|
||||||
|
else:
|
||||||
|
raise ValueError("argument can not be null")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def experimental_options(self):
|
||||||
|
"""
|
||||||
|
:Returns: A dictionary of experimental options for chrome
|
||||||
|
"""
|
||||||
|
return self._experimental_options
|
||||||
|
|
||||||
|
def add_experimental_option(self, name, value):
|
||||||
|
"""
|
||||||
|
Adds an experimental option which is passed to chrome.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
name: The experimental option name.
|
||||||
|
value: The option value.
|
||||||
|
"""
|
||||||
|
self._experimental_options[name] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headless(self):
|
||||||
|
"""
|
||||||
|
:Returns: True if the headless argument is set, else False
|
||||||
|
"""
|
||||||
|
return '--headless' in self._arguments
|
||||||
|
|
||||||
|
@headless.setter
|
||||||
|
def headless(self, value):
|
||||||
|
"""
|
||||||
|
Sets the headless argument
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
value: boolean value indicating to set the headless option
|
||||||
|
"""
|
||||||
|
args = {'--headless'}
|
||||||
|
if value is True:
|
||||||
|
self._arguments.extend(args)
|
||||||
|
else:
|
||||||
|
self._arguments = list(set(self._arguments) - args)
|
||||||
|
|
||||||
|
def to_capabilities(self):
|
||||||
|
"""
|
||||||
|
Creates a capabilities with all the options that have been set
|
||||||
|
|
||||||
|
:Returns: A dictionary with everything
|
||||||
|
"""
|
||||||
|
caps = self._caps
|
||||||
|
chrome_options = self.experimental_options.copy()
|
||||||
|
chrome_options["extensions"] = self.extensions
|
||||||
|
if self.binary_location:
|
||||||
|
chrome_options["binary"] = self.binary_location
|
||||||
|
chrome_options["args"] = self.arguments
|
||||||
|
if self.debugger_address:
|
||||||
|
chrome_options["debuggerAddress"] = self.debugger_address
|
||||||
|
|
||||||
|
caps[self.KEY] = chrome_options
|
||||||
|
|
||||||
|
return caps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_capabilities(self):
|
||||||
|
return DesiredCapabilities.CHROME.copy()
|
33
selenium/webdriver/chrome/remote_connection.py
Normal file
33
selenium/webdriver/chrome/remote_connection.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||||
|
|
||||||
|
|
||||||
|
class ChromeRemoteConnection(RemoteConnection):
|
||||||
|
|
||||||
|
def __init__(self, remote_server_addr, keep_alive=True):
|
||||||
|
RemoteConnection.__init__(self, remote_server_addr, keep_alive)
|
||||||
|
self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
|
||||||
|
self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions')
|
||||||
|
self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions')
|
||||||
|
self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/goog/cdp/execute')
|
||||||
|
self._commands['getSinks'] = ('GET', '/session/$sessionId/goog/cast/get_sinks')
|
||||||
|
self._commands['getIssueMessage'] = ('GET', '/session/$sessionId/goog/cast/get_issue_message')
|
||||||
|
self._commands['setSinkToUse'] = ('POST', '/session/$sessionId/goog/cast/set_sink_to_use')
|
||||||
|
self._commands['startTabMirroring'] = ('POST', '/session/$sessionId/goog/cast/start_tab_mirroring')
|
||||||
|
self._commands['stopCasting'] = ('POST', '/session/$sessionId/goog/cast/stop_casting')
|
45
selenium/webdriver/chrome/service.py
Normal file
45
selenium/webdriver/chrome/service.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common import service
|
||||||
|
|
||||||
|
|
||||||
|
class Service(service.Service):
|
||||||
|
"""
|
||||||
|
Object that manages the starting and stopping of the ChromeDriver
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable_path, port=0, service_args=None,
|
||||||
|
log_path=None, env=None):
|
||||||
|
"""
|
||||||
|
Creates a new instance of the Service
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- executable_path : Path to the ChromeDriver
|
||||||
|
- port : Port the service is running on
|
||||||
|
- service_args : List of args to pass to the chromedriver service
|
||||||
|
- log_path : Path for the chromedriver service to log to"""
|
||||||
|
|
||||||
|
self.service_args = service_args or []
|
||||||
|
if log_path:
|
||||||
|
self.service_args.append('--log-path=%s' % log_path)
|
||||||
|
|
||||||
|
service.Service.__init__(self, executable_path, port=port, env=env,
|
||||||
|
start_error_message="Please see https://sites.google.com/a/chromium.org/chromedriver/home")
|
||||||
|
|
||||||
|
def command_line_args(self):
|
||||||
|
return ["--port=%d" % self.port] + self.service_args
|
223
selenium/webdriver/chrome/webdriver.py
Normal file
223
selenium/webdriver/chrome/webdriver.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import warnings
|
||||||
|
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||||
|
from .remote_connection import ChromeRemoteConnection
|
||||||
|
from .service import Service
|
||||||
|
from .options import Options
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PORT = 0
|
||||||
|
DEFAULT_SERVICE_LOG_PATH = None
|
||||||
|
|
||||||
|
|
||||||
|
class WebDriver(RemoteWebDriver):
|
||||||
|
"""
|
||||||
|
Controls the ChromeDriver and allows you to drive the browser.
|
||||||
|
|
||||||
|
You will need to download the ChromeDriver executable from
|
||||||
|
http://chromedriver.storage.googleapis.com/index.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT,
|
||||||
|
options=None, service_args=None,
|
||||||
|
desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH,
|
||||||
|
chrome_options=None, service=None, keep_alive=True):
|
||||||
|
"""
|
||||||
|
Creates a new instance of the chrome driver.
|
||||||
|
|
||||||
|
Starts the service and then creates new instance of chrome driver.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH
|
||||||
|
- port - Deprecated: port you would like the service to run, if left as 0, a free port will be found.
|
||||||
|
- options - this takes an instance of ChromeOptions
|
||||||
|
- service_args - Deprecated: List of args to pass to the driver service
|
||||||
|
- desired_capabilities - Deprecated: Dictionary object with non-browser specific
|
||||||
|
capabilities only, such as "proxy" or "loggingPref".
|
||||||
|
- service_log_path - Deprecated: Where to log information from the driver.
|
||||||
|
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
|
||||||
|
"""
|
||||||
|
if executable_path != 'chromedriver':
|
||||||
|
warnings.warn('executable_path has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if desired_capabilities is not None:
|
||||||
|
warnings.warn('desired_capabilities has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if port != DEFAULT_PORT:
|
||||||
|
warnings.warn('port has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
self.port = port
|
||||||
|
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
|
||||||
|
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
|
if chrome_options:
|
||||||
|
warnings.warn('use options instead of chrome_options',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
options = chrome_options
|
||||||
|
|
||||||
|
if options is None:
|
||||||
|
# desired_capabilities stays as passed in
|
||||||
|
if desired_capabilities is None:
|
||||||
|
desired_capabilities = self.create_options().to_capabilities()
|
||||||
|
else:
|
||||||
|
if desired_capabilities is None:
|
||||||
|
desired_capabilities = options.to_capabilities()
|
||||||
|
else:
|
||||||
|
desired_capabilities.update(options.to_capabilities())
|
||||||
|
|
||||||
|
if service:
|
||||||
|
self.service = service
|
||||||
|
else:
|
||||||
|
self.service = Service(
|
||||||
|
executable_path,
|
||||||
|
port=port,
|
||||||
|
service_args=service_args,
|
||||||
|
log_path=service_log_path)
|
||||||
|
self.service.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
RemoteWebDriver.__init__(
|
||||||
|
self,
|
||||||
|
command_executor=ChromeRemoteConnection(
|
||||||
|
remote_server_addr=self.service.service_url,
|
||||||
|
keep_alive=keep_alive),
|
||||||
|
desired_capabilities=desired_capabilities)
|
||||||
|
except Exception:
|
||||||
|
self.quit()
|
||||||
|
raise
|
||||||
|
self._is_remote = False
|
||||||
|
|
||||||
|
def launch_app(self, id):
|
||||||
|
"""Launches Chrome app specified by id."""
|
||||||
|
return self.execute("launchApp", {'id': id})
|
||||||
|
|
||||||
|
def get_network_conditions(self):
|
||||||
|
"""
|
||||||
|
Gets Chrome network emulation settings.
|
||||||
|
|
||||||
|
:Returns:
|
||||||
|
A dict. For example:
|
||||||
|
|
||||||
|
{'latency': 4, 'download_throughput': 2, 'upload_throughput': 2,
|
||||||
|
'offline': False}
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.execute("getNetworkConditions")['value']
|
||||||
|
|
||||||
|
def set_network_conditions(self, **network_conditions):
|
||||||
|
"""
|
||||||
|
Sets Chrome network emulation settings.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- network_conditions: A dict with conditions specification.
|
||||||
|
|
||||||
|
:Usage:
|
||||||
|
::
|
||||||
|
|
||||||
|
driver.set_network_conditions(
|
||||||
|
offline=False,
|
||||||
|
latency=5, # additional latency (ms)
|
||||||
|
download_throughput=500 * 1024, # maximal throughput
|
||||||
|
upload_throughput=500 * 1024) # maximal throughput
|
||||||
|
|
||||||
|
Note: 'throughput' can be used to set both (for download and upload).
|
||||||
|
"""
|
||||||
|
self.execute("setNetworkConditions", {
|
||||||
|
'network_conditions': network_conditions
|
||||||
|
})
|
||||||
|
|
||||||
|
def execute_cdp_cmd(self, cmd, cmd_args):
|
||||||
|
"""
|
||||||
|
Execute Chrome Devtools Protocol command and get returned result
|
||||||
|
|
||||||
|
The command and command args should follow chrome devtools protocol domains/commands, refer to link
|
||||||
|
https://chromedevtools.github.io/devtools-protocol/
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- cmd: A str, command name
|
||||||
|
- cmd_args: A dict, command args. empty dict {} if there is no command args
|
||||||
|
|
||||||
|
:Usage:
|
||||||
|
::
|
||||||
|
|
||||||
|
driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
|
||||||
|
|
||||||
|
:Returns:
|
||||||
|
A dict, empty dict {} if there is no result to return.
|
||||||
|
For example to getResponseBody:
|
||||||
|
|
||||||
|
{'base64Encoded': False, 'body': 'response body string'}
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
|
||||||
|
|
||||||
|
def get_sinks(self):
|
||||||
|
"""
|
||||||
|
:Returns: A list of sinks avaliable for Cast.
|
||||||
|
"""
|
||||||
|
return self.execute('getSinks')['value']
|
||||||
|
|
||||||
|
def get_issue_message(self):
|
||||||
|
"""
|
||||||
|
:Returns: An error message when there is any issue in a Cast session.
|
||||||
|
"""
|
||||||
|
return self.execute('getIssueMessage')['value']
|
||||||
|
|
||||||
|
def set_sink_to_use(self, sink_name):
|
||||||
|
"""
|
||||||
|
Sets a specific sink, using its name, as a Cast session receiver target.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- sink_name: Name of the sink to use as the target.
|
||||||
|
"""
|
||||||
|
return self.execute('setSinkToUse', {'sinkName': sink_name})
|
||||||
|
|
||||||
|
def start_tab_mirroring(self, sink_name):
|
||||||
|
"""
|
||||||
|
Starts a tab mirroring session on a specific receiver target.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- sink_name: Name of the sink to use as the target.
|
||||||
|
"""
|
||||||
|
return self.execute('startTabMirroring', {'sinkName': sink_name})
|
||||||
|
|
||||||
|
def stop_casting(self, sink_name):
|
||||||
|
"""
|
||||||
|
Stops the existing Cast session on a specific receiver target.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- sink_name: Name of the sink to stop the Cast session.
|
||||||
|
"""
|
||||||
|
return self.execute('stopCasting', {'sinkName': sink_name})
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
"""
|
||||||
|
Closes the browser and shuts down the ChromeDriver executable
|
||||||
|
that is started when starting the ChromeDriver
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
RemoteWebDriver.quit(self)
|
||||||
|
except Exception:
|
||||||
|
# We don't care about the message because something probably has gone wrong
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.service.stop()
|
||||||
|
|
||||||
|
def create_options(self):
|
||||||
|
return Options()
|
16
selenium/webdriver/common/__init__.py
Normal file
16
selenium/webdriver/common/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
365
selenium/webdriver/common/action_chains.py
Normal file
365
selenium/webdriver/common/action_chains.py
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The ActionChains implementation,
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
|
||||||
|
from .utils import keys_to_typing
|
||||||
|
from .actions.action_builder import ActionBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class ActionChains(object):
|
||||||
|
"""
|
||||||
|
ActionChains are a way to automate low level interactions such as
|
||||||
|
mouse movements, mouse button actions, key press, and context menu interactions.
|
||||||
|
This is useful for doing more complex actions like hover over and drag and drop.
|
||||||
|
|
||||||
|
Generate user actions.
|
||||||
|
When you call methods for actions on the ActionChains object,
|
||||||
|
the actions are stored in a queue in the ActionChains object.
|
||||||
|
When you call perform(), the events are fired in the order they
|
||||||
|
are queued up.
|
||||||
|
|
||||||
|
ActionChains can be used in a chain pattern::
|
||||||
|
|
||||||
|
menu = driver.find_element_by_css_selector(".nav")
|
||||||
|
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
|
||||||
|
|
||||||
|
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
|
||||||
|
|
||||||
|
Or actions can be queued up one by one, then performed.::
|
||||||
|
|
||||||
|
menu = driver.find_element_by_css_selector(".nav")
|
||||||
|
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
|
||||||
|
|
||||||
|
actions = ActionChains(driver)
|
||||||
|
actions.move_to_element(menu)
|
||||||
|
actions.click(hidden_submenu)
|
||||||
|
actions.perform()
|
||||||
|
|
||||||
|
Either way, the actions are performed in the order they are called, one after
|
||||||
|
another.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
"""
|
||||||
|
Creates a new ActionChains.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- driver: The WebDriver instance which performs user actions.
|
||||||
|
"""
|
||||||
|
self._driver = driver
|
||||||
|
self._actions = []
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions = ActionBuilder(driver)
|
||||||
|
|
||||||
|
def perform(self):
|
||||||
|
"""
|
||||||
|
Performs all stored actions.
|
||||||
|
"""
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.perform()
|
||||||
|
else:
|
||||||
|
for action in self._actions:
|
||||||
|
action()
|
||||||
|
|
||||||
|
def reset_actions(self):
|
||||||
|
"""
|
||||||
|
Clears actions that are already stored locally and on the remote end
|
||||||
|
"""
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.clear_actions()
|
||||||
|
for device in self.w3c_actions.devices:
|
||||||
|
device.clear_actions()
|
||||||
|
self._actions = []
|
||||||
|
|
||||||
|
def click(self, on_element=None):
|
||||||
|
"""
|
||||||
|
Clicks an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to click.
|
||||||
|
If None, clicks on current mouse position.
|
||||||
|
"""
|
||||||
|
if on_element:
|
||||||
|
self.move_to_element(on_element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.click()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.CLICK, {'button': 0}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click_and_hold(self, on_element=None):
|
||||||
|
"""
|
||||||
|
Holds down the left mouse button on an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to mouse down.
|
||||||
|
If None, clicks on current mouse position.
|
||||||
|
"""
|
||||||
|
if on_element:
|
||||||
|
self.move_to_element(on_element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.click_and_hold()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.MOUSE_DOWN, {}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def context_click(self, on_element=None):
|
||||||
|
"""
|
||||||
|
Performs a context-click (right click) on an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to context-click.
|
||||||
|
If None, clicks on current mouse position.
|
||||||
|
"""
|
||||||
|
if on_element:
|
||||||
|
self.move_to_element(on_element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.context_click()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.CLICK, {'button': 2}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def double_click(self, on_element=None):
|
||||||
|
"""
|
||||||
|
Double-clicks an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to double-click.
|
||||||
|
If None, clicks on current mouse position.
|
||||||
|
"""
|
||||||
|
if on_element:
|
||||||
|
self.move_to_element(on_element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.double_click()
|
||||||
|
for _ in range(4):
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.DOUBLE_CLICK, {}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def drag_and_drop(self, source, target):
|
||||||
|
"""
|
||||||
|
Holds down the left mouse button on the source element,
|
||||||
|
then moves to the target element and releases the mouse button.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- source: The element to mouse down.
|
||||||
|
- target: The element to mouse up.
|
||||||
|
"""
|
||||||
|
self.click_and_hold(source)
|
||||||
|
self.release(target)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def drag_and_drop_by_offset(self, source, xoffset, yoffset):
|
||||||
|
"""
|
||||||
|
Holds down the left mouse button on the source element,
|
||||||
|
then moves to the target offset and releases the mouse button.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- source: The element to mouse down.
|
||||||
|
- xoffset: X offset to move to.
|
||||||
|
- yoffset: Y offset to move to.
|
||||||
|
"""
|
||||||
|
self.click_and_hold(source)
|
||||||
|
self.move_by_offset(xoffset, yoffset)
|
||||||
|
self.release()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def key_down(self, value, element=None):
|
||||||
|
"""
|
||||||
|
Sends a key press only, without releasing it.
|
||||||
|
Should only be used with modifier keys (Control, Alt and Shift).
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The modifier key to send. Values are defined in `Keys` class.
|
||||||
|
- element: The element to send keys.
|
||||||
|
If None, sends a key to current focused element.
|
||||||
|
|
||||||
|
Example, pressing ctrl+c::
|
||||||
|
|
||||||
|
ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
|
||||||
|
|
||||||
|
"""
|
||||||
|
if element:
|
||||||
|
self.click(element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.key_action.key_down(value)
|
||||||
|
self.w3c_actions.pointer_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
|
||||||
|
{"value": keys_to_typing(value)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def key_up(self, value, element=None):
|
||||||
|
"""
|
||||||
|
Releases a modifier key.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The modifier key to send. Values are defined in Keys class.
|
||||||
|
- element: The element to send keys.
|
||||||
|
If None, sends a key to current focused element.
|
||||||
|
|
||||||
|
Example, pressing ctrl+c::
|
||||||
|
|
||||||
|
ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
|
||||||
|
|
||||||
|
"""
|
||||||
|
if element:
|
||||||
|
self.click(element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.key_action.key_up(value)
|
||||||
|
self.w3c_actions.pointer_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
|
||||||
|
{"value": keys_to_typing(value)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_by_offset(self, xoffset, yoffset):
|
||||||
|
"""
|
||||||
|
Moving the mouse to an offset from current mouse position.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xoffset: X offset to move to, as a positive or negative integer.
|
||||||
|
- yoffset: Y offset to move to, as a positive or negative integer.
|
||||||
|
"""
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.MOVE_TO, {
|
||||||
|
'xoffset': int(xoffset),
|
||||||
|
'yoffset': int(yoffset)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_to_element(self, to_element):
|
||||||
|
"""
|
||||||
|
Moving the mouse to the middle of an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- to_element: The WebElement to move to.
|
||||||
|
"""
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.move_to(to_element)
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.MOVE_TO, {'element': to_element.id}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_to_element_with_offset(self, to_element, xoffset, yoffset):
|
||||||
|
"""
|
||||||
|
Move the mouse by an offset of the specified element.
|
||||||
|
Offsets are relative to the top-left corner of the element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- to_element: The WebElement to move to.
|
||||||
|
- xoffset: X offset to move to.
|
||||||
|
- yoffset: Y offset to move to.
|
||||||
|
"""
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.move_to(to_element, xoffset, yoffset)
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(
|
||||||
|
lambda: self._driver.execute(Command.MOVE_TO, {
|
||||||
|
'element': to_element.id,
|
||||||
|
'xoffset': int(xoffset),
|
||||||
|
'yoffset': int(yoffset)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def pause(self, seconds):
|
||||||
|
""" Pause all inputs for the specified duration in seconds """
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.pause(seconds)
|
||||||
|
self.w3c_actions.key_action.pause(seconds)
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: time.sleep(seconds))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def release(self, on_element=None):
|
||||||
|
"""
|
||||||
|
Releasing a held mouse button on an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to mouse up.
|
||||||
|
If None, releases on current mouse position.
|
||||||
|
"""
|
||||||
|
if on_element:
|
||||||
|
self.move_to_element(on_element)
|
||||||
|
if self._driver.w3c:
|
||||||
|
self.w3c_actions.pointer_action.release()
|
||||||
|
self.w3c_actions.key_action.pause()
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(Command.MOUSE_UP, {}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def send_keys(self, *keys_to_send):
|
||||||
|
"""
|
||||||
|
Sends keys to current focused element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- keys_to_send: The keys to send. Modifier keys constants can be found in the
|
||||||
|
'Keys' class.
|
||||||
|
"""
|
||||||
|
typing = keys_to_typing(keys_to_send)
|
||||||
|
if self._driver.w3c:
|
||||||
|
for key in typing:
|
||||||
|
self.key_down(key)
|
||||||
|
self.key_up(key)
|
||||||
|
else:
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': typing}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def send_keys_to_element(self, element, *keys_to_send):
|
||||||
|
"""
|
||||||
|
Sends keys to an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- element: The element to send keys.
|
||||||
|
- keys_to_send: The keys to send. Modifier keys constants can be found in the
|
||||||
|
'Keys' class.
|
||||||
|
"""
|
||||||
|
self.click(element)
|
||||||
|
self.send_keys(*keys_to_send)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# Context manager so ActionChains can be used in a 'with .. as' statements.
|
||||||
|
def __enter__(self):
|
||||||
|
return self # Return created instance of self.
|
||||||
|
|
||||||
|
def __exit__(self, _type, _value, _traceback):
|
||||||
|
pass # Do nothing, does not require additional cleanup.
|
16
selenium/webdriver/common/actions/__init__.py
Normal file
16
selenium/webdriver/common/actions/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
85
selenium/webdriver/common/actions/action_builder.py
Normal file
85
selenium/webdriver/common/actions/action_builder.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
from . import interaction
|
||||||
|
from .key_actions import KeyActions
|
||||||
|
from .key_input import KeyInput
|
||||||
|
from .pointer_actions import PointerActions
|
||||||
|
from .pointer_input import PointerInput
|
||||||
|
|
||||||
|
|
||||||
|
class ActionBuilder(object):
|
||||||
|
def __init__(self, driver, mouse=None, keyboard=None):
|
||||||
|
if mouse is None:
|
||||||
|
mouse = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||||
|
if keyboard is None:
|
||||||
|
keyboard = KeyInput(interaction.KEY)
|
||||||
|
self.devices = [mouse, keyboard]
|
||||||
|
self._key_action = KeyActions(keyboard)
|
||||||
|
self._pointer_action = PointerActions(mouse)
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def get_device_with(self, name):
|
||||||
|
try:
|
||||||
|
idx = self.devices.index(name)
|
||||||
|
return self.devices[idx]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pointer_inputs(self):
|
||||||
|
return [device for device in self.devices if device.type == interaction.POINTER]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_inputs(self):
|
||||||
|
return [device for device in self.devices if device.type == interaction.KEY]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_action(self):
|
||||||
|
return self._key_action
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pointer_action(self):
|
||||||
|
return self._pointer_action
|
||||||
|
|
||||||
|
def add_key_input(self, name):
|
||||||
|
new_input = KeyInput(name)
|
||||||
|
self._add_input(new_input)
|
||||||
|
return new_input
|
||||||
|
|
||||||
|
def add_pointer_input(self, kind, name):
|
||||||
|
new_input = PointerInput(kind, name)
|
||||||
|
self._add_input(new_input)
|
||||||
|
return new_input
|
||||||
|
|
||||||
|
def perform(self):
|
||||||
|
enc = {"actions": []}
|
||||||
|
for device in self.devices:
|
||||||
|
encoded = device.encode()
|
||||||
|
if encoded['actions']:
|
||||||
|
enc["actions"].append(encoded)
|
||||||
|
self.driver.execute(Command.W3C_ACTIONS, enc)
|
||||||
|
|
||||||
|
def clear_actions(self):
|
||||||
|
"""
|
||||||
|
Clears actions that are already stored on the remote end
|
||||||
|
"""
|
||||||
|
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
|
||||||
|
|
||||||
|
def _add_input(self, input):
|
||||||
|
self.devices.append(input)
|
43
selenium/webdriver/common/actions/input_device.py
Normal file
43
selenium/webdriver/common/actions/input_device.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class InputDevice(object):
|
||||||
|
"""
|
||||||
|
Describes the input device being used for the action.
|
||||||
|
"""
|
||||||
|
def __init__(self, name=None):
|
||||||
|
if name is None:
|
||||||
|
self.name = uuid.uuid4()
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
self.actions = []
|
||||||
|
|
||||||
|
def add_action(self, action):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.actions.append(action)
|
||||||
|
|
||||||
|
def clear_actions(self):
|
||||||
|
self.actions = []
|
||||||
|
|
||||||
|
def create_pause(self, duraton=0):
|
||||||
|
pass
|
50
selenium/webdriver/common/actions/interaction.py
Normal file
50
selenium/webdriver/common/actions/interaction.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
KEY = "key"
|
||||||
|
POINTER = "pointer"
|
||||||
|
NONE = "none"
|
||||||
|
SOURCE_TYPES = set([KEY, POINTER, NONE])
|
||||||
|
|
||||||
|
POINTER_MOUSE = "mouse"
|
||||||
|
POINTER_TOUCH = "touch"
|
||||||
|
POINTER_PEN = "pen"
|
||||||
|
|
||||||
|
POINTER_KINDS = set([POINTER_MOUSE, POINTER_TOUCH, POINTER_PEN])
|
||||||
|
|
||||||
|
|
||||||
|
class Interaction(object):
|
||||||
|
|
||||||
|
PAUSE = "pause"
|
||||||
|
|
||||||
|
def __init__(self, source):
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
|
||||||
|
class Pause(Interaction):
|
||||||
|
|
||||||
|
def __init__(self, source, duration=0):
|
||||||
|
super(Interaction, self).__init__()
|
||||||
|
self.source = source
|
||||||
|
self.duration = duration
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
return {
|
||||||
|
"type": self.PAUSE,
|
||||||
|
"duration": int(self.duration * 1000)
|
||||||
|
}
|
50
selenium/webdriver/common/actions/key_actions.py
Normal file
50
selenium/webdriver/common/actions/key_actions.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from .interaction import Interaction, KEY
|
||||||
|
from .key_input import KeyInput
|
||||||
|
from ..utils import keys_to_typing
|
||||||
|
|
||||||
|
|
||||||
|
class KeyActions(Interaction):
|
||||||
|
|
||||||
|
def __init__(self, source=None):
|
||||||
|
if source is None:
|
||||||
|
source = KeyInput(KEY)
|
||||||
|
self.source = source
|
||||||
|
super(KeyActions, self).__init__(source)
|
||||||
|
|
||||||
|
def key_down(self, letter):
|
||||||
|
return self._key_action("create_key_down", letter)
|
||||||
|
|
||||||
|
def key_up(self, letter):
|
||||||
|
return self._key_action("create_key_up", letter)
|
||||||
|
|
||||||
|
def pause(self, duration=0):
|
||||||
|
return self._key_action("create_pause", duration)
|
||||||
|
|
||||||
|
def send_keys(self, text):
|
||||||
|
if not isinstance(text, list):
|
||||||
|
text = keys_to_typing(text)
|
||||||
|
for letter in text:
|
||||||
|
self.key_down(letter)
|
||||||
|
self.key_up(letter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _key_action(self, action, letter):
|
||||||
|
meth = getattr(self.source, action)
|
||||||
|
meth(letter)
|
||||||
|
return self
|
51
selenium/webdriver/common/actions/key_input.py
Normal file
51
selenium/webdriver/common/actions/key_input.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from . import interaction
|
||||||
|
|
||||||
|
from .input_device import InputDevice
|
||||||
|
from .interaction import (Interaction,
|
||||||
|
Pause)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyInput(InputDevice):
|
||||||
|
def __init__(self, name):
|
||||||
|
super(KeyInput, self).__init__()
|
||||||
|
self.name = name
|
||||||
|
self.type = interaction.KEY
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
return {"type": self.type, "id": self.name, "actions": [acts.encode() for acts in self.actions]}
|
||||||
|
|
||||||
|
def create_key_down(self, key):
|
||||||
|
self.add_action(TypingInteraction(self, "keyDown", key))
|
||||||
|
|
||||||
|
def create_key_up(self, key):
|
||||||
|
self.add_action(TypingInteraction(self, "keyUp", key))
|
||||||
|
|
||||||
|
def create_pause(self, pause_duration=0):
|
||||||
|
self.add_action(Pause(self, pause_duration))
|
||||||
|
|
||||||
|
|
||||||
|
class TypingInteraction(Interaction):
|
||||||
|
|
||||||
|
def __init__(self, source, type_, key):
|
||||||
|
super(TypingInteraction, self).__init__(source)
|
||||||
|
self.type = type_
|
||||||
|
self.key = key
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
return {"type": self.type, "value": self.key}
|
23
selenium/webdriver/common/actions/mouse_button.py
Normal file
23
selenium/webdriver/common/actions/mouse_button.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
class MouseButton(object):
|
||||||
|
|
||||||
|
LEFT = 0
|
||||||
|
MIDDLE = 1
|
||||||
|
RIGHT = 2
|
101
selenium/webdriver/common/actions/pointer_actions.py
Normal file
101
selenium/webdriver/common/actions/pointer_actions.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from . import interaction
|
||||||
|
|
||||||
|
from .interaction import Interaction
|
||||||
|
from .mouse_button import MouseButton
|
||||||
|
from .pointer_input import PointerInput
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement
|
||||||
|
|
||||||
|
|
||||||
|
class PointerActions(Interaction):
|
||||||
|
|
||||||
|
def __init__(self, source=None):
|
||||||
|
if source is None:
|
||||||
|
source = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||||
|
self.source = source
|
||||||
|
super(PointerActions, self).__init__(source)
|
||||||
|
|
||||||
|
def pointer_down(self, button=MouseButton.LEFT):
|
||||||
|
self._button_action("create_pointer_down", button=button)
|
||||||
|
|
||||||
|
def pointer_up(self, button=MouseButton.LEFT):
|
||||||
|
self._button_action("create_pointer_up", button=button)
|
||||||
|
|
||||||
|
def move_to(self, element, x=None, y=None):
|
||||||
|
if not isinstance(element, (WebElement, EventFiringWebElement)):
|
||||||
|
raise AttributeError("move_to requires a WebElement")
|
||||||
|
if x is not None or y is not None:
|
||||||
|
el_rect = element.rect
|
||||||
|
left_offset = el_rect['width'] / 2
|
||||||
|
top_offset = el_rect['height'] / 2
|
||||||
|
left = -left_offset + (x or 0)
|
||||||
|
top = -top_offset + (y or 0)
|
||||||
|
else:
|
||||||
|
left = 0
|
||||||
|
top = 0
|
||||||
|
self.source.create_pointer_move(origin=element, x=int(left), y=int(top))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_by(self, x, y):
|
||||||
|
self.source.create_pointer_move(origin=interaction.POINTER, x=int(x), y=int(y))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_to_location(self, x, y):
|
||||||
|
self.source.create_pointer_move(origin='viewport', x=int(x), y=int(y))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click(self, element=None):
|
||||||
|
if element:
|
||||||
|
self.move_to(element)
|
||||||
|
self.pointer_down(MouseButton.LEFT)
|
||||||
|
self.pointer_up(MouseButton.LEFT)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def context_click(self, element=None):
|
||||||
|
if element:
|
||||||
|
self.move_to(element)
|
||||||
|
self.pointer_down(MouseButton.RIGHT)
|
||||||
|
self.pointer_up(MouseButton.RIGHT)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click_and_hold(self, element=None):
|
||||||
|
if element:
|
||||||
|
self.move_to(element)
|
||||||
|
self.pointer_down()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
self.pointer_up()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def double_click(self, element=None):
|
||||||
|
if element:
|
||||||
|
self.move_to(element)
|
||||||
|
self.click()
|
||||||
|
self.click()
|
||||||
|
|
||||||
|
def pause(self, duration=0):
|
||||||
|
self.source.create_pause(duration)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _button_action(self, action, button=MouseButton.LEFT):
|
||||||
|
meth = getattr(self.source, action)
|
||||||
|
meth(button)
|
||||||
|
return self
|
64
selenium/webdriver/common/actions/pointer_input.py
Normal file
64
selenium/webdriver/common/actions/pointer_input.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from .input_device import InputDevice
|
||||||
|
from .interaction import POINTER, POINTER_KINDS
|
||||||
|
|
||||||
|
from selenium.common.exceptions import InvalidArgumentException
|
||||||
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement
|
||||||
|
|
||||||
|
|
||||||
|
class PointerInput(InputDevice):
|
||||||
|
|
||||||
|
DEFAULT_MOVE_DURATION = 250
|
||||||
|
|
||||||
|
def __init__(self, kind, name):
|
||||||
|
super(PointerInput, self).__init__()
|
||||||
|
if (kind not in POINTER_KINDS):
|
||||||
|
raise InvalidArgumentException("Invalid PointerInput kind '%s'" % kind)
|
||||||
|
self.type = POINTER
|
||||||
|
self.kind = kind
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def create_pointer_move(self, duration=DEFAULT_MOVE_DURATION, x=None, y=None, origin=None):
|
||||||
|
action = dict(type="pointerMove", duration=duration)
|
||||||
|
action["x"] = x
|
||||||
|
action["y"] = y
|
||||||
|
if isinstance(origin, (WebElement, EventFiringWebElement)):
|
||||||
|
action["origin"] = {"element-6066-11e4-a52e-4f735466cecf": origin.id}
|
||||||
|
elif origin is not None:
|
||||||
|
action["origin"] = origin
|
||||||
|
|
||||||
|
self.add_action(action)
|
||||||
|
|
||||||
|
def create_pointer_down(self, button):
|
||||||
|
self.add_action({"type": "pointerDown", "duration": 0, "button": button})
|
||||||
|
|
||||||
|
def create_pointer_up(self, button):
|
||||||
|
self.add_action({"type": "pointerUp", "duration": 0, "button": button})
|
||||||
|
|
||||||
|
def create_pointer_cancel(self):
|
||||||
|
self.add_action({"type": "pointerCancel"})
|
||||||
|
|
||||||
|
def create_pause(self, pause_duration):
|
||||||
|
self.add_action({"type": "pause", "duration": int(pause_duration * 1000)})
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
return {"type": self.type,
|
||||||
|
"parameters": {"pointerType": self.kind},
|
||||||
|
"id": self.name,
|
||||||
|
"actions": [acts for acts in self.actions]}
|
105
selenium/webdriver/common/alert.py
Normal file
105
selenium/webdriver/common/alert.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Alert implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from selenium.webdriver.common.utils import keys_to_typing
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
|
||||||
|
|
||||||
|
class Alert(object):
|
||||||
|
"""
|
||||||
|
Allows to work with alerts.
|
||||||
|
|
||||||
|
Use this class to interact with alert prompts. It contains methods for dismissing,
|
||||||
|
accepting, inputting, and getting text from alert prompts.
|
||||||
|
|
||||||
|
Accepting / Dismissing alert prompts::
|
||||||
|
|
||||||
|
Alert(driver).accept()
|
||||||
|
Alert(driver).dismiss()
|
||||||
|
|
||||||
|
Inputting a value into an alert prompt:
|
||||||
|
|
||||||
|
name_prompt = Alert(driver)
|
||||||
|
name_prompt.send_keys("Willian Shakesphere")
|
||||||
|
name_prompt.accept()
|
||||||
|
|
||||||
|
|
||||||
|
Reading a the text of a prompt for verification:
|
||||||
|
|
||||||
|
alert_text = Alert(driver).text
|
||||||
|
self.assertEqual("Do you wish to quit?", alert_text)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
"""
|
||||||
|
Creates a new Alert.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- driver: The WebDriver instance which performs user actions.
|
||||||
|
"""
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
"""
|
||||||
|
Gets the text of the Alert.
|
||||||
|
"""
|
||||||
|
if self.driver.w3c:
|
||||||
|
return self.driver.execute(Command.W3C_GET_ALERT_TEXT)["value"]
|
||||||
|
else:
|
||||||
|
return self.driver.execute(Command.GET_ALERT_TEXT)["value"]
|
||||||
|
|
||||||
|
def dismiss(self):
|
||||||
|
"""
|
||||||
|
Dismisses the alert available.
|
||||||
|
"""
|
||||||
|
if self.driver.w3c:
|
||||||
|
self.driver.execute(Command.W3C_DISMISS_ALERT)
|
||||||
|
else:
|
||||||
|
self.driver.execute(Command.DISMISS_ALERT)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
"""
|
||||||
|
Accepts the alert available.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
Alert(driver).accept() # Confirm a alert dialog.
|
||||||
|
"""
|
||||||
|
if self.driver.w3c:
|
||||||
|
self.driver.execute(Command.W3C_ACCEPT_ALERT)
|
||||||
|
else:
|
||||||
|
self.driver.execute(Command.ACCEPT_ALERT)
|
||||||
|
|
||||||
|
def send_keys(self, keysToSend):
|
||||||
|
"""
|
||||||
|
Send Keys to the Alert.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- keysToSend: The text to be sent to Alert.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.driver.w3c:
|
||||||
|
self.driver.execute(Command.W3C_SET_ALERT_VALUE, {'value': keys_to_typing(keysToSend),
|
||||||
|
'text': keysToSend})
|
||||||
|
else:
|
||||||
|
self.driver.execute(Command.SET_ALERT_VALUE, {'text': keysToSend})
|
35
selenium/webdriver/common/by.py
Normal file
35
selenium/webdriver/common/by.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The By implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class By(object):
|
||||||
|
"""
|
||||||
|
Set of supported locator strategies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ID = "id"
|
||||||
|
XPATH = "xpath"
|
||||||
|
LINK_TEXT = "link text"
|
||||||
|
PARTIAL_LINK_TEXT = "partial link text"
|
||||||
|
NAME = "name"
|
||||||
|
TAG_NAME = "tag name"
|
||||||
|
CLASS_NAME = "class name"
|
||||||
|
CSS_SELECTOR = "css selector"
|
127
selenium/webdriver/common/desired_capabilities.py
Normal file
127
selenium/webdriver/common/desired_capabilities.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Desired Capabilities implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DesiredCapabilities(object):
|
||||||
|
"""
|
||||||
|
Set of default supported desired capabilities.
|
||||||
|
|
||||||
|
Use this as a starting point for creating a desired capabilities object for
|
||||||
|
requesting remote webdrivers for connecting to selenium server or selenium grid.
|
||||||
|
|
||||||
|
Usage Example::
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
|
||||||
|
selenium_grid_url = "http://198.0.0.1:4444/wd/hub"
|
||||||
|
|
||||||
|
# Create a desired capabilities object as a starting point.
|
||||||
|
capabilities = DesiredCapabilities.FIREFOX.copy()
|
||||||
|
capabilities['platform'] = "WINDOWS"
|
||||||
|
capabilities['version'] = "10"
|
||||||
|
|
||||||
|
# Instantiate an instance of Remote WebDriver with the desired capabilities.
|
||||||
|
driver = webdriver.Remote(desired_capabilities=capabilities,
|
||||||
|
command_executor=selenium_grid_url)
|
||||||
|
|
||||||
|
Note: Always use '.copy()' on the DesiredCapabilities object to avoid the side
|
||||||
|
effects of altering the Global class instance.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
FIREFOX = {
|
||||||
|
"browserName": "firefox",
|
||||||
|
"acceptInsecureCerts": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERNETEXPLORER = {
|
||||||
|
"browserName": "internet explorer",
|
||||||
|
"version": "",
|
||||||
|
"platform": "WINDOWS",
|
||||||
|
}
|
||||||
|
|
||||||
|
EDGE = {
|
||||||
|
"browserName": "MicrosoftEdge",
|
||||||
|
"version": "",
|
||||||
|
"platform": "WINDOWS"
|
||||||
|
}
|
||||||
|
|
||||||
|
CHROME = {
|
||||||
|
"browserName": "chrome",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANY",
|
||||||
|
}
|
||||||
|
|
||||||
|
OPERA = {
|
||||||
|
"browserName": "opera",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANY",
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFARI = {
|
||||||
|
"browserName": "safari",
|
||||||
|
"version": "",
|
||||||
|
"platform": "MAC",
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLUNIT = {
|
||||||
|
"browserName": "htmlunit",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANY",
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLUNITWITHJS = {
|
||||||
|
"browserName": "htmlunit",
|
||||||
|
"version": "firefox",
|
||||||
|
"platform": "ANY",
|
||||||
|
"javascriptEnabled": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
IPHONE = {
|
||||||
|
"browserName": "iPhone",
|
||||||
|
"version": "",
|
||||||
|
"platform": "MAC",
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAD = {
|
||||||
|
"browserName": "iPad",
|
||||||
|
"version": "",
|
||||||
|
"platform": "MAC",
|
||||||
|
}
|
||||||
|
|
||||||
|
ANDROID = {
|
||||||
|
"browserName": "android",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANDROID",
|
||||||
|
}
|
||||||
|
|
||||||
|
PHANTOMJS = {
|
||||||
|
"browserName": "phantomjs",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANY",
|
||||||
|
"javascriptEnabled": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
WEBKITGTK = {
|
||||||
|
"browserName": "MiniBrowser",
|
||||||
|
"version": "",
|
||||||
|
"platform": "ANY",
|
||||||
|
}
|
16
selenium/webdriver/common/html5/__init__.py
Normal file
16
selenium/webdriver/common/html5/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
48
selenium/webdriver/common/html5/application_cache.py
Normal file
48
selenium/webdriver/common/html5/application_cache.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The ApplicationCache implementaion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationCache(object):
|
||||||
|
|
||||||
|
UNCACHED = 0
|
||||||
|
IDLE = 1
|
||||||
|
CHECKING = 2
|
||||||
|
DOWNLOADING = 3
|
||||||
|
UPDATE_READY = 4
|
||||||
|
OBSOLETE = 5
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
"""
|
||||||
|
Creates a new Aplication Cache.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- driver: The WebDriver instance which performs user actions.
|
||||||
|
"""
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
"""
|
||||||
|
Returns a current status of application cache.
|
||||||
|
"""
|
||||||
|
return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value']
|
96
selenium/webdriver/common/keys.py
Normal file
96
selenium/webdriver/common/keys.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Keys implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class Keys(object):
|
||||||
|
"""
|
||||||
|
Set of special keys codes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NULL = '\ue000'
|
||||||
|
CANCEL = '\ue001' # ^break
|
||||||
|
HELP = '\ue002'
|
||||||
|
BACKSPACE = '\ue003'
|
||||||
|
BACK_SPACE = BACKSPACE
|
||||||
|
TAB = '\ue004'
|
||||||
|
CLEAR = '\ue005'
|
||||||
|
RETURN = '\ue006'
|
||||||
|
ENTER = '\ue007'
|
||||||
|
SHIFT = '\ue008'
|
||||||
|
LEFT_SHIFT = SHIFT
|
||||||
|
CONTROL = '\ue009'
|
||||||
|
LEFT_CONTROL = CONTROL
|
||||||
|
ALT = '\ue00a'
|
||||||
|
LEFT_ALT = ALT
|
||||||
|
PAUSE = '\ue00b'
|
||||||
|
ESCAPE = '\ue00c'
|
||||||
|
SPACE = '\ue00d'
|
||||||
|
PAGE_UP = '\ue00e'
|
||||||
|
PAGE_DOWN = '\ue00f'
|
||||||
|
END = '\ue010'
|
||||||
|
HOME = '\ue011'
|
||||||
|
LEFT = '\ue012'
|
||||||
|
ARROW_LEFT = LEFT
|
||||||
|
UP = '\ue013'
|
||||||
|
ARROW_UP = UP
|
||||||
|
RIGHT = '\ue014'
|
||||||
|
ARROW_RIGHT = RIGHT
|
||||||
|
DOWN = '\ue015'
|
||||||
|
ARROW_DOWN = DOWN
|
||||||
|
INSERT = '\ue016'
|
||||||
|
DELETE = '\ue017'
|
||||||
|
SEMICOLON = '\ue018'
|
||||||
|
EQUALS = '\ue019'
|
||||||
|
|
||||||
|
NUMPAD0 = '\ue01a' # number pad keys
|
||||||
|
NUMPAD1 = '\ue01b'
|
||||||
|
NUMPAD2 = '\ue01c'
|
||||||
|
NUMPAD3 = '\ue01d'
|
||||||
|
NUMPAD4 = '\ue01e'
|
||||||
|
NUMPAD5 = '\ue01f'
|
||||||
|
NUMPAD6 = '\ue020'
|
||||||
|
NUMPAD7 = '\ue021'
|
||||||
|
NUMPAD8 = '\ue022'
|
||||||
|
NUMPAD9 = '\ue023'
|
||||||
|
MULTIPLY = '\ue024'
|
||||||
|
ADD = '\ue025'
|
||||||
|
SEPARATOR = '\ue026'
|
||||||
|
SUBTRACT = '\ue027'
|
||||||
|
DECIMAL = '\ue028'
|
||||||
|
DIVIDE = '\ue029'
|
||||||
|
|
||||||
|
F1 = '\ue031' # function keys
|
||||||
|
F2 = '\ue032'
|
||||||
|
F3 = '\ue033'
|
||||||
|
F4 = '\ue034'
|
||||||
|
F5 = '\ue035'
|
||||||
|
F6 = '\ue036'
|
||||||
|
F7 = '\ue037'
|
||||||
|
F8 = '\ue038'
|
||||||
|
F9 = '\ue039'
|
||||||
|
F10 = '\ue03a'
|
||||||
|
F11 = '\ue03b'
|
||||||
|
F12 = '\ue03c'
|
||||||
|
|
||||||
|
META = '\ue03d'
|
||||||
|
COMMAND = '\ue03d'
|
74
selenium/webdriver/common/options.py
Normal file
74
selenium/webdriver/common/options.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class BaseOptions(object):
|
||||||
|
"""
|
||||||
|
Base class for individual browser options
|
||||||
|
"""
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._caps = self.default_capabilities
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capabilities(self):
|
||||||
|
return self._caps
|
||||||
|
|
||||||
|
def set_capability(self, name, value):
|
||||||
|
""" Sets a capability """
|
||||||
|
self._caps[name] = value
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def to_capabilities(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def default_capabilities(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class ArgOptions(BaseOptions):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ArgOptions, self).__init__()
|
||||||
|
self._arguments = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arguments(self):
|
||||||
|
"""
|
||||||
|
:Returns: A list of arguments needed for the browser
|
||||||
|
"""
|
||||||
|
return self._arguments
|
||||||
|
|
||||||
|
def add_argument(self, argument):
|
||||||
|
"""
|
||||||
|
Adds an argument to the list
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- Sets the arguments
|
||||||
|
"""
|
||||||
|
if argument:
|
||||||
|
self._arguments.append(argument)
|
||||||
|
else:
|
||||||
|
raise ValueError('argument can not be null')
|
||||||
|
|
||||||
|
def to_capabilities(self):
|
||||||
|
return self._caps
|
358
selenium/webdriver/common/proxy.py
Normal file
358
selenium/webdriver/common/proxy.py
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Proxy implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyTypeFactory:
|
||||||
|
"""
|
||||||
|
Factory for proxy types.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make(ff_value, string):
|
||||||
|
return {'ff_value': ff_value, 'string': string}
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyType:
|
||||||
|
"""
|
||||||
|
Set of possible types of proxy.
|
||||||
|
|
||||||
|
Each proxy type has 2 properties:
|
||||||
|
'ff_value' is value of Firefox profile preference,
|
||||||
|
'string' is id of proxy type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows).
|
||||||
|
MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy).
|
||||||
|
PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL.
|
||||||
|
RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used.
|
||||||
|
AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD).
|
||||||
|
SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux).
|
||||||
|
UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') # Not initialized (for internal use).
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, value):
|
||||||
|
if isinstance(value, dict) and 'string' in value:
|
||||||
|
value = value['string']
|
||||||
|
value = str(value).upper()
|
||||||
|
for attr in dir(cls):
|
||||||
|
attr_value = getattr(cls, attr)
|
||||||
|
if isinstance(attr_value, dict) and \
|
||||||
|
'string' in attr_value and \
|
||||||
|
attr_value['string'] is not None and \
|
||||||
|
attr_value['string'] == value:
|
||||||
|
return attr_value
|
||||||
|
raise Exception("No proxy type is found for %s" % (value))
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(object):
|
||||||
|
"""
|
||||||
|
Proxy contains information about proxy type and necessary proxy settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxyType = ProxyType.UNSPECIFIED
|
||||||
|
autodetect = False
|
||||||
|
ftpProxy = ''
|
||||||
|
httpProxy = ''
|
||||||
|
noProxy = ''
|
||||||
|
proxyAutoconfigUrl = ''
|
||||||
|
sslProxy = ''
|
||||||
|
socksProxy = ''
|
||||||
|
socksUsername = ''
|
||||||
|
socksPassword = ''
|
||||||
|
socksVersion = None
|
||||||
|
|
||||||
|
def __init__(self, raw=None):
|
||||||
|
"""
|
||||||
|
Creates a new Proxy.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- raw: raw proxy data. If None, default class values are used.
|
||||||
|
"""
|
||||||
|
if raw is not None:
|
||||||
|
if 'proxyType' in raw and raw['proxyType'] is not None:
|
||||||
|
self.proxy_type = ProxyType.load(raw['proxyType'])
|
||||||
|
if 'ftpProxy' in raw and raw['ftpProxy'] is not None:
|
||||||
|
self.ftp_proxy = raw['ftpProxy']
|
||||||
|
if 'httpProxy' in raw and raw['httpProxy'] is not None:
|
||||||
|
self.http_proxy = raw['httpProxy']
|
||||||
|
if 'noProxy' in raw and raw['noProxy'] is not None:
|
||||||
|
self.no_proxy = raw['noProxy']
|
||||||
|
if 'proxyAutoconfigUrl' in raw and raw['proxyAutoconfigUrl'] is not None:
|
||||||
|
self.proxy_autoconfig_url = raw['proxyAutoconfigUrl']
|
||||||
|
if 'sslProxy' in raw and raw['sslProxy'] is not None:
|
||||||
|
self.sslProxy = raw['sslProxy']
|
||||||
|
if 'autodetect' in raw and raw['autodetect'] is not None:
|
||||||
|
self.auto_detect = raw['autodetect']
|
||||||
|
if 'socksProxy' in raw and raw['socksProxy'] is not None:
|
||||||
|
self.socks_proxy = raw['socksProxy']
|
||||||
|
if 'socksUsername' in raw and raw['socksUsername'] is not None:
|
||||||
|
self.socks_username = raw['socksUsername']
|
||||||
|
if 'socksPassword' in raw and raw['socksPassword'] is not None:
|
||||||
|
self.socks_password = raw['socksPassword']
|
||||||
|
if 'socksVersion' in raw and raw['socksVersion'] is not None:
|
||||||
|
self.socks_version = raw['socksVersion']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proxy_type(self):
|
||||||
|
"""
|
||||||
|
Returns proxy type as `ProxyType`.
|
||||||
|
"""
|
||||||
|
return self.proxyType
|
||||||
|
|
||||||
|
@proxy_type.setter
|
||||||
|
def proxy_type(self, value):
|
||||||
|
"""
|
||||||
|
Sets proxy type.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The proxy type.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(value)
|
||||||
|
self.proxyType = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_detect(self):
|
||||||
|
"""
|
||||||
|
Returns autodetect setting.
|
||||||
|
"""
|
||||||
|
return self.autodetect
|
||||||
|
|
||||||
|
@auto_detect.setter
|
||||||
|
def auto_detect(self, value):
|
||||||
|
"""
|
||||||
|
Sets autodetect setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The autodetect value.
|
||||||
|
"""
|
||||||
|
if isinstance(value, bool):
|
||||||
|
if self.autodetect is not value:
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.AUTODETECT)
|
||||||
|
self.proxyType = ProxyType.AUTODETECT
|
||||||
|
self.autodetect = value
|
||||||
|
else:
|
||||||
|
raise ValueError("Autodetect proxy value needs to be a boolean")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ftp_proxy(self):
|
||||||
|
"""
|
||||||
|
Returns ftp proxy setting.
|
||||||
|
"""
|
||||||
|
return self.ftpProxy
|
||||||
|
|
||||||
|
@ftp_proxy.setter
|
||||||
|
def ftp_proxy(self, value):
|
||||||
|
"""
|
||||||
|
Sets ftp proxy setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The ftp proxy value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.ftpProxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def http_proxy(self):
|
||||||
|
"""
|
||||||
|
Returns http proxy setting.
|
||||||
|
"""
|
||||||
|
return self.httpProxy
|
||||||
|
|
||||||
|
@http_proxy.setter
|
||||||
|
def http_proxy(self, value):
|
||||||
|
"""
|
||||||
|
Sets http proxy setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The http proxy value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.httpProxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def no_proxy(self):
|
||||||
|
"""
|
||||||
|
Returns noproxy setting.
|
||||||
|
"""
|
||||||
|
return self.noProxy
|
||||||
|
|
||||||
|
@no_proxy.setter
|
||||||
|
def no_proxy(self, value):
|
||||||
|
"""
|
||||||
|
Sets noproxy setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The noproxy value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.noProxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proxy_autoconfig_url(self):
|
||||||
|
"""
|
||||||
|
Returns proxy autoconfig url setting.
|
||||||
|
"""
|
||||||
|
return self.proxyAutoconfigUrl
|
||||||
|
|
||||||
|
@proxy_autoconfig_url.setter
|
||||||
|
def proxy_autoconfig_url(self, value):
|
||||||
|
"""
|
||||||
|
Sets proxy autoconfig url setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The proxy autoconfig url value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.PAC)
|
||||||
|
self.proxyType = ProxyType.PAC
|
||||||
|
self.proxyAutoconfigUrl = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ssl_proxy(self):
|
||||||
|
"""
|
||||||
|
Returns https proxy setting.
|
||||||
|
"""
|
||||||
|
return self.sslProxy
|
||||||
|
|
||||||
|
@ssl_proxy.setter
|
||||||
|
def ssl_proxy(self, value):
|
||||||
|
"""
|
||||||
|
Sets https proxy setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The https proxy value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.sslProxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def socks_proxy(self):
|
||||||
|
"""
|
||||||
|
Returns socks proxy setting.
|
||||||
|
"""
|
||||||
|
return self.socksProxy
|
||||||
|
|
||||||
|
@socks_proxy.setter
|
||||||
|
def socks_proxy(self, value):
|
||||||
|
"""
|
||||||
|
Sets socks proxy setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The socks proxy value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.socksProxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def socks_username(self):
|
||||||
|
"""
|
||||||
|
Returns socks proxy username setting.
|
||||||
|
"""
|
||||||
|
return self.socksUsername
|
||||||
|
|
||||||
|
@socks_username.setter
|
||||||
|
def socks_username(self, value):
|
||||||
|
"""
|
||||||
|
Sets socks proxy username setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The socks proxy username value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.socksUsername = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def socks_password(self):
|
||||||
|
"""
|
||||||
|
Returns socks proxy password setting.
|
||||||
|
"""
|
||||||
|
return self.socksPassword
|
||||||
|
|
||||||
|
@socks_password.setter
|
||||||
|
def socks_password(self, value):
|
||||||
|
"""
|
||||||
|
Sets socks proxy password setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The socks proxy password value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.socksPassword = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def socks_version(self):
|
||||||
|
"""
|
||||||
|
Returns socks proxy version setting.
|
||||||
|
"""
|
||||||
|
return self.socksVersion
|
||||||
|
|
||||||
|
@socks_version.setter
|
||||||
|
def socks_version(self, value):
|
||||||
|
"""
|
||||||
|
Sets socks proxy version setting.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- value: The socks proxy version value.
|
||||||
|
"""
|
||||||
|
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||||
|
self.proxyType = ProxyType.MANUAL
|
||||||
|
self.socksVersion = value
|
||||||
|
|
||||||
|
def _verify_proxy_type_compatibility(self, compatibleProxy):
|
||||||
|
if self.proxyType != ProxyType.UNSPECIFIED and self.proxyType != compatibleProxy:
|
||||||
|
raise Exception(" Specified proxy type (%s) not compatible with current setting (%s)" % (compatibleProxy, self.proxyType))
|
||||||
|
|
||||||
|
def add_to_capabilities(self, capabilities):
|
||||||
|
"""
|
||||||
|
Adds proxy information as capability in specified capabilities.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- capabilities: The capabilities to which proxy will be added.
|
||||||
|
"""
|
||||||
|
proxy_caps = {}
|
||||||
|
proxy_caps['proxyType'] = self.proxyType['string']
|
||||||
|
if self.autodetect:
|
||||||
|
proxy_caps['autodetect'] = self.autodetect
|
||||||
|
if self.ftpProxy:
|
||||||
|
proxy_caps['ftpProxy'] = self.ftpProxy
|
||||||
|
if self.httpProxy:
|
||||||
|
proxy_caps['httpProxy'] = self.httpProxy
|
||||||
|
if self.proxyAutoconfigUrl:
|
||||||
|
proxy_caps['proxyAutoconfigUrl'] = self.proxyAutoconfigUrl
|
||||||
|
if self.sslProxy:
|
||||||
|
proxy_caps['sslProxy'] = self.sslProxy
|
||||||
|
if self.noProxy:
|
||||||
|
proxy_caps['noProxy'] = self.noProxy
|
||||||
|
if self.socksProxy:
|
||||||
|
proxy_caps['socksProxy'] = self.socksProxy
|
||||||
|
if self.socksUsername:
|
||||||
|
proxy_caps['socksUsername'] = self.socksUsername
|
||||||
|
if self.socksPassword:
|
||||||
|
proxy_caps['socksPassword'] = self.socksPassword
|
||||||
|
if self.socksVersion:
|
||||||
|
proxy_caps['socksVersion'] = self.socksVersion
|
||||||
|
capabilities['proxy'] = proxy_caps
|
178
selenium/webdriver/common/service.py
Normal file
178
selenium/webdriver/common/service.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
from subprocess import PIPE
|
||||||
|
import time
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
from selenium.webdriver.common import utils
|
||||||
|
|
||||||
|
try:
|
||||||
|
from subprocess import DEVNULL
|
||||||
|
_HAS_NATIVE_DEVNULL = True
|
||||||
|
except ImportError:
|
||||||
|
DEVNULL = -3
|
||||||
|
_HAS_NATIVE_DEVNULL = False
|
||||||
|
|
||||||
|
|
||||||
|
class Service(object):
|
||||||
|
|
||||||
|
def __init__(self, executable, port=0, log_file=DEVNULL, env=None, start_error_message=""):
|
||||||
|
self.path = executable
|
||||||
|
|
||||||
|
self.port = port
|
||||||
|
if self.port == 0:
|
||||||
|
self.port = utils.free_port()
|
||||||
|
|
||||||
|
if not _HAS_NATIVE_DEVNULL and log_file == DEVNULL:
|
||||||
|
log_file = open(os.devnull, 'wb')
|
||||||
|
|
||||||
|
self.start_error_message = start_error_message
|
||||||
|
self.log_file = log_file
|
||||||
|
self.env = env or os.environ
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_url(self):
|
||||||
|
"""
|
||||||
|
Gets the url of the Service
|
||||||
|
"""
|
||||||
|
return "http://%s" % utils.join_host_port('localhost', self.port)
|
||||||
|
|
||||||
|
def command_line_args(self):
|
||||||
|
raise NotImplemented("This method needs to be implemented in a sub class")
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the Service.
|
||||||
|
|
||||||
|
:Exceptions:
|
||||||
|
- WebDriverException : Raised either when it can't start the service
|
||||||
|
or when it can't connect to the service
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cmd = [self.path]
|
||||||
|
cmd.extend(self.command_line_args())
|
||||||
|
self.process = subprocess.Popen(cmd, env=self.env,
|
||||||
|
close_fds=platform.system() != 'Windows',
|
||||||
|
stdout=self.log_file,
|
||||||
|
stderr=self.log_file,
|
||||||
|
stdin=PIPE)
|
||||||
|
except TypeError:
|
||||||
|
raise
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno == errno.ENOENT:
|
||||||
|
raise WebDriverException(
|
||||||
|
"'%s' executable needs to be in PATH. %s" % (
|
||||||
|
os.path.basename(self.path), self.start_error_message)
|
||||||
|
)
|
||||||
|
elif err.errno == errno.EACCES:
|
||||||
|
raise WebDriverException(
|
||||||
|
"'%s' executable may have wrong permissions. %s" % (
|
||||||
|
os.path.basename(self.path), self.start_error_message)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise WebDriverException(
|
||||||
|
"The executable %s needs to be available in the path. %s\n%s" %
|
||||||
|
(os.path.basename(self.path), self.start_error_message, str(e)))
|
||||||
|
count = 0
|
||||||
|
while True:
|
||||||
|
self.assert_process_still_running()
|
||||||
|
if self.is_connectable():
|
||||||
|
break
|
||||||
|
count += 1
|
||||||
|
time.sleep(1)
|
||||||
|
if count == 30:
|
||||||
|
raise WebDriverException("Can not connect to the Service %s" % self.path)
|
||||||
|
|
||||||
|
def assert_process_still_running(self):
|
||||||
|
return_code = self.process.poll()
|
||||||
|
if return_code is not None:
|
||||||
|
raise WebDriverException(
|
||||||
|
'Service %s unexpectedly exited. Status code was: %s'
|
||||||
|
% (self.path, return_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_connectable(self):
|
||||||
|
return utils.is_connectable(self.port)
|
||||||
|
|
||||||
|
def send_remote_shutdown_command(self):
|
||||||
|
try:
|
||||||
|
from urllib import request as url_request
|
||||||
|
URLError = url_request.URLError
|
||||||
|
except ImportError:
|
||||||
|
import urllib2 as url_request
|
||||||
|
import urllib2
|
||||||
|
URLError = urllib2.URLError
|
||||||
|
|
||||||
|
try:
|
||||||
|
url_request.urlopen("%s/shutdown" % self.service_url)
|
||||||
|
except URLError:
|
||||||
|
return
|
||||||
|
|
||||||
|
for x in range(30):
|
||||||
|
if not self.is_connectable():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stops the service.
|
||||||
|
"""
|
||||||
|
if self.log_file != PIPE and not (self.log_file == DEVNULL and _HAS_NATIVE_DEVNULL):
|
||||||
|
try:
|
||||||
|
self.log_file.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.process is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.send_remote_shutdown_command()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.process:
|
||||||
|
for stream in [self.process.stdin,
|
||||||
|
self.process.stdout,
|
||||||
|
self.process.stderr]:
|
||||||
|
try:
|
||||||
|
stream.close()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.process.terminate()
|
||||||
|
self.process.wait()
|
||||||
|
self.process.kill()
|
||||||
|
self.process = None
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
# `subprocess.Popen` doesn't send signal on `__del__`;
|
||||||
|
# so we attempt to close the launched process when `__del__`
|
||||||
|
# is triggered.
|
||||||
|
try:
|
||||||
|
self.stop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
192
selenium/webdriver/common/touch_actions.py
Normal file
192
selenium/webdriver/common/touch_actions.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Touch Actions implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
|
||||||
|
|
||||||
|
class TouchActions(object):
|
||||||
|
"""
|
||||||
|
Generate touch actions. Works like ActionChains; actions are stored in the
|
||||||
|
TouchActions object and are fired with perform().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
"""
|
||||||
|
Creates a new TouchActions object.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- driver: The WebDriver instance which performs user actions.
|
||||||
|
It should be with touchscreen enabled.
|
||||||
|
"""
|
||||||
|
self._driver = driver
|
||||||
|
self._actions = []
|
||||||
|
|
||||||
|
def perform(self):
|
||||||
|
"""
|
||||||
|
Performs all stored actions.
|
||||||
|
"""
|
||||||
|
for action in self._actions:
|
||||||
|
action()
|
||||||
|
|
||||||
|
def tap(self, on_element):
|
||||||
|
"""
|
||||||
|
Taps on a given element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to tap.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.SINGLE_TAP, {'element': on_element.id}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def double_tap(self, on_element):
|
||||||
|
"""
|
||||||
|
Double taps on a given element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to tap.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.DOUBLE_TAP, {'element': on_element.id}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def tap_and_hold(self, xcoord, ycoord):
|
||||||
|
"""
|
||||||
|
Touch down at given coordinates.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xcoord: X Coordinate to touch down.
|
||||||
|
- ycoord: Y Coordinate to touch down.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.TOUCH_DOWN, {
|
||||||
|
'x': int(xcoord),
|
||||||
|
'y': int(ycoord)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move(self, xcoord, ycoord):
|
||||||
|
"""
|
||||||
|
Move held tap to specified location.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xcoord: X Coordinate to move.
|
||||||
|
- ycoord: Y Coordinate to move.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.TOUCH_MOVE, {
|
||||||
|
'x': int(xcoord),
|
||||||
|
'y': int(ycoord)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def release(self, xcoord, ycoord):
|
||||||
|
"""
|
||||||
|
Release previously issued tap 'and hold' command at specified location.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xcoord: X Coordinate to release.
|
||||||
|
- ycoord: Y Coordinate to release.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.TOUCH_UP, {
|
||||||
|
'x': int(xcoord),
|
||||||
|
'y': int(ycoord)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scroll(self, xoffset, yoffset):
|
||||||
|
"""
|
||||||
|
Touch and scroll, moving by xoffset and yoffset.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xoffset: X offset to scroll to.
|
||||||
|
- yoffset: Y offset to scroll to.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.TOUCH_SCROLL, {
|
||||||
|
'xoffset': int(xoffset),
|
||||||
|
'yoffset': int(yoffset)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scroll_from_element(self, on_element, xoffset, yoffset):
|
||||||
|
"""
|
||||||
|
Touch and scroll starting at on_element, moving by xoffset and yoffset.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element where scroll starts.
|
||||||
|
- xoffset: X offset to scroll to.
|
||||||
|
- yoffset: Y offset to scroll to.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.TOUCH_SCROLL, {
|
||||||
|
'element': on_element.id,
|
||||||
|
'xoffset': int(xoffset),
|
||||||
|
'yoffset': int(yoffset)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def long_press(self, on_element):
|
||||||
|
"""
|
||||||
|
Long press on an element.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: The element to long press.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.LONG_PRESS, {'element': on_element.id}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def flick(self, xspeed, yspeed):
|
||||||
|
"""
|
||||||
|
Flicks, starting anywhere on the screen.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- xspeed: The X speed in pixels per second.
|
||||||
|
- yspeed: The Y speed in pixels per second.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.FLICK, {
|
||||||
|
'xspeed': int(xspeed),
|
||||||
|
'yspeed': int(yspeed)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def flick_element(self, on_element, xoffset, yoffset, speed):
|
||||||
|
"""
|
||||||
|
Flick starting at on_element, and moving by the xoffset and yoffset
|
||||||
|
with specified speed.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- on_element: Flick will start at center of element.
|
||||||
|
- xoffset: X offset to flick to.
|
||||||
|
- yoffset: Y offset to flick to.
|
||||||
|
- speed: Pixels per second to flick.
|
||||||
|
"""
|
||||||
|
self._actions.append(lambda: self._driver.execute(
|
||||||
|
Command.FLICK, {
|
||||||
|
'element': on_element.id,
|
||||||
|
'xoffset': int(xoffset),
|
||||||
|
'yoffset': int(yoffset),
|
||||||
|
'speed': int(speed)}))
|
||||||
|
return self
|
||||||
|
|
||||||
|
# Context manager so TouchActions can be used in a 'with .. as' statements.
|
||||||
|
def __enter__(self):
|
||||||
|
return self # Return created instance of self.
|
||||||
|
|
||||||
|
def __exit__(self, _type, _value, _traceback):
|
||||||
|
pass # Do nothing, does not require additional cleanup.
|
155
selenium/webdriver/common/utils.py
Normal file
155
selenium/webdriver/common/utils.py
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The Utils methods.
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python 2
|
||||||
|
basestring
|
||||||
|
_is_connectable_exceptions = (socket.error,)
|
||||||
|
except NameError:
|
||||||
|
# Python 3
|
||||||
|
basestring = str
|
||||||
|
_is_connectable_exceptions = (socket.error, ConnectionResetError)
|
||||||
|
|
||||||
|
|
||||||
|
def free_port():
|
||||||
|
"""
|
||||||
|
Determines a free port using sockets.
|
||||||
|
"""
|
||||||
|
free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
free_socket.bind(('0.0.0.0', 0))
|
||||||
|
free_socket.listen(5)
|
||||||
|
port = free_socket.getsockname()[1]
|
||||||
|
free_socket.close()
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def find_connectable_ip(host, port=None):
|
||||||
|
"""Resolve a hostname to an IP, preferring IPv4 addresses.
|
||||||
|
|
||||||
|
We prefer IPv4 so that we don't change behavior from previous IPv4-only
|
||||||
|
implementations, and because some drivers (e.g., FirefoxDriver) do not
|
||||||
|
support IPv6 connections.
|
||||||
|
|
||||||
|
If the optional port number is provided, only IPs that listen on the given
|
||||||
|
port are considered.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- host - A hostname.
|
||||||
|
- port - Optional port number.
|
||||||
|
|
||||||
|
:Returns:
|
||||||
|
A single IP address, as a string. If any IPv4 address is found, one is
|
||||||
|
returned. Otherwise, if any IPv6 address is found, one is returned. If
|
||||||
|
neither, then None is returned.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
addrinfos = socket.getaddrinfo(host, None)
|
||||||
|
except socket.gaierror:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ip = None
|
||||||
|
for family, _, _, _, sockaddr in addrinfos:
|
||||||
|
connectable = True
|
||||||
|
if port:
|
||||||
|
connectable = is_connectable(port, sockaddr[0])
|
||||||
|
|
||||||
|
if connectable and family == socket.AF_INET:
|
||||||
|
return sockaddr[0]
|
||||||
|
if connectable and not ip and family == socket.AF_INET6:
|
||||||
|
ip = sockaddr[0]
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def join_host_port(host, port):
|
||||||
|
"""Joins a hostname and port together.
|
||||||
|
|
||||||
|
This is a minimal implementation intended to cope with IPv6 literals. For
|
||||||
|
example, _join_host_port('::1', 80) == '[::1]:80'.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- host - A hostname.
|
||||||
|
- port - An integer port.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if ':' in host and not host.startswith('['):
|
||||||
|
return '[%s]:%d' % (host, port)
|
||||||
|
return '%s:%d' % (host, port)
|
||||||
|
|
||||||
|
|
||||||
|
def is_connectable(port, host="localhost"):
|
||||||
|
"""
|
||||||
|
Tries to connect to the server at port to see if it is running.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- port - The port to connect.
|
||||||
|
"""
|
||||||
|
socket_ = None
|
||||||
|
try:
|
||||||
|
socket_ = socket.create_connection((host, port), 1)
|
||||||
|
result = True
|
||||||
|
except _is_connectable_exceptions:
|
||||||
|
result = False
|
||||||
|
finally:
|
||||||
|
if socket_:
|
||||||
|
socket_.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_url_connectable(port):
|
||||||
|
"""
|
||||||
|
Tries to connect to the HTTP server at /status path
|
||||||
|
and specified port to see if it responds successfully.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- port - The port to connect.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from urllib import request as url_request
|
||||||
|
except ImportError:
|
||||||
|
import urllib2 as url_request
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = url_request.urlopen("http://127.0.0.1:%s/status" % port)
|
||||||
|
if res.getcode() == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def keys_to_typing(value):
|
||||||
|
"""Processes the values that will be typed in the element."""
|
||||||
|
typing = []
|
||||||
|
for val in value:
|
||||||
|
if isinstance(val, Keys):
|
||||||
|
typing.append(val)
|
||||||
|
elif isinstance(val, int):
|
||||||
|
val = str(val)
|
||||||
|
for i in range(len(val)):
|
||||||
|
typing.append(val[i])
|
||||||
|
else:
|
||||||
|
for i in range(len(val)):
|
||||||
|
typing.append(val[i])
|
||||||
|
return typing
|
29
selenium/webdriver/common/window.py
Normal file
29
selenium/webdriver/common/window.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
The WindowTypes implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class WindowTypes(object):
|
||||||
|
"""Set of supported window types."""
|
||||||
|
|
||||||
|
TAB = 'tab'
|
||||||
|
WINDOW = 'window'
|
16
selenium/webdriver/edge/__init__.py
Normal file
16
selenium/webdriver/edge/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
51
selenium/webdriver/edge/options.py
Normal file
51
selenium/webdriver/edge/options.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
from selenium.webdriver.common.options import BaseOptions
|
||||||
|
|
||||||
|
|
||||||
|
class Options(BaseOptions):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Options, self).__init__()
|
||||||
|
self._page_load_strategy = "normal"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_load_strategy(self):
|
||||||
|
return self._page_load_strategy
|
||||||
|
|
||||||
|
@page_load_strategy.setter
|
||||||
|
def page_load_strategy(self, value):
|
||||||
|
if value not in ['normal', 'eager', 'none']:
|
||||||
|
raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
|
||||||
|
self._page_load_strategy = value
|
||||||
|
|
||||||
|
def to_capabilities(self):
|
||||||
|
"""
|
||||||
|
Creates a capabilities with all the options that have been set and
|
||||||
|
|
||||||
|
:Returns: A dictionary with everything
|
||||||
|
"""
|
||||||
|
caps = self._caps
|
||||||
|
caps['pageLoadStrategy'] = self._page_load_strategy
|
||||||
|
|
||||||
|
return caps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_capabilities(self):
|
||||||
|
return DesiredCapabilities.EDGE.copy()
|
57
selenium/webdriver/edge/service.py
Normal file
57
selenium/webdriver/edge/service.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common import service
|
||||||
|
|
||||||
|
|
||||||
|
class Service(service.Service):
|
||||||
|
|
||||||
|
def __init__(self, executable_path, port=0, verbose=False, log_path=None):
|
||||||
|
"""
|
||||||
|
Creates a new instance of the EdgeDriver service.
|
||||||
|
|
||||||
|
EdgeDriver provides an interface for Microsoft WebDriver to use
|
||||||
|
with Microsoft Edge.
|
||||||
|
|
||||||
|
:param executable_path: Path to the Microsoft WebDriver binary.
|
||||||
|
:param port: Run the remote service on a specified port.
|
||||||
|
Defaults to 0, which binds to a random open port of the
|
||||||
|
system's choosing.
|
||||||
|
:verbose: Whether to make the webdriver more verbose (passes the
|
||||||
|
--verbose option to the binary). Defaults to False.
|
||||||
|
:param log_path: Optional path for the webdriver binary to log to.
|
||||||
|
Defaults to None which disables logging.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.service_args = []
|
||||||
|
if verbose:
|
||||||
|
self.service_args.append("--verbose")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"executable": executable_path,
|
||||||
|
"port": port,
|
||||||
|
"start_error_message": "Please download from https://go.microsoft.com/fwlink/?LinkId=619687"
|
||||||
|
}
|
||||||
|
|
||||||
|
if log_path:
|
||||||
|
params["log_file"] = open(log_path, "a+")
|
||||||
|
|
||||||
|
service.Service.__init__(self, **params)
|
||||||
|
|
||||||
|
def command_line_args(self):
|
||||||
|
return ["--port=%d" % self.port] + self.service_args
|
92
selenium/webdriver/edge/webdriver.py
Normal file
92
selenium/webdriver/edge/webdriver.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
from selenium.webdriver.edge.service import Service
|
||||||
|
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||||
|
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||||
|
|
||||||
|
DEFAULT_PORT = 0
|
||||||
|
DEFAULT_SERVICE_LOG_PATH = None
|
||||||
|
|
||||||
|
|
||||||
|
class WebDriver(RemoteWebDriver):
|
||||||
|
|
||||||
|
def __init__(self, executable_path='MicrosoftWebDriver.exe',
|
||||||
|
capabilities=None, port=DEFAULT_PORT, verbose=False,
|
||||||
|
service_log_path=None, log_path=DEFAULT_SERVICE_LOG_PATH,
|
||||||
|
service=None, options=None, keep_alive=False):
|
||||||
|
"""
|
||||||
|
Creates a new instance of the chrome driver.
|
||||||
|
|
||||||
|
Starts the service and then creates new instance of chrome driver.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
|
||||||
|
- capabilities - Dictionary object with non-browser specific
|
||||||
|
capabilities only, such as "proxy" or "loggingPref".
|
||||||
|
- port - port you would like the service to run, if left as 0, a free port will be found.
|
||||||
|
- verbose - whether to set verbose logging in the service
|
||||||
|
- service_log_path - Where to log information from the driver.
|
||||||
|
- keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive.
|
||||||
|
"""
|
||||||
|
if port != DEFAULT_PORT:
|
||||||
|
warnings.warn('port has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
|
||||||
|
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if capabilities is not None:
|
||||||
|
warnings.warn('capabilities has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if service_log_path != DEFAULT_SERVICE_LOG_PATH:
|
||||||
|
warnings.warn('service_log_path has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if verbose:
|
||||||
|
warnings.warn('verbose has been deprecated, please pass in a Service object',
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
|
if service:
|
||||||
|
self.service = service
|
||||||
|
else:
|
||||||
|
self.service = Service(executable_path, port=self.port, verbose=verbose,
|
||||||
|
log_path=service_log_path)
|
||||||
|
self.service.start()
|
||||||
|
|
||||||
|
if capabilities is None:
|
||||||
|
capabilities = DesiredCapabilities.EDGE
|
||||||
|
|
||||||
|
RemoteWebDriver.__init__(
|
||||||
|
self,
|
||||||
|
command_executor=RemoteConnection(self.service.service_url,
|
||||||
|
resolve_ip=False,
|
||||||
|
keep_alive=keep_alive),
|
||||||
|
desired_capabilities=capabilities)
|
||||||
|
self._is_remote = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edge_service(self):
|
||||||
|
warnings.warn("'edge_service' has been deprecated, please use 'service'",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
return self.service
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
RemoteWebDriver.quit(self)
|
||||||
|
self.service.stop()
|
16
selenium/webdriver/firefox/__init__.py
Normal file
16
selenium/webdriver/firefox/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
84
selenium/webdriver/firefox/extension_connection.py
Normal file
84
selenium/webdriver/firefox/extension_connection.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
from selenium.webdriver.common import utils
|
||||||
|
from selenium.webdriver.remote.command import Command
|
||||||
|
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||||
|
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
PORT = 0
|
||||||
|
HOST = None
|
||||||
|
_URL = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionConnection(RemoteConnection):
|
||||||
|
def __init__(self, host, firefox_profile, firefox_binary=None, timeout=30):
|
||||||
|
self.profile = firefox_profile
|
||||||
|
self.binary = firefox_binary
|
||||||
|
HOST = host
|
||||||
|
timeout = int(timeout)
|
||||||
|
|
||||||
|
if self.binary is None:
|
||||||
|
self.binary = FirefoxBinary()
|
||||||
|
|
||||||
|
if HOST is None:
|
||||||
|
HOST = "127.0.0.1"
|
||||||
|
|
||||||
|
PORT = utils.free_port()
|
||||||
|
self.profile.port = PORT
|
||||||
|
self.profile.update_preferences()
|
||||||
|
|
||||||
|
self.profile.add_extension()
|
||||||
|
|
||||||
|
self.binary.launch_browser(self.profile, timeout=timeout)
|
||||||
|
_URL = "http://%s:%d/hub" % (HOST, PORT)
|
||||||
|
RemoteConnection.__init__(
|
||||||
|
self, _URL, keep_alive=True)
|
||||||
|
|
||||||
|
def quit(self, sessionId=None):
|
||||||
|
self.execute(Command.QUIT, {'sessionId': sessionId})
|
||||||
|
while self.is_connectable():
|
||||||
|
LOGGER.info("waiting to quit")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Connects to the extension and retrieves the session id."""
|
||||||
|
return self.execute(Command.NEW_SESSION,
|
||||||
|
{'desiredCapabilities': DesiredCapabilities.FIREFOX})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def connect_and_quit(self):
|
||||||
|
"""Connects to an running browser and quit immediately."""
|
||||||
|
self._request('%s/extensions/firefox/quit' % _URL)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_connectable(self):
|
||||||
|
"""Trys to connect to the extension but do not retrieve context."""
|
||||||
|
utils.is_connectable(self.profile.port)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionConnectionError(Exception):
|
||||||
|
"""An internal error occurred int the extension.
|
||||||
|
|
||||||
|
Might be caused by bad input or bugs in webdriver
|
||||||
|
"""
|
||||||
|
pass
|
217
selenium/webdriver/firefox/firefox_binary.py
Normal file
217
selenium/webdriver/firefox/firefox_binary.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
from subprocess import Popen, STDOUT
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
from selenium.webdriver.common import utils
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class FirefoxBinary(object):
|
||||||
|
|
||||||
|
NO_FOCUS_LIBRARY_NAME = "x_ignore_nofocus.so"
|
||||||
|
|
||||||
|
def __init__(self, firefox_path=None, log_file=None):
|
||||||
|
"""
|
||||||
|
Creates a new instance of Firefox binary.
|
||||||
|
|
||||||
|
:Args:
|
||||||
|
- firefox_path - Path to the Firefox executable. By default, it will be detected from the standard locations.
|
||||||
|
- log_file - A file object to redirect the firefox process output to. It can be sys.stdout.
|
||||||
|
Please note that with parallel run the output won't be synchronous.
|
||||||
|
By default, it will be redirected to /dev/null.
|
||||||
|
"""
|
||||||
|
self._start_cmd = firefox_path
|
||||||
|
# We used to default to subprocess.PIPE instead of /dev/null, but after
|
||||||
|
# a while the pipe would fill up and Firefox would freeze.
|
||||||
|
self._log_file = log_file or open(os.devnull, "wb")
|
||||||
|
self.command_line = None
|
||||||
|
if self._start_cmd is None:
|
||||||
|
self._start_cmd = self._get_firefox_start_cmd()
|
||||||
|
if not self._start_cmd.strip():
|
||||||
|
raise WebDriverException(
|
||||||
|
"Failed to find firefox binary. You can set it by specifying "
|
||||||
|
"the path to 'firefox_binary':\n\nfrom "
|
||||||
|
"selenium.webdriver.firefox.firefox_binary import "
|
||||||
|
"FirefoxBinary\n\nbinary = "
|
||||||
|
"FirefoxBinary('/path/to/binary')\ndriver = "
|
||||||
|
"webdriver.Firefox(firefox_binary=binary)")
|
||||||
|
# Rather than modifying the environment of the calling Python process
|
||||||
|
# copy it and modify as needed.
|
||||||
|
self._firefox_env = os.environ.copy()
|
||||||
|
self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1"
|
||||||
|
self._firefox_env["MOZ_NO_REMOTE"] = "1"
|
||||||
|
self._firefox_env["NO_EM_RESTART"] = "1"
|
||||||
|
|
||||||
|
def add_command_line_options(self, *args):
|
||||||
|
self.command_line = args
|
||||||
|
|
||||||
|
def launch_browser(self, profile, timeout=30):
|
||||||
|
"""Launches the browser for the given profile name.
|
||||||
|
It is assumed the profile already exists.
|
||||||
|
"""
|
||||||
|
self.profile = profile
|
||||||
|
|
||||||
|
self._start_from_profile_path(self.profile.path)
|
||||||
|
self._wait_until_connectable(timeout=timeout)
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
"""Kill the browser.
|
||||||
|
|
||||||
|
This is useful when the browser is stuck.
|
||||||
|
"""
|
||||||
|
if self.process:
|
||||||
|
self.process.kill()
|
||||||
|
self.process.wait()
|
||||||
|
|
||||||
|
def _start_from_profile_path(self, path):
|
||||||
|
self._firefox_env["XRE_PROFILE_PATH"] = path
|
||||||
|
|
||||||
|
if platform.system().lower() == 'linux':
|
||||||
|
self._modify_link_library_path()
|
||||||
|
command = [self._start_cmd, "-foreground"]
|
||||||
|
if self.command_line is not None:
|
||||||
|
for cli in self.command_line:
|
||||||
|
command.append(cli)
|
||||||
|
self.process = Popen(
|
||||||
|
command, stdout=self._log_file, stderr=STDOUT,
|
||||||
|
env=self._firefox_env)
|
||||||
|
|
||||||
|
def _wait_until_connectable(self, timeout=30):
|
||||||
|
"""Blocks until the extension is connectable in the firefox."""
|
||||||
|
count = 0
|
||||||
|
while not utils.is_connectable(self.profile.port):
|
||||||
|
if self.process.poll() is not None:
|
||||||
|
# Browser has exited
|
||||||
|
raise WebDriverException(
|
||||||
|
"The browser appears to have exited "
|
||||||
|
"before we could connect. If you specified a log_file in "
|
||||||
|
"the FirefoxBinary constructor, check it for details.")
|
||||||
|
if count >= timeout:
|
||||||
|
self.kill()
|
||||||
|
raise WebDriverException(
|
||||||
|
"Can't load the profile. Possible firefox version mismatch. "
|
||||||
|
"You must use GeckoDriver instead for Firefox 48+. Profile "
|
||||||
|
"Dir: %s If you specified a log_file in the "
|
||||||
|
"FirefoxBinary constructor, check it for details."
|
||||||
|
% (self.profile.path))
|
||||||
|
count += 1
|
||||||
|
time.sleep(1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _find_exe_in_registry(self):
|
||||||
|
try:
|
||||||
|
from _winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER
|
||||||
|
except ImportError:
|
||||||
|
from winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER
|
||||||
|
import shlex
|
||||||
|
keys = (r"SOFTWARE\Classes\FirefoxHTML\shell\open\command",
|
||||||
|
r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command")
|
||||||
|
command = ""
|
||||||
|
for path in keys:
|
||||||
|
try:
|
||||||
|
key = OpenKey(HKEY_LOCAL_MACHINE, path)
|
||||||
|
command = QueryValue(key, "")
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
try:
|
||||||
|
key = OpenKey(HKEY_CURRENT_USER, path)
|
||||||
|
command = QueryValue(key, "")
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if not command:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return shlex.split(command)[0]
|
||||||
|
|
||||||
|
def _get_firefox_start_cmd(self):
|
||||||
|
"""Return the command to start firefox."""
|
||||||
|
start_cmd = ""
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
start_cmd = "/Applications/Firefox.app/Contents/MacOS/firefox-bin"
|
||||||
|
# fallback to homebrew installation for mac users
|
||||||
|
if not os.path.exists(start_cmd):
|
||||||
|
start_cmd = os.path.expanduser("~") + start_cmd
|
||||||
|
elif platform.system() == "Windows":
|
||||||
|
start_cmd = (self._find_exe_in_registry() or self._default_windows_location())
|
||||||
|
elif platform.system() == 'Java' and os._name == 'nt':
|
||||||
|
start_cmd = self._default_windows_location()
|
||||||
|
else:
|
||||||
|
for ffname in ["firefox", "iceweasel"]:
|
||||||
|
start_cmd = self.which(ffname)
|
||||||
|
if start_cmd is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# couldn't find firefox on the system path
|
||||||
|
raise RuntimeError(
|
||||||
|
"Could not find firefox in your system PATH." +
|
||||||
|
" Please specify the firefox binary location or install firefox")
|
||||||
|
return start_cmd
|
||||||
|
|
||||||
|
def _default_windows_location(self):
|
||||||
|
program_files = [os.getenv("PROGRAMFILES", r"C:\Program Files"),
|
||||||
|
os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)")]
|
||||||
|
for path in program_files:
|
||||||
|
binary_path = os.path.join(path, r"Mozilla Firefox\firefox.exe")
|
||||||
|
if os.access(binary_path, os.X_OK):
|
||||||
|
return binary_path
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _modify_link_library_path(self):
|
||||||
|
existing_ld_lib_path = os.environ.get('LD_LIBRARY_PATH', '')
|
||||||
|
|
||||||
|
new_ld_lib_path = self._extract_and_check(
|
||||||
|
self.profile, self.NO_FOCUS_LIBRARY_NAME, "x86", "amd64")
|
||||||
|
|
||||||
|
new_ld_lib_path += existing_ld_lib_path
|
||||||
|
|
||||||
|
self._firefox_env["LD_LIBRARY_PATH"] = new_ld_lib_path
|
||||||
|
self._firefox_env['LD_PRELOAD'] = self.NO_FOCUS_LIBRARY_NAME
|
||||||
|
|
||||||
|
def _extract_and_check(self, profile, no_focus_so_name, x86, amd64):
|
||||||
|
|
||||||
|
paths = [x86, amd64]
|
||||||
|
built_path = ""
|
||||||
|
for path in paths:
|
||||||
|
library_path = os.path.join(profile.path, path)
|
||||||
|
if not os.path.exists(library_path):
|
||||||
|
os.makedirs(library_path)
|
||||||
|
import shutil
|
||||||
|
shutil.copy(os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
path,
|
||||||
|
self.NO_FOCUS_LIBRARY_NAME),
|
||||||
|
library_path)
|
||||||
|
built_path += library_path + ":"
|
||||||
|
|
||||||
|
return built_path
|
||||||
|
|
||||||
|
def which(self, fname):
|
||||||
|
"""Returns the fully qualified path by searching Path of the given
|
||||||
|
name"""
|
||||||
|
for pe in os.environ['PATH'].split(os.pathsep):
|
||||||
|
checkname = os.path.join(pe, fname)
|
||||||
|
if os.access(checkname, os.X_OK) and not os.path.isdir(checkname):
|
||||||
|
return checkname
|
||||||
|
return None
|
364
selenium/webdriver/firefox/firefox_profile.py
Normal file
364
selenium/webdriver/firefox/firefox_profile.py
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO as BytesIO
|
||||||
|
except ImportError:
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from xml.dom import minidom
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
|
||||||
|
|
||||||
|
WEBDRIVER_EXT = "webdriver.xpi"
|
||||||
|
WEBDRIVER_PREFERENCES = "webdriver_prefs.json"
|
||||||
|
EXTENSION_NAME = "fxdriver@googlecode.com"
|
||||||
|
|
||||||
|
|
||||||
|
class AddonFormatError(Exception):
|
||||||
|
"""Exception for not well-formed add-on manifest files"""
|
||||||
|
|
||||||
|
|
||||||
|
class FirefoxProfile(object):
|
||||||
|
ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE"
|
||||||
|
DEFAULT_PREFERENCES = None
|
||||||
|
|
||||||
|
def __init__(self, profile_directory=None):
|
||||||
|
"""
|
||||||
|
Initialises a new instance of a Firefox Profile
|
||||||
|
|
||||||
|
:args:
|
||||||
|
- profile_directory: Directory of profile that you want to use. If a
|
||||||
|
directory is passed in it will be cloned and the cloned directory
|
||||||
|
will be used by the driver when instantiated.
|
||||||
|
This defaults to None and will create a new
|
||||||
|
directory when object is created.
|
||||||
|
"""
|
||||||
|
if not FirefoxProfile.DEFAULT_PREFERENCES:
|
||||||
|
with open(os.path.join(os.path.dirname(__file__),
|
||||||
|
WEBDRIVER_PREFERENCES)) as default_prefs:
|
||||||
|
FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)
|
||||||
|
|
||||||
|
self.default_preferences = copy.deepcopy(
|
||||||
|
FirefoxProfile.DEFAULT_PREFERENCES['mutable'])
|
||||||
|
self.profile_dir = profile_directory
|
||||||
|
self.tempfolder = None
|
||||||
|
if self.profile_dir is None:
|
||||||
|
self.profile_dir = self._create_tempfolder()
|
||||||
|
else:
|
||||||
|
self.tempfolder = tempfile.mkdtemp()
|
||||||
|
newprof = os.path.join(self.tempfolder, "webdriver-py-profilecopy")
|
||||||
|
shutil.copytree(self.profile_dir, newprof,
|
||||||
|
ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock"))
|
||||||
|
self.profile_dir = newprof
|
||||||
|
os.chmod(self.profile_dir, 0o755)
|
||||||
|
self._read_existing_userjs(os.path.join(self.profile_dir, "user.js"))
|
||||||
|
self.extensionsDir = os.path.join(self.profile_dir, "extensions")
|
||||||
|
self.userPrefs = os.path.join(self.profile_dir, "user.js")
|
||||||
|
if os.path.isfile(self.userPrefs):
|
||||||
|
os.chmod(self.userPrefs, 0o644)
|
||||||
|
|
||||||
|
# Public Methods
|
||||||
|
def set_preference(self, key, value):
|
||||||
|
"""
|
||||||
|
sets the preference that we want in the profile.
|
||||||
|
"""
|
||||||
|
self.default_preferences[key] = value
|
||||||
|
|
||||||
|
def add_extension(self, extension=WEBDRIVER_EXT):
|
||||||
|
self._install_extension(extension)
|
||||||
|
|
||||||
|
def update_preferences(self):
|
||||||
|
for key, value in FirefoxProfile.DEFAULT_PREFERENCES['frozen'].items():
|
||||||
|
self.default_preferences[key] = value
|
||||||
|
self._write_user_prefs(self.default_preferences)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
"""
|
||||||
|
Gets the profile directory that is currently being used
|
||||||
|
"""
|
||||||
|
return self.profile_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
"""
|
||||||
|
Gets the port that WebDriver is working on
|
||||||
|
"""
|
||||||
|
return self._port
|
||||||
|
|
||||||
|
@port.setter
|
||||||
|
def port(self, port):
|
||||||
|
"""
|
||||||
|
Sets the port that WebDriver will be running on
|
||||||
|
"""
|
||||||
|
if not isinstance(port, int):
|
||||||
|
raise WebDriverException("Port needs to be an integer")
|
||||||
|
try:
|
||||||
|
port = int(port)
|
||||||
|
if port < 1 or port > 65535:
|
||||||
|
raise WebDriverException("Port number must be in the range 1..65535")
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise WebDriverException("Port needs to be an integer")
|
||||||
|
self._port = port
|
||||||
|
self.set_preference("webdriver_firefox_port", self._port)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accept_untrusted_certs(self):
|
||||||
|
return self.default_preferences["webdriver_accept_untrusted_certs"]
|
||||||
|
|
||||||
|
@accept_untrusted_certs.setter
|
||||||
|
def accept_untrusted_certs(self, value):
|
||||||
|
if value not in [True, False]:
|
||||||
|
raise WebDriverException("Please pass in a Boolean to this call")
|
||||||
|
self.set_preference("webdriver_accept_untrusted_certs", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def assume_untrusted_cert_issuer(self):
|
||||||
|
return self.default_preferences["webdriver_assume_untrusted_issuer"]
|
||||||
|
|
||||||
|
@assume_untrusted_cert_issuer.setter
|
||||||
|
def assume_untrusted_cert_issuer(self, value):
|
||||||
|
if value not in [True, False]:
|
||||||
|
raise WebDriverException("Please pass in a Boolean to this call")
|
||||||
|
|
||||||
|
self.set_preference("webdriver_assume_untrusted_issuer", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encoded(self):
|
||||||
|
"""
|
||||||
|
A zipped, base64 encoded string of profile directory
|
||||||
|
for use with remote WebDriver JSON wire protocol
|
||||||
|
"""
|
||||||
|
self.update_preferences()
|
||||||
|
fp = BytesIO()
|
||||||
|
zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
path_root = len(self.path) + 1 # account for trailing slash
|
||||||
|
for base, dirs, files in os.walk(self.path):
|
||||||
|
for fyle in files:
|
||||||
|
filename = os.path.join(base, fyle)
|
||||||
|
zipped.write(filename, filename[path_root:])
|
||||||
|
zipped.close()
|
||||||
|
return base64.b64encode(fp.getvalue()).decode('UTF-8')
|
||||||
|
|
||||||
|
def _create_tempfolder(self):
|
||||||
|
"""
|
||||||
|
Creates a temp folder to store User.js and the extension
|
||||||
|
"""
|
||||||
|
return tempfile.mkdtemp()
|
||||||
|
|
||||||
|
def _write_user_prefs(self, user_prefs):
|
||||||
|
"""
|
||||||
|
writes the current user prefs dictionary to disk
|
||||||
|
"""
|
||||||
|
with open(self.userPrefs, "w") as f:
|
||||||
|
for key, value in user_prefs.items():
|
||||||
|
f.write('user_pref("%s", %s);\n' % (key, json.dumps(value)))
|
||||||
|
|
||||||
|
def _read_existing_userjs(self, userjs):
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
PREF_RE = re.compile(r'user_pref\("(.*)",\s(.*)\)')
|
||||||
|
try:
|
||||||
|
with open(userjs) as f:
|
||||||
|
for usr in f:
|
||||||
|
matches = re.search(PREF_RE, usr)
|
||||||
|
try:
|
||||||
|
self.default_preferences[matches.group(1)] = json.loads(matches.group(2))
|
||||||
|
except Exception:
|
||||||
|
warnings.warn("(skipping) failed to json.loads existing preference: " +
|
||||||
|
matches.group(1) + matches.group(2))
|
||||||
|
except Exception:
|
||||||
|
# The profile given hasn't had any changes made, i.e no users.js
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _install_extension(self, addon, unpack=True):
|
||||||
|
"""
|
||||||
|
Installs addon from a filepath, url
|
||||||
|
or directory of addons in the profile.
|
||||||
|
- path: url, absolute path to .xpi, or directory of addons
|
||||||
|
- unpack: whether to unpack unless specified otherwise in the install.rdf
|
||||||
|
"""
|
||||||
|
if addon == WEBDRIVER_EXT:
|
||||||
|
addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT)
|
||||||
|
|
||||||
|
tmpdir = None
|
||||||
|
xpifile = None
|
||||||
|
if addon.endswith('.xpi'):
|
||||||
|
tmpdir = tempfile.mkdtemp(suffix='.' + os.path.split(addon)[-1])
|
||||||
|
compressed_file = zipfile.ZipFile(addon, 'r')
|
||||||
|
for name in compressed_file.namelist():
|
||||||
|
if name.endswith('/'):
|
||||||
|
if not os.path.isdir(os.path.join(tmpdir, name)):
|
||||||
|
os.makedirs(os.path.join(tmpdir, name))
|
||||||
|
else:
|
||||||
|
if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))):
|
||||||
|
os.makedirs(os.path.dirname(os.path.join(tmpdir, name)))
|
||||||
|
data = compressed_file.read(name)
|
||||||
|
with open(os.path.join(tmpdir, name), 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
xpifile = addon
|
||||||
|
addon = tmpdir
|
||||||
|
|
||||||
|
# determine the addon id
|
||||||
|
addon_details = self._addon_details(addon)
|
||||||
|
addon_id = addon_details.get('id')
|
||||||
|
assert addon_id, 'The addon id could not be found: %s' % addon
|
||||||
|
|
||||||
|
# copy the addon to the profile
|
||||||
|
addon_path = os.path.join(self.extensionsDir, addon_id)
|
||||||
|
if not unpack and not addon_details['unpack'] and xpifile:
|
||||||
|
if not os.path.exists(self.extensionsDir):
|
||||||
|
os.makedirs(self.extensionsDir)
|
||||||
|
os.chmod(self.extensionsDir, 0o755)
|
||||||
|
shutil.copy(xpifile, addon_path + '.xpi')
|
||||||
|
else:
|
||||||
|
if not os.path.exists(addon_path):
|
||||||
|
shutil.copytree(addon, addon_path, symlinks=True)
|
||||||
|
|
||||||
|
# remove the temporary directory, if any
|
||||||
|
if tmpdir:
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
def _addon_details(self, addon_path):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of details about the addon.
|
||||||
|
|
||||||
|
:param addon_path: path to the add-on directory or XPI
|
||||||
|
|
||||||
|
Returns::
|
||||||
|
|
||||||
|
{'id': u'rainbow@colors.org', # id of the addon
|
||||||
|
'version': u'1.4', # version of the addon
|
||||||
|
'name': u'Rainbow', # name of the addon
|
||||||
|
'unpack': False } # whether to unpack the addon
|
||||||
|
"""
|
||||||
|
|
||||||
|
details = {
|
||||||
|
'id': None,
|
||||||
|
'unpack': False,
|
||||||
|
'name': None,
|
||||||
|
'version': None
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_namespace_id(doc, url):
|
||||||
|
attributes = doc.documentElement.attributes
|
||||||
|
namespace = ""
|
||||||
|
for i in range(attributes.length):
|
||||||
|
if attributes.item(i).value == url:
|
||||||
|
if ":" in attributes.item(i).name:
|
||||||
|
# If the namespace is not the default one remove 'xlmns:'
|
||||||
|
namespace = attributes.item(i).name.split(':')[1] + ":"
|
||||||
|
break
|
||||||
|
return namespace
|
||||||
|
|
||||||
|
def get_text(element):
|
||||||
|
"""Retrieve the text value of a given node"""
|
||||||
|
rc = []
|
||||||
|
for node in element.childNodes:
|
||||||
|
if node.nodeType == node.TEXT_NODE:
|
||||||
|
rc.append(node.data)
|
||||||
|
return ''.join(rc).strip()
|
||||||
|
|
||||||
|
def parse_manifest_json(content):
|
||||||
|
"""Extracts the details from the contents of a WebExtensions `manifest.json` file."""
|
||||||
|
manifest = json.loads(content)
|
||||||
|
try:
|
||||||
|
id = manifest['applications']['gecko']['id']
|
||||||
|
except KeyError:
|
||||||
|
id = manifest['name'].replace(" ", "") + "@" + manifest['version']
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'version': manifest['version'],
|
||||||
|
'name': manifest['version'],
|
||||||
|
'unpack': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not os.path.exists(addon_path):
|
||||||
|
raise IOError('Add-on path does not exist: %s' % addon_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if zipfile.is_zipfile(addon_path):
|
||||||
|
# Bug 944361 - We cannot use 'with' together with zipFile because
|
||||||
|
# it will cause an exception thrown in Python 2.6.
|
||||||
|
try:
|
||||||
|
compressed_file = zipfile.ZipFile(addon_path, 'r')
|
||||||
|
if 'manifest.json' in compressed_file.namelist():
|
||||||
|
return parse_manifest_json(compressed_file.read('manifest.json'))
|
||||||
|
|
||||||
|
manifest = compressed_file.read('install.rdf')
|
||||||
|
finally:
|
||||||
|
compressed_file.close()
|
||||||
|
elif os.path.isdir(addon_path):
|
||||||
|
manifest_json_filename = os.path.join(addon_path, 'manifest.json')
|
||||||
|
if os.path.exists(manifest_json_filename):
|
||||||
|
with open(manifest_json_filename, 'r') as f:
|
||||||
|
return parse_manifest_json(f.read())
|
||||||
|
|
||||||
|
with open(os.path.join(addon_path, 'install.rdf'), 'r') as f:
|
||||||
|
manifest = f.read()
|
||||||
|
else:
|
||||||
|
raise IOError('Add-on path is neither an XPI nor a directory: %s' % addon_path)
|
||||||
|
except (IOError, KeyError) as e:
|
||||||
|
raise AddonFormatError(str(e), sys.exc_info()[2])
|
||||||
|
|
||||||
|
try:
|
||||||
|
doc = minidom.parseString(manifest)
|
||||||
|
|
||||||
|
# Get the namespaces abbreviations
|
||||||
|
em = get_namespace_id(doc, 'http://www.mozilla.org/2004/em-rdf#')
|
||||||
|
rdf = get_namespace_id(doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
|
||||||
|
|
||||||
|
description = doc.getElementsByTagName(rdf + 'Description').item(0)
|
||||||
|
if description is None:
|
||||||
|
description = doc.getElementsByTagName('Description').item(0)
|
||||||
|
for node in description.childNodes:
|
||||||
|
# Remove the namespace prefix from the tag for comparison
|
||||||
|
entry = node.nodeName.replace(em, "")
|
||||||
|
if entry in details.keys():
|
||||||
|
details.update({entry: get_text(node)})
|
||||||
|
if details.get('id') is None:
|
||||||
|
for i in range(description.attributes.length):
|
||||||
|
attribute = description.attributes.item(i)
|
||||||
|
if attribute.name == em + 'id':
|
||||||
|
details.update({'id': attribute.value})
|
||||||
|
except Exception as e:
|
||||||
|
raise AddonFormatError(str(e), sys.exc_info()[2])
|
||||||
|
|
||||||
|
# turn unpack into a true/false value
|
||||||
|
if isinstance(details['unpack'], str):
|
||||||
|
details['unpack'] = details['unpack'].lower() == 'true'
|
||||||
|
|
||||||
|
# If no ID is set, the add-on is invalid
|
||||||
|
if details.get('id') is None:
|
||||||
|
raise AddonFormatError('Add-on id could not be found.')
|
||||||
|
|
||||||
|
return details
|
171
selenium/webdriver/firefox/options.py
Normal file
171
selenium/webdriver/firefox/options.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from selenium.common.exceptions import InvalidArgumentException
|
||||||
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
|
from selenium.webdriver.common.proxy import Proxy
|
||||||
|
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
|
||||||
|
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
|
||||||
|
from selenium.webdriver.common.options import ArgOptions
|
||||||
|
|
||||||
|
|
||||||
|
class Log(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.level = None
|
||||||
|
|
||||||
|
def to_capabilities(self):
|
||||||
|
if self.level is not None:
|
||||||
|
return {"log": {"level": self.level}}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class Options(ArgOptions):
|
||||||
|
KEY = "moz:firefoxOptions"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Options, self).__init__()
|
||||||
|
self._binary = None
|
||||||
|
self._preferences = {}
|
||||||
|
self._profile = None
|
||||||
|
self._proxy = None
|
||||||
|
self.log = Log()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def binary(self):
|
||||||
|
"""Returns the FirefoxBinary instance"""
|
||||||
|
return self._binary
|
||||||
|
|
||||||
|
@binary.setter
|
||||||
|
def binary(self, new_binary):
|
||||||
|
"""Sets location of the browser binary, either by string or
|
||||||
|
``FirefoxBinary`` instance.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(new_binary, FirefoxBinary):
|
||||||
|
new_binary = FirefoxBinary(new_binary)
|
||||||
|
self._binary = new_binary
|
||||||
|
|
||||||
|
@property
|
||||||
|
def binary_location(self):
|
||||||
|
"""
|
||||||
|
:Returns: The location of the binary.
|
||||||
|
"""
|
||||||
|
return self.binary._start_cmd
|
||||||
|
|
||||||
|
@binary_location.setter # noqa
|
||||||
|
def binary_location(self, value):
|
||||||
|
""" Sets the location of the browser binary by string """
|
||||||
|
self.binary = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accept_insecure_certs(self):
|
||||||
|
return self._caps.get('acceptInsecureCerts')
|
||||||
|
|
||||||
|
@accept_insecure_certs.setter
|
||||||
|
def accept_insecure_certs(self, value):
|
||||||
|
self._caps['acceptInsecureCerts'] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preferences(self):
|
||||||
|
""":Returns: A dict of preferences."""
|
||||||
|
return self._preferences
|
||||||
|
|
||||||
|
def set_preference(self, name, value):
|
||||||
|
"""Sets a preference."""
|
||||||
|
self._preferences[name] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proxy(self):
|
||||||
|
"""
|
||||||
|
:Returns: Proxy if set, otherwise None.
|
||||||
|
"""
|
||||||
|
return self._proxy
|
||||||
|
|
||||||
|
@proxy.setter
|
||||||
|
def proxy(self, value):
|
||||||
|
if not isinstance(value, Proxy):
|
||||||
|
raise InvalidArgumentException("Only Proxy objects can be passed in.")
|
||||||
|
self._proxy = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def profile(self):
|
||||||
|
"""
|
||||||
|
:Returns: The Firefox profile to use.
|
||||||
|
"""
|
||||||
|
return self._profile
|
||||||
|
|
||||||
|
@profile.setter
|
||||||
|
def profile(self, new_profile):
|
||||||
|
"""Sets location of the browser profile to use, either by string
|
||||||
|
or ``FirefoxProfile``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(new_profile, FirefoxProfile):
|
||||||
|
new_profile = FirefoxProfile(new_profile)
|
||||||
|
self._profile = new_profile
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headless(self):
|
||||||
|
"""
|
||||||
|
:Returns: True if the headless argument is set, else False
|
||||||
|
"""
|
||||||
|
return '-headless' in self._arguments
|
||||||
|
|
||||||
|
@headless.setter
|
||||||
|
def headless(self, value):
|
||||||
|
"""
|
||||||
|
Sets the headless argument
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: boolean value indicating to set the headless option
|
||||||
|
"""
|
||||||
|
if value is True:
|
||||||
|
self._arguments.append('-headless')
|
||||||
|
elif '-headless' in self._arguments:
|
||||||
|
self._arguments.remove('-headless')
|
||||||
|
|
||||||
|
def to_capabilities(self):
|
||||||
|
"""Marshals the Firefox options to a `moz:firefoxOptions`
|
||||||
|
object.
|
||||||
|
"""
|
||||||
|
# This intentionally looks at the internal properties
|
||||||
|
# so if a binary or profile has _not_ been set,
|
||||||
|
# it will defer to geckodriver to find the system Firefox
|
||||||
|
# and generate a fresh profile.
|
||||||
|
caps = self._caps
|
||||||
|
opts = {}
|
||||||
|
|
||||||
|
if self._binary is not None:
|
||||||
|
opts["binary"] = self._binary._start_cmd
|
||||||
|
if len(self._preferences) > 0:
|
||||||
|
opts["prefs"] = self._preferences
|
||||||
|
if self._proxy is not None:
|
||||||
|
self._proxy.add_to_capabilities(caps)
|
||||||
|
if self._profile is not None:
|
||||||
|
opts["profile"] = self._profile.encoded
|
||||||
|
if len(self._arguments) > 0:
|
||||||
|
opts["args"] = self._arguments
|
||||||
|
|
||||||
|
opts.update(self.log.to_capabilities())
|
||||||
|
|
||||||
|
if len(opts) > 0:
|
||||||
|
caps[Options.KEY] = opts
|
||||||
|
|
||||||
|
return caps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_capabilities(self):
|
||||||
|
return DesiredCapabilities.FIREFOX.copy()
|
36
selenium/webdriver/firefox/remote_connection.py
Normal file
36
selenium/webdriver/firefox/remote_connection.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||||
|
|
||||||
|
|
||||||
|
class FirefoxRemoteConnection(RemoteConnection):
|
||||||
|
def __init__(self, remote_server_addr, keep_alive=True):
|
||||||
|
RemoteConnection.__init__(self, remote_server_addr, keep_alive)
|
||||||
|
|
||||||
|
self._commands["GET_CONTEXT"] = ('GET', '/session/$sessionId/moz/context')
|
||||||
|
self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context")
|
||||||
|
self._commands["ELEMENT_GET_ANONYMOUS_CHILDREN"] = \
|
||||||
|
("POST", "/session/$sessionId/moz/xbl/$id/anonymous_children")
|
||||||
|
self._commands["ELEMENT_FIND_ANONYMOUS_ELEMENTS_BY_ATTRIBUTE"] = \
|
||||||
|
("POST", "/session/$sessionId/moz/xbl/$id/anonymous_by_attribute")
|
||||||
|
self._commands["INSTALL_ADDON"] = \
|
||||||
|
("POST", "/session/$sessionId/moz/addon/install")
|
||||||
|
self._commands["UNINSTALL_ADDON"] = \
|
||||||
|
("POST", "/session/$sessionId/moz/addon/uninstall")
|
||||||
|
self._commands["FULL_PAGE_SCREENSHOT"] = \
|
||||||
|
("GET", "/session/$sessionId/moz/screenshot/full")
|
54
selenium/webdriver/firefox/service.py
Normal file
54
selenium/webdriver/firefox/service.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The SFC licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common import service
|
||||||
|
|
||||||
|
|
||||||
|
class Service(service.Service):
|
||||||
|
"""Object that manages the starting and stopping of the
|
||||||
|
GeckoDriver."""
|
||||||
|
|
||||||
|
def __init__(self, executable_path, port=0, service_args=None,
|
||||||
|
log_path="geckodriver.log", env=None):
|
||||||
|
"""Creates a new instance of the GeckoDriver remote service proxy.
|
||||||
|
|
||||||
|
GeckoDriver provides a HTTP interface speaking the W3C WebDriver
|
||||||
|
protocol to Marionette.
|
||||||
|
|
||||||
|
:param executable_path: Path to the GeckoDriver binary.
|
||||||
|
:param port: Run the remote service on a specified port.
|
||||||
|
Defaults to 0, which binds to a random open port of the
|
||||||
|
system's choosing.
|
||||||
|
:param service_args: Optional list of arguments to pass to the
|
||||||
|
GeckoDriver binary.
|
||||||
|
:param log_path: Optional path for the GeckoDriver to log to.
|
||||||
|
Defaults to _geckodriver.log_ in the current working directory.
|
||||||
|
:param env: Optional dictionary of output variables to expose
|
||||||
|
in the services' environment.
|
||||||
|
|
||||||
|
"""
|
||||||
|
log_file = open(log_path, "a+") if log_path is not None and log_path != "" else None
|
||||||
|
|
||||||
|
service.Service.__init__(
|
||||||
|
self, executable_path, port=port, log_file=log_file, env=env)
|
||||||
|
self.service_args = service_args or []
|
||||||
|
|
||||||
|
def command_line_args(self):
|
||||||
|
return ["--port", "%d" % self.port] + self.service_args
|
||||||
|
|
||||||
|
def send_remote_shutdown_command(self):
|
||||||
|
pass
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user