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