mirror of
https://github.com/l1ving/youtube-dl
synced 2025-02-09 09:32:52 +08:00
• Added Selenium 3.11.0 (slightly modified for Safari to work) under license Apache 2. Selenium is necessary for the new extractor ThisVid.
This commit is contained in:
parent
671e241bfb
commit
5634bbfe7f
19
youtube_dl/selenium/__init__.py
Normal file
19
youtube_dl/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__ = "3.11.0"
|
18
youtube_dl/selenium/common/__init__.py
Normal file
18
youtube_dl/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
|
327
youtube_dl/selenium/common/exceptions.py
Normal file
327
youtube_dl/selenium/common/exceptions.py
Normal file
@ -0,0 +1,327 @@
|
||||
# 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 ErrorInResponseException(WebDriverException):
|
||||
"""
|
||||
Thrown when an error has occurred on the server side.
|
||||
|
||||
This may happen when communicating with the firefox extension
|
||||
or the remote driver server.
|
||||
"""
|
||||
def __init__(self, response, msg):
|
||||
WebDriverException.__init__(self, msg)
|
||||
self.response = response
|
||||
|
||||
|
||||
class InvalidSwitchToTargetException(WebDriverException):
|
||||
"""
|
||||
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 is appeared.
|
||||
|
||||
Usually raised when when an expected modal is blocking webdriver form executing any
|
||||
more 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
|
38
youtube_dl/selenium/webdriver/__init__.py
Normal file
38
youtube_dl/selenium/webdriver/__init__.py
Normal file
@ -0,0 +1,38 @@
|
||||
# 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 .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__ = '3.9.0'
|
16
youtube_dl/selenium/webdriver/android/__init__.py
Normal file
16
youtube_dl/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
youtube_dl/selenium/webdriver/android/webdriver.py
Normal file
42
youtube_dl/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
youtube_dl/selenium/webdriver/blackberry/__init__.py
Normal file
16
youtube_dl/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.
|
116
youtube_dl/selenium/webdriver/blackberry/webdriver.py
Normal file
116
youtube_dl/selenium/webdriver/blackberry/webdriver.py
Normal file
@ -0,0 +1,116 @@
|
||||
# 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={}):
|
||||
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
youtube_dl/selenium/webdriver/chrome/__init__.py
Normal file
16
youtube_dl/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.
|
192
youtube_dl/selenium/webdriver/chrome/options.py
Normal file
192
youtube_dl/selenium/webdriver/chrome/options.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.
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
|
||||
|
||||
class Options(object):
|
||||
KEY = "goog:chromeOptions"
|
||||
|
||||
def __init__(self):
|
||||
self._binary_location = ''
|
||||
self._arguments = []
|
||||
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 arguments(self):
|
||||
"""
|
||||
Returns a list of arguments needed for the browser
|
||||
"""
|
||||
return self._arguments
|
||||
|
||||
def add_argument(self, argument):
|
||||
"""
|
||||
Adds an argument to the list
|
||||
|
||||
:Args:
|
||||
- Sets the arguments
|
||||
"""
|
||||
if argument:
|
||||
self._arguments.append(argument)
|
||||
else:
|
||||
raise ValueError("argument can not be null")
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
"""
|
||||
Returns a list of encoded extensions that will be loaded into chrome
|
||||
|
||||
"""
|
||||
encoded_extensions = []
|
||||
for ext in self._extension_files:
|
||||
file_ = open(ext, '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 whether or not the headless argument is set
|
||||
"""
|
||||
return '--headless' in self._arguments
|
||||
|
||||
def set_headless(self, headless=True):
|
||||
"""
|
||||
Sets the headless argument
|
||||
|
||||
Args:
|
||||
headless: boolean value indicating to set the headless option
|
||||
"""
|
||||
args = {'--headless', '--disable-gpu'}
|
||||
if headless:
|
||||
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 and
|
||||
|
||||
returns a dictionary with everything
|
||||
"""
|
||||
chrome = DesiredCapabilities.CHROME.copy()
|
||||
|
||||
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
|
||||
|
||||
chrome[self.KEY] = chrome_options
|
||||
|
||||
return chrome
|
27
youtube_dl/selenium/webdriver/chrome/remote_connection.py
Normal file
27
youtube_dl/selenium/webdriver/chrome/remote_connection.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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')
|
45
youtube_dl/selenium/webdriver/chrome/service.py
Normal file
45
youtube_dl/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
|
132
youtube_dl/selenium/webdriver/chrome/webdriver.py
Normal file
132
youtube_dl/selenium/webdriver/chrome/webdriver.py
Normal file
@ -0,0 +1,132 @@
|
||||
# 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
|
||||
|
||||
|
||||
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=0,
|
||||
options=None, service_args=None,
|
||||
desired_capabilities=None, service_log_path=None,
|
||||
chrome_options=None):
|
||||
"""
|
||||
Creates a new instance of the chrome driver.
|
||||
|
||||
Starts the service and then creates new instance of chrome driver.
|
||||
|
||||
:Args:
|
||||
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
|
||||
- port - port you would like the service to run, if left as 0, a free port will be found.
|
||||
- desired_capabilities: Dictionary object with non-browser specific
|
||||
capabilities only, such as "proxy" or "loggingPref".
|
||||
- options: this takes an instance of ChromeOptions
|
||||
"""
|
||||
if chrome_options:
|
||||
warnings.warn('use options instead of chrome_options', DeprecationWarning)
|
||||
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())
|
||||
|
||||
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),
|
||||
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 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
youtube_dl/selenium/webdriver/common/__init__.py
Normal file
16
youtube_dl/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.
|
378
youtube_dl/selenium/webdriver/common/action_chains.py
Normal file
378
youtube_dl/selenium/webdriver/common/action_chains.py
Normal file
@ -0,0 +1,378 @@
|
||||
# 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 on the remote end.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self._driver.execute(Command.W3C_CLEAR_ACTIONS)
|
||||
else:
|
||||
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 self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.click(on_element)
|
||||
self.w3c_actions.key_action.pause()
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
if on_element:
|
||||
self.move_to_element(on_element)
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.CLICK, {'button': 0}))
|
||||
return self
|
||||
|
||||
def click_and_hold(self, on_element=None):
|
||||
"""
|
||||
Holds down the left mouse button on an element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to mouse down.
|
||||
If None, clicks on current mouse position.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.click_and_hold(on_element)
|
||||
self.w3c_actions.key_action.pause()
|
||||
if on_element:
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
if on_element:
|
||||
self.move_to_element(on_element)
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.MOUSE_DOWN, {}))
|
||||
return self
|
||||
|
||||
def context_click(self, on_element=None):
|
||||
"""
|
||||
Performs a context-click (right click) on an element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to context-click.
|
||||
If None, clicks on current mouse position.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.context_click(on_element)
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
if on_element:
|
||||
self.move_to_element(on_element)
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.CLICK, {'button': 2}))
|
||||
return self
|
||||
|
||||
def double_click(self, on_element=None):
|
||||
"""
|
||||
Double-clicks an element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to double-click.
|
||||
If None, clicks on current mouse position.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.double_click(on_element)
|
||||
for _ in range(4):
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
if on_element:
|
||||
self.move_to_element(on_element)
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.DOUBLE_CLICK, {}))
|
||||
return self
|
||||
|
||||
def drag_and_drop(self, source, target):
|
||||
"""
|
||||
Holds down the left mouse button on the source element,
|
||||
then moves to the target element and releases the mouse button.
|
||||
|
||||
:Args:
|
||||
- source: The element to mouse down.
|
||||
- target: The element to mouse up.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.click_and_hold(source) \
|
||||
.move_to(target) \
|
||||
.release()
|
||||
for _ in range(3):
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
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.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.pointer_action.click_and_hold(source) \
|
||||
.move_to_location(xoffset, yoffset) \
|
||||
.release()
|
||||
for _ in range(3):
|
||||
self.w3c_actions.key_action.pause()
|
||||
else:
|
||||
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.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.key_action.send_keys(keys_to_send)
|
||||
else:
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
|
||||
return self
|
||||
|
||||
def send_keys_to_element(self, element, *keys_to_send):
|
||||
"""
|
||||
Sends keys to an element.
|
||||
|
||||
:Args:
|
||||
- element: The element to send keys.
|
||||
- keys_to_send: The keys to send. Modifier keys constants can be found in the
|
||||
'Keys' class.
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self.w3c_actions.key_action.send_keys(keys_to_send, element=element)
|
||||
else:
|
||||
self._actions.append(lambda: element.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
youtube_dl/selenium/webdriver/common/actions/__init__.py
Normal file
16
youtube_dl/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.
|
@ -0,0 +1,82 @@
|
||||
# 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")
|
||||
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, type_, name):
|
||||
new_input = PointerInput(type_, 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):
|
||||
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
|
||||
|
||||
def _add_input(self, input):
|
||||
self.devices.append(input)
|
43
youtube_dl/selenium/webdriver/common/actions/input_device.py
Normal file
43
youtube_dl/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
|
43
youtube_dl/selenium/webdriver/common/actions/interaction.py
Normal file
43
youtube_dl/selenium/webdriver/common/actions/interaction.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.
|
||||
|
||||
|
||||
KEY = "key"
|
||||
POINTER = "pointer"
|
||||
NONE = "none"
|
||||
SOURCE_TYPES = set([KEY, POINTER, NONE])
|
||||
|
||||
|
||||
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):
|
||||
output = {"type": self.PAUSE}
|
||||
output["duration"] = self.duration * 1000
|
||||
return output
|
52
youtube_dl/selenium/webdriver/common/actions/key_actions.py
Normal file
52
youtube_dl/selenium/webdriver/common/actions/key_actions.py
Normal file
@ -0,0 +1,52 @@
|
||||
# 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
|
||||
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()
|
||||
self.source = source
|
||||
super(KeyActions, self).__init__(source)
|
||||
|
||||
def key_down(self, letter, element=None):
|
||||
return self._key_action("create_key_down",
|
||||
letter, element)
|
||||
|
||||
def key_up(self, letter, element=None):
|
||||
return self._key_action("create_key_up",
|
||||
letter, element)
|
||||
|
||||
def pause(self, duration=0):
|
||||
return self._key_action("create_pause", duration)
|
||||
|
||||
def send_keys(self, text, element=None):
|
||||
if not isinstance(text, list):
|
||||
text = keys_to_typing(text)
|
||||
for letter in text:
|
||||
self.key_down(letter, element)
|
||||
self.key_up(letter, element)
|
||||
return self
|
||||
|
||||
def _key_action(self, action, letter, element=None):
|
||||
meth = getattr(self.source, action)
|
||||
meth(letter)
|
||||
return self
|
51
youtube_dl/selenium/webdriver/common/actions/key_input.py
Normal file
51
youtube_dl/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}
|
@ -0,0 +1,5 @@
|
||||
class MouseButton(object):
|
||||
|
||||
LEFT = 0
|
||||
MIDDLE = 1
|
||||
RIGHT = 2
|
100
youtube_dl/selenium/webdriver/common/actions/pointer_actions.py
Normal file
100
youtube_dl/selenium/webdriver/common/actions/pointer_actions.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 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
|
||||
|
||||
|
||||
class PointerActions(Interaction):
|
||||
|
||||
def __init__(self, source=None):
|
||||
if source is None:
|
||||
source = PointerInput(interaction.POINTER, "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):
|
||||
raise AttributeError("move_to requires a WebElement")
|
||||
if x is not None or y is not None:
|
||||
el_rect = element.rect
|
||||
left_offset = el_rect['width'] / 2
|
||||
top_offset = el_rect['height'] / 2
|
||||
left = -left_offset + (x or 0)
|
||||
top = -top_offset + (y or 0)
|
||||
else:
|
||||
left = 0
|
||||
top = 0
|
||||
self.source.create_pointer_move(origin=element, x=int(left), y=int(top))
|
||||
return self
|
||||
|
||||
def move_by(self, x, y):
|
||||
self.source.create_pointer_move(origin=interaction.POINTER, x=int(x), y=int(y))
|
||||
return self
|
||||
|
||||
def move_to_location(self, x, y):
|
||||
self.source.create_pointer_move(origin='viewport', x=int(x), y=int(y))
|
||||
return self
|
||||
|
||||
def click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
return self
|
||||
|
||||
def context_click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(MouseButton.RIGHT)
|
||||
self.pointer_up(MouseButton.RIGHT)
|
||||
return self
|
||||
|
||||
def click_and_hold(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down()
|
||||
return self
|
||||
|
||||
def release(self):
|
||||
self.pointer_up()
|
||||
return self
|
||||
|
||||
def double_click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.click()
|
||||
self.click()
|
||||
|
||||
def pause(self, duration=0):
|
||||
self.source.create_pause(duration)
|
||||
return self
|
||||
|
||||
def _button_action(self, action, button=MouseButton.LEFT):
|
||||
meth = getattr(self.source, action)
|
||||
meth(button)
|
||||
return self
|
@ -0,0 +1,58 @@
|
||||
# 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 selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class PointerInput(InputDevice):
|
||||
|
||||
DEFAULT_MOVE_DURATION = 250
|
||||
|
||||
def __init__(self, type_, name):
|
||||
super(PointerInput, self).__init__()
|
||||
self.type = type_
|
||||
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):
|
||||
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": pause_duration * 1000})
|
||||
|
||||
def encode(self):
|
||||
return {"type": self.type,
|
||||
"parameters": {"pointerType": self.name},
|
||||
"id": self.name,
|
||||
"actions": [acts for acts in self.actions]}
|
122
youtube_dl/selenium/webdriver/common/alert.py
Normal file
122
youtube_dl/selenium/webdriver/common/alert.py
Normal file
@ -0,0 +1,122 @@
|
||||
# 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})
|
||||
|
||||
def authenticate(self, username, password):
|
||||
"""
|
||||
Send the username / password to an Authenticated dialog (like with Basic HTTP Auth).
|
||||
Implicitly 'clicks ok'
|
||||
|
||||
Usage::
|
||||
driver.switch_to.alert.authenticate('cheese', 'secretGouda')
|
||||
|
||||
:Args:
|
||||
-username: string to be set in the username section of the dialog
|
||||
-password: string to be set in the password section of the dialog
|
||||
"""
|
||||
self.driver.execute(
|
||||
Command.SET_ALERT_CREDENTIALS,
|
||||
{'username': username, 'password': password})
|
||||
self.accept()
|
35
youtube_dl/selenium/webdriver/common/by.py
Normal file
35
youtube_dl/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"
|
128
youtube_dl/selenium/webdriver/common/desired_capabilities.py
Normal file
128
youtube_dl/selenium/webdriver/common/desired_capabilities.py
Normal file
@ -0,0 +1,128 @@
|
||||
# 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",
|
||||
"marionette": True,
|
||||
"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
youtube_dl/selenium/webdriver/common/html5/__init__.py
Normal file
16
youtube_dl/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.
|
@ -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
youtube_dl/selenium/webdriver/common/keys.py
Normal file
96
youtube_dl/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'
|
334
youtube_dl/selenium/webdriver/common/proxy.py
Normal file
334
youtube_dl/selenium/webdriver/common/proxy.py
Normal file
@ -0,0 +1,334 @@
|
||||
# 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 = ''
|
||||
|
||||
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']
|
||||
|
||||
@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
|
||||
|
||||
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
|
||||
capabilities['proxy'] = proxy_caps
|
178
youtube_dl/selenium/webdriver/common/service.py
Normal file
178
youtube_dl/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
youtube_dl/selenium/webdriver/common/touch_actions.py
Normal file
192
youtube_dl/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.
|
152
youtube_dl/selenium/webdriver/common/utils.py
Normal file
152
youtube_dl/selenium/webdriver/common/utils.py
Normal file
@ -0,0 +1,152 @@
|
||||
# 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:
|
||||
basestring
|
||||
except NameError:
|
||||
# Python 3
|
||||
basestring = str
|
||||
|
||||
|
||||
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 socket.error:
|
||||
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
|
16
youtube_dl/selenium/webdriver/edge/__init__.py
Normal file
16
youtube_dl/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.
|
45
youtube_dl/selenium/webdriver/edge/options.py
Normal file
45
youtube_dl/selenium/webdriver/edge/options.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.desired_capabilities import DesiredCapabilities
|
||||
|
||||
|
||||
class Options(object):
|
||||
|
||||
def __init__(self):
|
||||
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
|
||||
"""
|
||||
edge = DesiredCapabilities.EDGE.copy()
|
||||
edge['pageLoadStrategy'] = self._page_load_strategy
|
||||
|
||||
return edge
|
57
youtube_dl/selenium/webdriver/edge/service.py
Normal file
57
youtube_dl/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 http://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
|
48
youtube_dl/selenium/webdriver/edge/webdriver.py
Normal file
48
youtube_dl/selenium/webdriver/edge/webdriver.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.
|
||||
|
||||
from selenium.webdriver.common import utils
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from .service import Service
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
|
||||
def __init__(self, executable_path='MicrosoftWebDriver.exe',
|
||||
capabilities=None, port=0, verbose=False, log_path=None):
|
||||
self.port = port
|
||||
if self.port == 0:
|
||||
self.port = utils.free_port()
|
||||
|
||||
self.edge_service = Service(executable_path, port=self.port, verbose=verbose, log_path=log_path)
|
||||
self.edge_service.start()
|
||||
|
||||
if capabilities is None:
|
||||
capabilities = DesiredCapabilities.EDGE
|
||||
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=RemoteConnection('http://localhost:%d' % self.port,
|
||||
resolve_ip=False),
|
||||
desired_capabilities=capabilities)
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
RemoteWebDriver.quit(self)
|
||||
self.edge_service.stop()
|
16
youtube_dl/selenium/webdriver/firefox/__init__.py
Normal file
16
youtube_dl/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.
|
BIN
youtube_dl/selenium/webdriver/firefox/amd64/x_ignore_nofocus.so
Executable file
BIN
youtube_dl/selenium/webdriver/firefox/amd64/x_ignore_nofocus.so
Executable file
Binary file not shown.
@ -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
youtube_dl/selenium/webdriver/firefox/firefox_binary.py
Normal file
217
youtube_dl/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
|
384
youtube_dl/selenium/webdriver/firefox/firefox_profile.py
Normal file
384
youtube_dl/selenium/webdriver/firefox/firefox_profile.py
Normal file
@ -0,0 +1,384 @@
|
||||
# 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.webdriver.common.proxy import ProxyType
|
||||
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.
|
||||
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.native_events_enabled = True
|
||||
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 native_events_enabled(self):
|
||||
return self.default_preferences['webdriver_enable_native_events']
|
||||
|
||||
@native_events_enabled.setter
|
||||
def native_events_enabled(self, value):
|
||||
if value not in [True, False]:
|
||||
raise WebDriverException("Please pass in a Boolean to this call")
|
||||
self.set_preference("webdriver_enable_native_events", value)
|
||||
|
||||
@property
|
||||
def encoded(self):
|
||||
"""
|
||||
A zipped, base64 encoded string of profile directory
|
||||
for use with remote WebDriver JSON wire protocol
|
||||
"""
|
||||
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 set_proxy(self, proxy):
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"This method has been deprecated. Please pass in the proxy object to the Driver Object",
|
||||
DeprecationWarning)
|
||||
if proxy is None:
|
||||
raise ValueError("proxy can not be None")
|
||||
|
||||
if proxy.proxy_type is ProxyType.UNSPECIFIED:
|
||||
return
|
||||
|
||||
self.set_preference("network.proxy.type", proxy.proxy_type['ff_value'])
|
||||
|
||||
if proxy.proxy_type is ProxyType.MANUAL:
|
||||
self.set_preference("network.proxy.no_proxies_on", proxy.no_proxy)
|
||||
self._set_manual_proxy_preference("ftp", proxy.ftp_proxy)
|
||||
self._set_manual_proxy_preference("http", proxy.http_proxy)
|
||||
self._set_manual_proxy_preference("ssl", proxy.ssl_proxy)
|
||||
self._set_manual_proxy_preference("socks", proxy.socks_proxy)
|
||||
elif proxy.proxy_type is ProxyType.PAC:
|
||||
self.set_preference("network.proxy.autoconfig_url", proxy.proxy_autoconfig_url)
|
||||
|
||||
def _set_manual_proxy_preference(self, key, setting):
|
||||
if setting is None or setting is '':
|
||||
return
|
||||
|
||||
host_details = setting.split(":")
|
||||
self.set_preference("network.proxy.%s" % key, host_details[0])
|
||||
if len(host_details) > 1:
|
||||
self.set_preference("network.proxy.%s_port" % key, int(host_details[1]))
|
||||
|
||||
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()
|
||||
|
||||
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')
|
||||
manifest = compressed_file.read('install.rdf')
|
||||
finally:
|
||||
compressed_file.close()
|
||||
elif os.path.isdir(addon_path):
|
||||
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
|
160
youtube_dl/selenium/webdriver/firefox/options.py
Normal file
160
youtube_dl/selenium/webdriver/firefox/options.py
Normal file
@ -0,0 +1,160 @@
|
||||
# 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.proxy import Proxy
|
||||
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
|
||||
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
|
||||
|
||||
|
||||
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(object):
|
||||
KEY = "moz:firefoxOptions"
|
||||
|
||||
def __init__(self):
|
||||
self._binary = None
|
||||
self._preferences = {}
|
||||
self._profile = None
|
||||
self._proxy = None
|
||||
self._arguments = []
|
||||
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 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 arguments(self):
|
||||
"""Returns a list of browser process arguments."""
|
||||
return self._arguments
|
||||
|
||||
def add_argument(self, argument):
|
||||
"""Add argument to be used for the browser process."""
|
||||
if argument is None:
|
||||
raise ValueError()
|
||||
self._arguments.append(argument)
|
||||
|
||||
@property
|
||||
def headless(self):
|
||||
"""
|
||||
Returns whether or not the headless argument is set
|
||||
"""
|
||||
return '-headless' in self._arguments
|
||||
|
||||
def set_headless(self, headless=True):
|
||||
"""
|
||||
Sets the headless argument
|
||||
|
||||
Args:
|
||||
headless: boolean value indicating to set the headless option
|
||||
"""
|
||||
if headless:
|
||||
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.
|
||||
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(opts)
|
||||
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:
|
||||
return {Options.KEY: opts}
|
||||
return {}
|
34
youtube_dl/selenium/webdriver/firefox/remote_connection.py
Normal file
34
youtube_dl/selenium/webdriver/firefox/remote_connection.py
Normal file
@ -0,0 +1,34 @@
|
||||
# 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")
|
54
youtube_dl/selenium/webdriver/firefox/service.py
Normal file
54
youtube_dl/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
|
265
youtube_dl/selenium/webdriver/firefox/webdriver.py
Normal file
265
youtube_dl/selenium/webdriver/firefox/webdriver.py
Normal file
@ -0,0 +1,265 @@
|
||||
# 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
|
||||
|
||||
try:
|
||||
import http.client as http_client
|
||||
except ImportError:
|
||||
import httplib as http_client
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError: # Python 3.x
|
||||
basestring = str
|
||||
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
|
||||
from .extension_connection import ExtensionConnection
|
||||
from .firefox_binary import FirefoxBinary
|
||||
from .firefox_profile import FirefoxProfile
|
||||
from .options import Options
|
||||
from .remote_connection import FirefoxRemoteConnection
|
||||
from .service import Service
|
||||
from .webelement import FirefoxWebElement
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
|
||||
# There is no native event support on Mac
|
||||
NATIVE_EVENTS_ALLOWED = sys.platform != "darwin"
|
||||
|
||||
CONTEXT_CHROME = "chrome"
|
||||
CONTEXT_CONTENT = "content"
|
||||
|
||||
_web_element_cls = FirefoxWebElement
|
||||
|
||||
def __init__(self, firefox_profile=None, firefox_binary=None,
|
||||
timeout=30, capabilities=None, proxy=None,
|
||||
executable_path="geckodriver", options=None,
|
||||
log_path="geckodriver.log", firefox_options=None,
|
||||
service_args=None):
|
||||
"""Starts a new local session of Firefox.
|
||||
|
||||
Based on the combination and specificity of the various keyword
|
||||
arguments, a capabilities dictionary will be constructed that
|
||||
is passed to the remote end.
|
||||
|
||||
The keyword arguments given to this constructor are helpers to
|
||||
more easily allow Firefox WebDriver sessions to be customised
|
||||
with different options. They are mapped on to a capabilities
|
||||
dictionary that is passed on to the remote end.
|
||||
|
||||
As some of the options, such as `firefox_profile` and
|
||||
`options.profile` are mutually exclusive, precedence is
|
||||
given from how specific the setting is. `capabilities` is the
|
||||
least specific keyword argument, followed by `options`,
|
||||
followed by `firefox_binary` and `firefox_profile`.
|
||||
|
||||
In practice this means that if `firefox_profile` and
|
||||
`options.profile` are both set, the selected profile
|
||||
instance will always come from the most specific variable.
|
||||
In this case that would be `firefox_profile`. This will result in
|
||||
`options.profile` to be ignored because it is considered
|
||||
a less specific setting than the top-level `firefox_profile`
|
||||
keyword argument. Similarily, if you had specified a
|
||||
`capabilities["moz:firefoxOptions"]["profile"]` Base64 string,
|
||||
this would rank below `options.profile`.
|
||||
|
||||
:param firefox_profile: Instance of ``FirefoxProfile`` object
|
||||
or a string. If undefined, a fresh profile will be created
|
||||
in a temporary location on the system.
|
||||
:param firefox_binary: Instance of ``FirefoxBinary`` or full
|
||||
path to the Firefox binary. If undefined, the system default
|
||||
Firefox installation will be used.
|
||||
:param timeout: Time to wait for Firefox to launch when using
|
||||
the extension connection.
|
||||
:param capabilities: Dictionary of desired capabilities.
|
||||
:param proxy: The proxy settings to us when communicating with
|
||||
Firefox via the extension connection.
|
||||
:param executable_path: Full path to override which geckodriver
|
||||
binary to use for Firefox 47.0.1 and greater, which
|
||||
defaults to picking up the binary from the system path.
|
||||
:param options: Instance of ``options.Options``.
|
||||
:param log_path: Where to log information from the driver.
|
||||
|
||||
"""
|
||||
if firefox_options:
|
||||
warnings.warn('use options instead of firefox_options', DeprecationWarning)
|
||||
options = firefox_options
|
||||
self.binary = None
|
||||
self.profile = None
|
||||
self.service = None
|
||||
|
||||
if capabilities is None:
|
||||
capabilities = DesiredCapabilities.FIREFOX.copy()
|
||||
if options is None:
|
||||
options = Options()
|
||||
|
||||
capabilities = dict(capabilities)
|
||||
|
||||
if capabilities.get("binary"):
|
||||
self.binary = capabilities["binary"]
|
||||
|
||||
# options overrides capabilities
|
||||
if options is not None:
|
||||
if options.binary is not None:
|
||||
self.binary = options.binary
|
||||
if options.profile is not None:
|
||||
self.profile = options.profile
|
||||
|
||||
# firefox_binary and firefox_profile
|
||||
# override options
|
||||
if firefox_binary is not None:
|
||||
if isinstance(firefox_binary, basestring):
|
||||
firefox_binary = FirefoxBinary(firefox_binary)
|
||||
self.binary = firefox_binary
|
||||
options.binary = firefox_binary
|
||||
if firefox_profile is not None:
|
||||
if isinstance(firefox_profile, basestring):
|
||||
firefox_profile = FirefoxProfile(firefox_profile)
|
||||
self.profile = firefox_profile
|
||||
options.profile = firefox_profile
|
||||
|
||||
# W3C remote
|
||||
# TODO(ato): Perform conformance negotiation
|
||||
|
||||
if capabilities.get("marionette"):
|
||||
capabilities.pop("marionette")
|
||||
self.service = Service(
|
||||
executable_path,
|
||||
service_args=service_args,
|
||||
log_path=log_path)
|
||||
self.service.start()
|
||||
|
||||
capabilities.update(options.to_capabilities())
|
||||
|
||||
executor = FirefoxRemoteConnection(
|
||||
remote_server_addr=self.service.service_url)
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=executor,
|
||||
desired_capabilities=capabilities,
|
||||
keep_alive=True)
|
||||
|
||||
# Selenium remote
|
||||
else:
|
||||
if self.binary is None:
|
||||
self.binary = FirefoxBinary()
|
||||
if self.profile is None:
|
||||
self.profile = FirefoxProfile()
|
||||
|
||||
# disable native events if globally disabled
|
||||
self.profile.native_events_enabled = (
|
||||
self.NATIVE_EVENTS_ALLOWED and self.profile.native_events_enabled)
|
||||
|
||||
if proxy is not None:
|
||||
proxy.add_to_capabilities(capabilities)
|
||||
|
||||
executor = ExtensionConnection("127.0.0.1", self.profile,
|
||||
self.binary, timeout)
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=executor,
|
||||
desired_capabilities=capabilities,
|
||||
keep_alive=True)
|
||||
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
"""Quits the driver and close every associated window."""
|
||||
try:
|
||||
RemoteWebDriver.quit(self)
|
||||
except (http_client.BadStatusLine, socket.error):
|
||||
# Happens if Firefox shutsdown before we've read the response from
|
||||
# the socket.
|
||||
pass
|
||||
|
||||
if self.w3c:
|
||||
self.service.stop()
|
||||
else:
|
||||
self.binary.kill()
|
||||
|
||||
if self.profile is not None:
|
||||
try:
|
||||
shutil.rmtree(self.profile.path)
|
||||
if self.profile.tempfolder is not None:
|
||||
shutil.rmtree(self.profile.tempfolder)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
@property
|
||||
def firefox_profile(self):
|
||||
return self.profile
|
||||
|
||||
# Extension commands:
|
||||
|
||||
def set_context(self, context):
|
||||
self.execute("SET_CONTEXT", {"context": context})
|
||||
|
||||
@contextmanager
|
||||
def context(self, context):
|
||||
"""Sets the context that Selenium commands are running in using
|
||||
a `with` statement. The state of the context on the server is
|
||||
saved before entering the block, and restored upon exiting it.
|
||||
|
||||
:param context: Context, may be one of the class properties
|
||||
`CONTEXT_CHROME` or `CONTEXT_CONTENT`.
|
||||
|
||||
Usage example::
|
||||
|
||||
with selenium.context(selenium.CONTEXT_CHROME):
|
||||
# chrome scope
|
||||
... do stuff ...
|
||||
"""
|
||||
initial_context = self.execute('GET_CONTEXT').pop('value')
|
||||
self.set_context(context)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.set_context(initial_context)
|
||||
|
||||
def install_addon(self, path, temporary=None):
|
||||
"""
|
||||
Installs Firefox addon.
|
||||
|
||||
Returns identifier of installed addon. This identifier can later
|
||||
be used to uninstall addon.
|
||||
|
||||
:param path: Absolute path to the addon that will be installed.
|
||||
|
||||
:Usage:
|
||||
driver.install_addon('/path/to/firebug.xpi')
|
||||
"""
|
||||
payload = {"path": path}
|
||||
if temporary is not None:
|
||||
payload["temporary"] = temporary
|
||||
return self.execute("INSTALL_ADDON", payload)["value"]
|
||||
|
||||
def uninstall_addon(self, identifier):
|
||||
"""
|
||||
Uninstalls Firefox addon using its identifier.
|
||||
|
||||
:Usage:
|
||||
driver.uninstall_addon('addon@foo.com')
|
||||
"""
|
||||
self.execute("UNINSTALL_ADDON", {"id": identifier})
|
BIN
youtube_dl/selenium/webdriver/firefox/webdriver.xpi
Normal file
BIN
youtube_dl/selenium/webdriver/firefox/webdriver.xpi
Normal file
Binary file not shown.
70
youtube_dl/selenium/webdriver/firefox/webdriver_prefs.json
Normal file
70
youtube_dl/selenium/webdriver/firefox/webdriver_prefs.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"frozen": {
|
||||
"app.update.auto": false,
|
||||
"app.update.enabled": false,
|
||||
"browser.displayedE10SNotice": 4,
|
||||
"browser.download.manager.showWhenStarting": false,
|
||||
"browser.EULA.override": true,
|
||||
"browser.EULA.3.accepted": true,
|
||||
"browser.link.open_external": 2,
|
||||
"browser.link.open_newwindow": 2,
|
||||
"browser.offline": false,
|
||||
"browser.reader.detectedFirstArticle": true,
|
||||
"browser.safebrowsing.enabled": false,
|
||||
"browser.safebrowsing.malware.enabled": false,
|
||||
"browser.search.update": false,
|
||||
"browser.selfsupport.url" : "",
|
||||
"browser.sessionstore.resume_from_crash": false,
|
||||
"browser.shell.checkDefaultBrowser": false,
|
||||
"browser.tabs.warnOnClose": false,
|
||||
"browser.tabs.warnOnOpen": false,
|
||||
"datareporting.healthreport.service.enabled": false,
|
||||
"datareporting.healthreport.uploadEnabled": false,
|
||||
"datareporting.healthreport.service.firstRun": false,
|
||||
"datareporting.healthreport.logging.consoleEnabled": false,
|
||||
"datareporting.policy.dataSubmissionEnabled": false,
|
||||
"datareporting.policy.dataSubmissionPolicyAccepted": false,
|
||||
"devtools.errorconsole.enabled": true,
|
||||
"dom.disable_open_during_load": false,
|
||||
"extensions.autoDisableScopes": 10,
|
||||
"extensions.blocklist.enabled": false,
|
||||
"extensions.checkCompatibility.nightly": false,
|
||||
"extensions.logging.enabled": true,
|
||||
"extensions.update.enabled": false,
|
||||
"extensions.update.notifyUser": false,
|
||||
"javascript.enabled": true,
|
||||
"network.manage-offline-status": false,
|
||||
"network.http.phishy-userpass-length": 255,
|
||||
"offline-apps.allow_by_default": true,
|
||||
"prompts.tab_modal.enabled": false,
|
||||
"security.fileuri.origin_policy": 3,
|
||||
"security.fileuri.strict_origin_policy": false,
|
||||
"signon.rememberSignons": false,
|
||||
"toolkit.networkmanager.disable": true,
|
||||
"toolkit.telemetry.prompted": 2,
|
||||
"toolkit.telemetry.enabled": false,
|
||||
"toolkit.telemetry.rejected": true,
|
||||
"xpinstall.signatures.required": false,
|
||||
"xpinstall.whitelist.required": false
|
||||
},
|
||||
"mutable": {
|
||||
"browser.dom.window.dump.enabled": true,
|
||||
"browser.laterrun.enabled": false,
|
||||
"browser.newtab.url": "about:blank",
|
||||
"browser.newtabpage.enabled": false,
|
||||
"browser.startup.page": 0,
|
||||
"browser.startup.homepage": "about:blank",
|
||||
"browser.startup.homepage_override.mstone": "ignore",
|
||||
"browser.usedOnWindows10.introURL": "about:blank",
|
||||
"dom.max_chrome_script_run_time": 30,
|
||||
"dom.max_script_run_time": 30,
|
||||
"dom.report_all_js_exceptions": true,
|
||||
"javascript.options.showInConsole": true,
|
||||
"network.captive-portal-service.enabled": false,
|
||||
"security.csp.enable": false,
|
||||
"startup.homepage_welcome_url": "about:blank",
|
||||
"startup.homepage_welcome_url.additional": "about:blank",
|
||||
"webdriver_accept_untrusted_certs": true,
|
||||
"webdriver_assume_untrusted_issuer": true
|
||||
}
|
||||
}
|
49
youtube_dl/selenium/webdriver/firefox/webelement.py
Normal file
49
youtube_dl/selenium/webdriver/firefox/webelement.py
Normal file
@ -0,0 +1,49 @@
|
||||
# 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.webelement import WebElement as RemoteWebElement
|
||||
|
||||
|
||||
class FirefoxWebElement(RemoteWebElement):
|
||||
|
||||
@property
|
||||
def anonymous_children(self):
|
||||
"""Retrieve the anonymous children of this element in an XBL
|
||||
context. This is only available in chrome context.
|
||||
|
||||
See the `anonymous content documentation
|
||||
<https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XBL/XBL_1.0_Reference/Anonymous_Content>`_
|
||||
on MDN for more information.
|
||||
|
||||
"""
|
||||
return self._execute(
|
||||
"ELEMENT_GET_ANONYMOUS_CHILDREN",
|
||||
{"value": None})
|
||||
|
||||
def find_anonymous_element_by_attribute(self, name, value):
|
||||
"""Retrieve an anonymous descendant with a specified attribute
|
||||
value. Typically used with an (arbitrary) anonid attribute to
|
||||
retrieve a specific anonymous child in an XBL binding.
|
||||
|
||||
See the `anonymous content documentation
|
||||
<https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XBL/XBL_1.0_Reference/Anonymous_Content>`_
|
||||
on MDN for more information.
|
||||
|
||||
"""
|
||||
return self._execute(
|
||||
"ELEMENT_FIND_ANONYMOUS_ELEMENTS_BY_ATTRIBUTE",
|
||||
{"name": name, "value": value})["value"]
|
BIN
youtube_dl/selenium/webdriver/firefox/x86/x_ignore_nofocus.so
Executable file
BIN
youtube_dl/selenium/webdriver/firefox/x86/x_ignore_nofocus.so
Executable file
Binary file not shown.
16
youtube_dl/selenium/webdriver/ie/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/ie/__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.
|
339
youtube_dl/selenium/webdriver/ie/options.py
Normal file
339
youtube_dl/selenium/webdriver/ie/options.py
Normal file
@ -0,0 +1,339 @@
|
||||
# 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 ElementScrollBehavior(object):
|
||||
TOP = 0
|
||||
BOTTOM = 1
|
||||
|
||||
|
||||
class Options(object):
|
||||
|
||||
KEY = 'se:ieOptions'
|
||||
SWITCHES = 'ie.browserCommandLineSwitches'
|
||||
|
||||
BROWSER_ATTACH_TIMEOUT = 'browserAttachTimeout'
|
||||
ELEMENT_SCROLL_BEHAVIOR = 'elementScrollBehavior'
|
||||
ENSURE_CLEAN_SESSION = 'ie.ensureCleanSession'
|
||||
FILE_UPLOAD_DIALOG_TIMEOUT = 'ie.fileUploadDialogTimeout'
|
||||
FORCE_CREATE_PROCESS_API = 'ie.forceCreateProcessApi'
|
||||
FORCE_SHELL_WINDOWS_API = 'ie.forceShellWindowsApi'
|
||||
FULL_PAGE_SCREENSHOT = 'ie.enableFullPageScreenshot'
|
||||
IGNORE_PROTECTED_MODE_SETTINGS = 'ignoreProtectedModeSettings'
|
||||
IGNORE_ZOOM_LEVEL = 'ignoreZoomSetting'
|
||||
INITIAL_BROWSER_URL = 'initialBrowserUrl'
|
||||
NATIVE_EVENTS = 'nativeEvents'
|
||||
PERSISTENT_HOVER = 'enablePersistentHover'
|
||||
REQUIRE_WINDOW_FOCUS = 'requireWindowFocus'
|
||||
USE_PER_PROCESS_PROXY = 'ie.usePerProcessProxy'
|
||||
VALIDATE_COOKIE_DOCUMENT_TYPE = 'ie.validateCookieDocumentType'
|
||||
|
||||
def __init__(self):
|
||||
self._arguments = []
|
||||
self._options = {}
|
||||
self._additional = {}
|
||||
|
||||
@property
|
||||
def arguments(self):
|
||||
""" Returns a list of browser process arguments """
|
||||
return self._arguments
|
||||
|
||||
def add_argument(self, argument):
|
||||
""" Add argument to be used for the browser process """
|
||||
if argument is None:
|
||||
raise ValueError()
|
||||
self._arguments.append(argument)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
""" Returns a dictionary of browser options """
|
||||
return self._options
|
||||
|
||||
@property
|
||||
def browser_attach_timeout(self):
|
||||
""" Returns the options Browser Attach Timeout in milliseconds """
|
||||
return self._options.get(self.BROWSER_ATTACH_TIMEOUT)
|
||||
|
||||
@browser_attach_timeout.setter
|
||||
def browser_attach_timeout(self, value):
|
||||
"""
|
||||
Sets the options Browser Attach Timeout
|
||||
|
||||
:Args:
|
||||
- value: Timeout in milliseconds
|
||||
|
||||
"""
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('Browser Attach Timeout must be an integer.')
|
||||
self._options[self.BROWSER_ATTACH_TIMEOUT] = value
|
||||
|
||||
@property
|
||||
def element_scroll_behavior(self):
|
||||
""" Returns the options Element Scroll Behavior in milliseconds """
|
||||
return self._options.get(self.ELEMENT_SCROLL_BEHAVIOR)
|
||||
|
||||
@element_scroll_behavior.setter
|
||||
def element_scroll_behavior(self, value):
|
||||
"""
|
||||
Sets the options Element Scroll Behavior
|
||||
|
||||
:Args:
|
||||
- value: 0 - Top, 1 - Bottom
|
||||
|
||||
"""
|
||||
if value not in [ElementScrollBehavior.TOP, ElementScrollBehavior.BOTTOM]:
|
||||
raise ValueError('Element Scroll Behavior out of range.')
|
||||
self._options[self.ELEMENT_SCROLL_BEHAVIOR] = value
|
||||
|
||||
@property
|
||||
def ensure_clean_session(self):
|
||||
""" Returns the options Ensure Clean Session value """
|
||||
return self._options.get(self.ENSURE_CLEAN_SESSION)
|
||||
|
||||
@ensure_clean_session.setter
|
||||
def ensure_clean_session(self, value):
|
||||
"""
|
||||
Sets the options Ensure Clean Session value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.ENSURE_CLEAN_SESSION] = value
|
||||
|
||||
@property
|
||||
def file_upload_dialog_timeout(self):
|
||||
""" Returns the options File Upload Dialog Timeout in milliseconds """
|
||||
return self._options.get(self.FILE_UPLOAD_DIALOG_TIMEOUT)
|
||||
|
||||
@file_upload_dialog_timeout.setter
|
||||
def file_upload_dialog_timeout(self, value):
|
||||
"""
|
||||
Sets the options File Upload Dialog Timeout value
|
||||
|
||||
:Args:
|
||||
- value: Timeout in milliseconds
|
||||
|
||||
"""
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('File Upload Dialog Timeout must be an integer.')
|
||||
self._options[self.FILE_UPLOAD_DIALOG_TIMEOUT] = value
|
||||
|
||||
@property
|
||||
def force_create_process_api(self):
|
||||
""" Returns the options Force Create Process Api value """
|
||||
return self._options.get(self.FORCE_CREATE_PROCESS_API)
|
||||
|
||||
@force_create_process_api.setter
|
||||
def force_create_process_api(self, value):
|
||||
"""
|
||||
Sets the options Force Create Process Api value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.FORCE_CREATE_PROCESS_API] = value
|
||||
|
||||
@property
|
||||
def force_shell_windows_api(self):
|
||||
""" Returns the options Force Shell Windows Api value """
|
||||
return self._options.get(self.FORCE_SHELL_WINDOWS_API)
|
||||
|
||||
@force_shell_windows_api.setter
|
||||
def force_shell_windows_api(self, value):
|
||||
"""
|
||||
Sets the options Force Shell Windows Api value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.FORCE_SHELL_WINDOWS_API] = value
|
||||
|
||||
@property
|
||||
def full_page_screenshot(self):
|
||||
""" Returns the options Full Page Screenshot value """
|
||||
return self._options.get(self.FULL_PAGE_SCREENSHOT)
|
||||
|
||||
@full_page_screenshot.setter
|
||||
def full_page_screenshot(self, value):
|
||||
"""
|
||||
Sets the options Full Page Screenshot value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.FULL_PAGE_SCREENSHOT] = value
|
||||
|
||||
@property
|
||||
def ignore_protected_mode_settings(self):
|
||||
""" Returns the options Ignore Protected Mode Settings value """
|
||||
return self._options.get(self.IGNORE_PROTECTED_MODE_SETTINGS)
|
||||
|
||||
@ignore_protected_mode_settings.setter
|
||||
def ignore_protected_mode_settings(self, value):
|
||||
"""
|
||||
Sets the options Ignore Protected Mode Settings value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.IGNORE_PROTECTED_MODE_SETTINGS] = value
|
||||
|
||||
@property
|
||||
def ignore_zoom_level(self):
|
||||
""" Returns the options Ignore Zoom Level value """
|
||||
return self._options.get(self.IGNORE_ZOOM_LEVEL)
|
||||
|
||||
@ignore_zoom_level.setter
|
||||
def ignore_zoom_level(self, value):
|
||||
"""
|
||||
Sets the options Ignore Zoom Level value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.IGNORE_ZOOM_LEVEL] = value
|
||||
|
||||
@property
|
||||
def initial_browser_url(self):
|
||||
""" Returns the options Initial Browser Url value """
|
||||
return self._options.get(self.INITIAL_BROWSER_URL)
|
||||
|
||||
@initial_browser_url.setter
|
||||
def initial_browser_url(self, value):
|
||||
"""
|
||||
Sets the options Initial Browser Url value
|
||||
|
||||
:Args:
|
||||
- value: URL string
|
||||
|
||||
"""
|
||||
self._options[self.INITIAL_BROWSER_URL] = value
|
||||
|
||||
@property
|
||||
def native_events(self):
|
||||
""" Returns the options Native Events value """
|
||||
return self._options.get(self.NATIVE_EVENTS)
|
||||
|
||||
@native_events.setter
|
||||
def native_events(self, value):
|
||||
"""
|
||||
Sets the options Native Events value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.NATIVE_EVENTS] = value
|
||||
|
||||
@property
|
||||
def persistent_hover(self):
|
||||
""" Returns the options Persistent Hover value """
|
||||
return self._options.get(self.PERSISTENT_HOVER)
|
||||
|
||||
@persistent_hover.setter
|
||||
def persistent_hover(self, value):
|
||||
"""
|
||||
Sets the options Persistent Hover value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.PERSISTENT_HOVER] = value
|
||||
|
||||
@property
|
||||
def require_window_focus(self):
|
||||
""" Returns the options Require Window Focus value """
|
||||
return self._options.get(self.REQUIRE_WINDOW_FOCUS)
|
||||
|
||||
@require_window_focus.setter
|
||||
def require_window_focus(self, value):
|
||||
"""
|
||||
Sets the options Require Window Focus value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.REQUIRE_WINDOW_FOCUS] = value
|
||||
|
||||
@property
|
||||
def use_per_process_proxy(self):
|
||||
""" Returns the options User Per Process Proxy value """
|
||||
return self._options.get(self.USE_PER_PROCESS_PROXY)
|
||||
|
||||
@use_per_process_proxy.setter
|
||||
def use_per_process_proxy(self, value):
|
||||
"""
|
||||
Sets the options User Per Process Proxy value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.USE_PER_PROCESS_PROXY] = value
|
||||
|
||||
@property
|
||||
def validate_cookie_document_type(self):
|
||||
""" Returns the options Validate Cookie Document Type value """
|
||||
return self._options.get(self.VALIDATE_COOKIE_DOCUMENT_TYPE)
|
||||
|
||||
@validate_cookie_document_type.setter
|
||||
def validate_cookie_document_type(self, value):
|
||||
"""
|
||||
Sets the options Validate Cookie Document Type value
|
||||
|
||||
:Args:
|
||||
- value: boolean value
|
||||
|
||||
"""
|
||||
self._options[self.VALIDATE_COOKIE_DOCUMENT_TYPE] = value
|
||||
|
||||
@property
|
||||
def additional_options(self):
|
||||
""" Returns the additional options """
|
||||
return self._additional
|
||||
|
||||
def add_additional_option(self, name, value):
|
||||
"""
|
||||
Adds an additional option not yet added as a safe option for IE
|
||||
|
||||
:Args:
|
||||
- name: name of the option to add
|
||||
- value: value of the option to add
|
||||
|
||||
"""
|
||||
self._additional[name] = value
|
||||
|
||||
def to_capabilities(self):
|
||||
""" Marshals the IE options to a the correct object """
|
||||
opts = self._options.copy()
|
||||
if len(self._arguments) > 0:
|
||||
opts[self.SWITCHES] = ' '.join(self._arguments)
|
||||
|
||||
if len(self._additional) > 0:
|
||||
opts.update(self._additional)
|
||||
|
||||
if len(opts) > 0:
|
||||
return {self.KEY: opts}
|
||||
return {}
|
50
youtube_dl/selenium/webdriver/ie/service.py
Normal file
50
youtube_dl/selenium/webdriver/ie/service.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 selenium.webdriver.common import service
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
"""
|
||||
Object that manages the starting and stopping of the IEDriver
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path, port=0, host=None, log_level=None, log_file=None):
|
||||
"""
|
||||
Creates a new instance of the Service
|
||||
|
||||
:Args:
|
||||
- executable_path : Path to the IEDriver
|
||||
- port : Port the service is running on
|
||||
- host : IP address the service port is bound
|
||||
- log_level : Level of logging of service, may be "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE".
|
||||
Default is "FATAL".
|
||||
- log_file : Target of logging of service, may be "stdout", "stderr" or file path.
|
||||
Default is "stdout"."""
|
||||
self.service_args = []
|
||||
if host is not None:
|
||||
self.service_args.append("--host=%s" % host)
|
||||
if log_level is not None:
|
||||
self.service_args.append("--log-level=%s" % log_level)
|
||||
if log_file is not None:
|
||||
self.service_args.append("--log-file=%s" % log_file)
|
||||
|
||||
service.Service.__init__(self, executable_path, port=port,
|
||||
start_error_message="Please download from http://selenium-release.storage.googleapis.com/index.html and read up at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver")
|
||||
|
||||
def command_line_args(self):
|
||||
return ["--port=%d" % self.port] + self.service_args
|
95
youtube_dl/selenium/webdriver/ie/webdriver.py
Normal file
95
youtube_dl/selenium/webdriver/ie/webdriver.py
Normal file
@ -0,0 +1,95 @@
|
||||
# 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 import utils
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from .service import Service
|
||||
from .options import Options
|
||||
|
||||
DEFAULT_TIMEOUT = 30
|
||||
DEFAULT_PORT = 0
|
||||
DEFAULT_HOST = None
|
||||
DEFAULT_LOG_LEVEL = None
|
||||
DEFAULT_LOG_FILE = None
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
""" Controls the IEServerDriver and allows you to drive Internet Explorer """
|
||||
|
||||
def __init__(self, executable_path='IEDriverServer.exe', capabilities=None,
|
||||
port=DEFAULT_PORT, timeout=DEFAULT_TIMEOUT, host=DEFAULT_HOST,
|
||||
log_level=DEFAULT_LOG_LEVEL, log_file=DEFAULT_LOG_FILE, options=None,
|
||||
ie_options=None):
|
||||
"""
|
||||
Creates a new instance of the chrome driver.
|
||||
|
||||
Starts the service and then creates new instance of chrome driver.
|
||||
|
||||
:Args:
|
||||
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
|
||||
- capabilities: capabilities Dictionary object
|
||||
- port - port you would like the service to run, if left as 0, a free port will be found.
|
||||
- log_level - log level you would like the service to run.
|
||||
- log_file - log file you would like the service to log to.
|
||||
- options: IE Options instance, providing additional IE options
|
||||
"""
|
||||
if ie_options:
|
||||
warnings.warn('use options instead of ie_options', DeprecationWarning)
|
||||
options = ie_options
|
||||
self.port = port
|
||||
if self.port == 0:
|
||||
self.port = utils.free_port()
|
||||
self.host = host
|
||||
self.log_level = log_level
|
||||
self.log_file = log_file
|
||||
|
||||
if options is None:
|
||||
# desired_capabilities stays as passed in
|
||||
if capabilities is None:
|
||||
capabilities = self.create_options().to_capabilities()
|
||||
else:
|
||||
if capabilities is None:
|
||||
capabilities = options.to_capabilities()
|
||||
else:
|
||||
capabilities.update(options.to_capabilities())
|
||||
|
||||
self.iedriver = Service(
|
||||
executable_path,
|
||||
port=self.port,
|
||||
host=self.host,
|
||||
log_level=self.log_level,
|
||||
log_file=self.log_file)
|
||||
|
||||
self.iedriver.start()
|
||||
|
||||
if capabilities is None:
|
||||
capabilities = DesiredCapabilities.INTERNETEXPLORER
|
||||
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor='http://localhost:%d' % self.port,
|
||||
desired_capabilities=capabilities)
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
RemoteWebDriver.quit(self)
|
||||
self.iedriver.stop()
|
||||
|
||||
def create_options(self):
|
||||
return Options()
|
16
youtube_dl/selenium/webdriver/opera/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/opera/__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.
|
106
youtube_dl/selenium/webdriver/opera/options.py
Normal file
106
youtube_dl/selenium/webdriver/opera/options.py
Normal file
@ -0,0 +1,106 @@
|
||||
# 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.chrome.options import Options as ChromeOptions
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
|
||||
|
||||
class Options(ChromeOptions):
|
||||
KEY = "operaOptions"
|
||||
|
||||
def __init__(self):
|
||||
ChromeOptions.__init__(self)
|
||||
self._android_package_name = ''
|
||||
self._android_device_socket = ''
|
||||
self._android_command_line_file = ''
|
||||
|
||||
@property
|
||||
def android_package_name(self):
|
||||
"""
|
||||
Returns the name of the Opera package
|
||||
"""
|
||||
return self._android_package_name
|
||||
|
||||
@android_package_name.setter
|
||||
def android_package_name(self, value):
|
||||
"""
|
||||
Allows you to set the package name
|
||||
|
||||
:Args:
|
||||
- value: devtools socket name
|
||||
"""
|
||||
self._android_package_name = value
|
||||
|
||||
@property
|
||||
def android_device_socket(self):
|
||||
"""
|
||||
Returns the name of the devtools socket
|
||||
"""
|
||||
return self._android_device_socket
|
||||
|
||||
@android_device_socket.setter
|
||||
def android_device_socket(self, value):
|
||||
"""
|
||||
Allows you to set the devtools socket name
|
||||
|
||||
:Args:
|
||||
- value: devtools socket name
|
||||
"""
|
||||
self._android_device_socket = value
|
||||
|
||||
@property
|
||||
def android_command_line_file(self):
|
||||
"""
|
||||
Returns the path of the command line file
|
||||
"""
|
||||
return self._android_command_line_file
|
||||
|
||||
@android_command_line_file.setter
|
||||
def android_command_line_file(self, value):
|
||||
"""
|
||||
Allows you to set where the command line file lives
|
||||
|
||||
:Args:
|
||||
- value: command line file path
|
||||
"""
|
||||
self._android_command_line_file = value
|
||||
|
||||
def to_capabilities(self):
|
||||
"""
|
||||
Creates a capabilities with all the options that have been set and
|
||||
|
||||
returns a dictionary with everything
|
||||
"""
|
||||
capabilities = ChromeOptions.to_capabilities(self)
|
||||
capabilities.update(DesiredCapabilities.OPERA)
|
||||
opera_options = capabilities[self.KEY]
|
||||
|
||||
if self.android_package_name:
|
||||
opera_options["androidPackage"] = self.android_package_name
|
||||
if self.android_device_socket:
|
||||
opera_options["androidDeviceSocket"] = self.android_device_socket
|
||||
if self.android_command_line_file:
|
||||
opera_options["androidCommandLineFile"] = \
|
||||
self.android_command_line_file
|
||||
return capabilities
|
||||
|
||||
|
||||
class AndroidOptions(Options):
|
||||
|
||||
def __init__(self):
|
||||
Options.__init__(self)
|
||||
self.android_package_name = 'com.opera.browser'
|
78
youtube_dl/selenium/webdriver/opera/webdriver.py
Normal file
78
youtube_dl/selenium/webdriver/opera/webdriver.py
Normal file
@ -0,0 +1,78 @@
|
||||
# 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.chrome.webdriver import WebDriver as ChromiumDriver
|
||||
from .options import Options
|
||||
|
||||
|
||||
class OperaDriver(ChromiumDriver):
|
||||
"""Controls the new OperaDriver and allows you
|
||||
to drive the Opera browser based on Chromium."""
|
||||
|
||||
def __init__(self, executable_path=None, port=0,
|
||||
options=None, service_args=None,
|
||||
desired_capabilities=None, service_log_path=None,
|
||||
opera_options=None):
|
||||
"""
|
||||
Creates a new instance of the operadriver.
|
||||
|
||||
Starts the service and then creates new instance of operadriver.
|
||||
|
||||
:Args:
|
||||
- executable_path - path to the executable. If the default is used
|
||||
it assumes the executable is in the $PATH
|
||||
- port - port you would like the service to run, if left as 0,
|
||||
a free port will be found.
|
||||
- desired_capabilities: Dictionary object with non-browser specific
|
||||
capabilities only, such as "proxy" or "loggingPref".
|
||||
- options: this takes an instance of ChromeOptions
|
||||
"""
|
||||
if opera_options:
|
||||
warnings.warn('use options instead of opera_options', DeprecationWarning)
|
||||
options = opera_options
|
||||
|
||||
executable_path = (executable_path if executable_path is not None
|
||||
else "operadriver")
|
||||
ChromiumDriver.__init__(self,
|
||||
executable_path=executable_path,
|
||||
port=port,
|
||||
options=options,
|
||||
service_args=service_args,
|
||||
desired_capabilities=desired_capabilities,
|
||||
service_log_path=service_log_path)
|
||||
|
||||
def create_options(self):
|
||||
return Options()
|
||||
|
||||
|
||||
class WebDriver(OperaDriver):
|
||||
class ServiceType:
|
||||
CHROMIUM = 2
|
||||
|
||||
def __init__(self,
|
||||
desired_capabilities=None,
|
||||
executable_path=None,
|
||||
port=0,
|
||||
service_log_path=None,
|
||||
service_args=None,
|
||||
options=None):
|
||||
OperaDriver.__init__(self, executable_path=executable_path,
|
||||
port=port, options=options,
|
||||
service_args=service_args,
|
||||
desired_capabilities=desired_capabilities,
|
||||
service_log_path=service_log_path)
|
16
youtube_dl/selenium/webdriver/phantomjs/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/phantomjs/__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.
|
68
youtube_dl/selenium/webdriver/phantomjs/service.py
Normal file
68
youtube_dl/selenium/webdriver/phantomjs/service.py
Normal file
@ -0,0 +1,68 @@
|
||||
# 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 tempfile
|
||||
from selenium.webdriver.common import service
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
"""
|
||||
Object that manages the starting and stopping of PhantomJS / Ghostdriver
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path, port=0, service_args=None, log_path=None):
|
||||
"""
|
||||
Creates a new instance of the Service
|
||||
|
||||
:Args:
|
||||
- executable_path : Path to PhantomJS binary
|
||||
- port : Port the service is running on
|
||||
- service_args : A List of other command line options to pass to PhantomJS
|
||||
- log_path: Path for PhantomJS service to log to
|
||||
"""
|
||||
self.service_args = service_args
|
||||
if self.service_args is None:
|
||||
self.service_args = []
|
||||
else:
|
||||
self.service_args = service_args[:]
|
||||
if not log_path:
|
||||
log_path = "ghostdriver.log"
|
||||
if not self._args_contain("--cookies-file="):
|
||||
self._cookie_temp_file_handle, self._cookie_temp_file = tempfile.mkstemp()
|
||||
self.service_args.append("--cookies-file=" + self._cookie_temp_file)
|
||||
else:
|
||||
self._cookie_temp_file = None
|
||||
|
||||
service.Service.__init__(self, executable_path, port=port, log_file=open(log_path, 'w'))
|
||||
|
||||
def _args_contain(self, arg):
|
||||
return len(list(filter(lambda x: x.startswith(arg), self.service_args))) > 0
|
||||
|
||||
def command_line_args(self):
|
||||
return self.service_args + ["--webdriver=%d" % self.port]
|
||||
|
||||
@property
|
||||
def service_url(self):
|
||||
"""
|
||||
Gets the url of the GhostDriver Service
|
||||
"""
|
||||
return "http://localhost:%d/wd/hub" % self.port
|
||||
|
||||
def send_remote_shutdown_command(self):
|
||||
if self._cookie_temp_file:
|
||||
os.close(self._cookie_temp_file_handle)
|
||||
os.remove(self._cookie_temp_file)
|
80
youtube_dl/selenium/webdriver/phantomjs/webdriver.py
Normal file
80
youtube_dl/selenium/webdriver/phantomjs/webdriver.py
Normal file
@ -0,0 +1,80 @@
|
||||
# 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.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from .service import Service
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
"""
|
||||
Wrapper to communicate with PhantomJS through Ghostdriver.
|
||||
|
||||
You will need to follow all the directions here:
|
||||
https://github.com/detro/ghostdriver
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path="phantomjs",
|
||||
port=0, desired_capabilities=DesiredCapabilities.PHANTOMJS,
|
||||
service_args=None, service_log_path=None):
|
||||
"""
|
||||
Creates a new instance of the PhantomJS / Ghostdriver.
|
||||
|
||||
Starts the service and then creates new instance of the driver.
|
||||
|
||||
:Args:
|
||||
- executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
|
||||
- port - port you would like the service to run, if left as 0, a free port will be found.
|
||||
- desired_capabilities: Dictionary object with non-browser specific
|
||||
capabilities only, such as "proxy" or "loggingPref".
|
||||
- service_args : A List of command line arguments to pass to PhantomJS
|
||||
- service_log_path: Path for phantomjs service to log to.
|
||||
"""
|
||||
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '
|
||||
'versions of Chrome or Firefox instead')
|
||||
self.service = Service(
|
||||
executable_path,
|
||||
port=port,
|
||||
service_args=service_args,
|
||||
log_path=service_log_path)
|
||||
self.service.start()
|
||||
|
||||
try:
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=self.service.service_url,
|
||||
desired_capabilities=desired_capabilities)
|
||||
except Exception:
|
||||
self.quit()
|
||||
raise
|
||||
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
"""
|
||||
Closes the browser and shuts down the PhantomJS executable
|
||||
that is started when starting the PhantomJS
|
||||
"""
|
||||
try:
|
||||
RemoteWebDriver.quit(self)
|
||||
except Exception:
|
||||
# We don't care about the message because something probably has gone wrong
|
||||
pass
|
||||
finally:
|
||||
self.service.stop()
|
16
youtube_dl/selenium/webdriver/remote/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/remote/__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.
|
174
youtube_dl/selenium/webdriver/remote/command.py
Normal file
174
youtube_dl/selenium/webdriver/remote/command.py
Normal file
@ -0,0 +1,174 @@
|
||||
# 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 Command(object):
|
||||
"""
|
||||
Defines constants for the standard WebDriver commands.
|
||||
|
||||
While these constants have no meaning in and of themselves, they are
|
||||
used to marshal commands through a service that implements WebDriver's
|
||||
remote wire protocol:
|
||||
|
||||
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
|
||||
|
||||
"""
|
||||
|
||||
# Keep in sync with org.openqa.selenium.remote.DriverCommand
|
||||
|
||||
STATUS = "status"
|
||||
NEW_SESSION = "newSession"
|
||||
GET_ALL_SESSIONS = "getAllSessions"
|
||||
DELETE_SESSION = "deleteSession"
|
||||
CLOSE = "close"
|
||||
QUIT = "quit"
|
||||
GET = "get"
|
||||
GO_BACK = "goBack"
|
||||
GO_FORWARD = "goForward"
|
||||
REFRESH = "refresh"
|
||||
ADD_COOKIE = "addCookie"
|
||||
GET_COOKIE = "getCookie"
|
||||
GET_ALL_COOKIES = "getCookies"
|
||||
DELETE_COOKIE = "deleteCookie"
|
||||
DELETE_ALL_COOKIES = "deleteAllCookies"
|
||||
FIND_ELEMENT = "findElement"
|
||||
FIND_ELEMENTS = "findElements"
|
||||
FIND_CHILD_ELEMENT = "findChildElement"
|
||||
FIND_CHILD_ELEMENTS = "findChildElements"
|
||||
CLEAR_ELEMENT = "clearElement"
|
||||
CLICK_ELEMENT = "clickElement"
|
||||
SEND_KEYS_TO_ELEMENT = "sendKeysToElement"
|
||||
SEND_KEYS_TO_ACTIVE_ELEMENT = "sendKeysToActiveElement"
|
||||
SUBMIT_ELEMENT = "submitElement"
|
||||
UPLOAD_FILE = "uploadFile"
|
||||
GET_CURRENT_WINDOW_HANDLE = "getCurrentWindowHandle"
|
||||
W3C_GET_CURRENT_WINDOW_HANDLE = "w3cGetCurrentWindowHandle"
|
||||
GET_WINDOW_HANDLES = "getWindowHandles"
|
||||
W3C_GET_WINDOW_HANDLES = "w3cGetWindowHandles"
|
||||
GET_WINDOW_SIZE = "getWindowSize"
|
||||
W3C_GET_WINDOW_SIZE = "w3cGetWindowSize"
|
||||
W3C_GET_WINDOW_POSITION = "w3cGetWindowPosition"
|
||||
GET_WINDOW_POSITION = "getWindowPosition"
|
||||
SET_WINDOW_SIZE = "setWindowSize"
|
||||
W3C_SET_WINDOW_SIZE = "w3cSetWindowSize"
|
||||
SET_WINDOW_RECT = "setWindowRect"
|
||||
GET_WINDOW_RECT = "getWindowRect"
|
||||
SET_WINDOW_POSITION = "setWindowPosition"
|
||||
W3C_SET_WINDOW_POSITION = "w3cSetWindowPosition"
|
||||
SWITCH_TO_WINDOW = "switchToWindow"
|
||||
SWITCH_TO_FRAME = "switchToFrame"
|
||||
SWITCH_TO_PARENT_FRAME = "switchToParentFrame"
|
||||
GET_ACTIVE_ELEMENT = "getActiveElement"
|
||||
W3C_GET_ACTIVE_ELEMENT = "w3cGetActiveElement"
|
||||
GET_CURRENT_URL = "getCurrentUrl"
|
||||
GET_PAGE_SOURCE = "getPageSource"
|
||||
GET_TITLE = "getTitle"
|
||||
EXECUTE_SCRIPT = "executeScript"
|
||||
W3C_EXECUTE_SCRIPT = "w3cExecuteScript"
|
||||
W3C_EXECUTE_SCRIPT_ASYNC = "w3cExecuteScriptAsync"
|
||||
GET_ELEMENT_TEXT = "getElementText"
|
||||
GET_ELEMENT_VALUE = "getElementValue"
|
||||
GET_ELEMENT_TAG_NAME = "getElementTagName"
|
||||
SET_ELEMENT_SELECTED = "setElementSelected"
|
||||
IS_ELEMENT_SELECTED = "isElementSelected"
|
||||
IS_ELEMENT_ENABLED = "isElementEnabled"
|
||||
IS_ELEMENT_DISPLAYED = "isElementDisplayed"
|
||||
GET_ELEMENT_LOCATION = "getElementLocation"
|
||||
GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = "getElementLocationOnceScrolledIntoView"
|
||||
GET_ELEMENT_SIZE = "getElementSize"
|
||||
GET_ELEMENT_RECT = "getElementRect"
|
||||
GET_ELEMENT_ATTRIBUTE = "getElementAttribute"
|
||||
GET_ELEMENT_PROPERTY = "getElementProperty"
|
||||
GET_ELEMENT_VALUE_OF_CSS_PROPERTY = "getElementValueOfCssProperty"
|
||||
ELEMENT_EQUALS = "elementEquals"
|
||||
SCREENSHOT = "screenshot"
|
||||
ELEMENT_SCREENSHOT = "elementScreenshot"
|
||||
IMPLICIT_WAIT = "implicitlyWait"
|
||||
EXECUTE_ASYNC_SCRIPT = "executeAsyncScript"
|
||||
SET_SCRIPT_TIMEOUT = "setScriptTimeout"
|
||||
SET_TIMEOUTS = "setTimeouts"
|
||||
MAXIMIZE_WINDOW = "windowMaximize"
|
||||
W3C_MAXIMIZE_WINDOW = "w3cMaximizeWindow"
|
||||
GET_LOG = "getLog"
|
||||
GET_AVAILABLE_LOG_TYPES = "getAvailableLogTypes"
|
||||
FULLSCREEN_WINDOW = "fullscreenWindow"
|
||||
MINIMIZE_WINDOW = "minimizeWindow"
|
||||
|
||||
# Alerts
|
||||
DISMISS_ALERT = "dismissAlert"
|
||||
W3C_DISMISS_ALERT = "w3cDismissAlert"
|
||||
ACCEPT_ALERT = "acceptAlert"
|
||||
W3C_ACCEPT_ALERT = "w3cAcceptAlert"
|
||||
SET_ALERT_VALUE = "setAlertValue"
|
||||
W3C_SET_ALERT_VALUE = "w3cSetAlertValue"
|
||||
GET_ALERT_TEXT = "getAlertText"
|
||||
W3C_GET_ALERT_TEXT = "w3cGetAlertText"
|
||||
SET_ALERT_CREDENTIALS = "setAlertCredentials"
|
||||
|
||||
# Advanced user interactions
|
||||
W3C_ACTIONS = "actions"
|
||||
W3C_CLEAR_ACTIONS = "clearActionState"
|
||||
CLICK = "mouseClick"
|
||||
DOUBLE_CLICK = "mouseDoubleClick"
|
||||
MOUSE_DOWN = "mouseButtonDown"
|
||||
MOUSE_UP = "mouseButtonUp"
|
||||
MOVE_TO = "mouseMoveTo"
|
||||
|
||||
# Screen Orientation
|
||||
SET_SCREEN_ORIENTATION = "setScreenOrientation"
|
||||
GET_SCREEN_ORIENTATION = "getScreenOrientation"
|
||||
|
||||
# Touch Actions
|
||||
SINGLE_TAP = "touchSingleTap"
|
||||
TOUCH_DOWN = "touchDown"
|
||||
TOUCH_UP = "touchUp"
|
||||
TOUCH_MOVE = "touchMove"
|
||||
TOUCH_SCROLL = "touchScroll"
|
||||
DOUBLE_TAP = "touchDoubleTap"
|
||||
LONG_PRESS = "touchLongPress"
|
||||
FLICK = "touchFlick"
|
||||
|
||||
# HTML 5
|
||||
EXECUTE_SQL = "executeSql"
|
||||
|
||||
GET_LOCATION = "getLocation"
|
||||
SET_LOCATION = "setLocation"
|
||||
|
||||
GET_APP_CACHE = "getAppCache"
|
||||
GET_APP_CACHE_STATUS = "getAppCacheStatus"
|
||||
CLEAR_APP_CACHE = "clearAppCache"
|
||||
|
||||
GET_LOCAL_STORAGE_ITEM = "getLocalStorageItem"
|
||||
REMOVE_LOCAL_STORAGE_ITEM = "removeLocalStorageItem"
|
||||
GET_LOCAL_STORAGE_KEYS = "getLocalStorageKeys"
|
||||
SET_LOCAL_STORAGE_ITEM = "setLocalStorageItem"
|
||||
CLEAR_LOCAL_STORAGE = "clearLocalStorage"
|
||||
GET_LOCAL_STORAGE_SIZE = "getLocalStorageSize"
|
||||
|
||||
GET_SESSION_STORAGE_ITEM = "getSessionStorageItem"
|
||||
REMOVE_SESSION_STORAGE_ITEM = "removeSessionStorageItem"
|
||||
GET_SESSION_STORAGE_KEYS = "getSessionStorageKeys"
|
||||
SET_SESSION_STORAGE_ITEM = "setSessionStorageItem"
|
||||
CLEAR_SESSION_STORAGE = "clearSessionStorage"
|
||||
GET_SESSION_STORAGE_SIZE = "getSessionStorageSize"
|
||||
|
||||
# Mobile
|
||||
GET_NETWORK_CONNECTION = "getNetworkConnection"
|
||||
SET_NETWORK_CONNECTION = "setNetworkConnection"
|
||||
CURRENT_CONTEXT_HANDLE = "getCurrentContextHandle"
|
||||
CONTEXT_HANDLES = "getContextHandles"
|
||||
SWITCH_TO_CONTEXT = "switchToContext"
|
245
youtube_dl/selenium/webdriver/remote/errorhandler.py
Normal file
245
youtube_dl/selenium/webdriver/remote/errorhandler.py
Normal file
@ -0,0 +1,245 @@
|
||||
# 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 (ElementClickInterceptedException,
|
||||
ElementNotInteractableException,
|
||||
ElementNotSelectableException,
|
||||
ElementNotVisibleException,
|
||||
ErrorInResponseException,
|
||||
InsecureCertificateException,
|
||||
InvalidCoordinatesException,
|
||||
InvalidElementStateException,
|
||||
InvalidSessionIdException,
|
||||
InvalidSelectorException,
|
||||
ImeNotAvailableException,
|
||||
ImeActivationFailedException,
|
||||
InvalidArgumentException,
|
||||
InvalidCookieDomainException,
|
||||
JavascriptException,
|
||||
MoveTargetOutOfBoundsException,
|
||||
NoSuchCookieException,
|
||||
NoSuchElementException,
|
||||
NoSuchFrameException,
|
||||
NoSuchWindowException,
|
||||
NoAlertPresentException,
|
||||
ScreenshotException,
|
||||
SessionNotCreatedException,
|
||||
StaleElementReferenceException,
|
||||
TimeoutException,
|
||||
UnableToSetCookieException,
|
||||
UnexpectedAlertPresentException,
|
||||
UnknownMethodException,
|
||||
WebDriverException)
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError: # Python 3.x
|
||||
basestring = str
|
||||
|
||||
|
||||
class ErrorCode(object):
|
||||
"""
|
||||
Error codes defined in the WebDriver wire protocol.
|
||||
"""
|
||||
# Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h
|
||||
SUCCESS = 0
|
||||
NO_SUCH_ELEMENT = [7, 'no such element']
|
||||
NO_SUCH_FRAME = [8, 'no such frame']
|
||||
UNKNOWN_COMMAND = [9, 'unknown command']
|
||||
STALE_ELEMENT_REFERENCE = [10, 'stale element reference']
|
||||
ELEMENT_NOT_VISIBLE = [11, 'element not visible']
|
||||
INVALID_ELEMENT_STATE = [12, 'invalid element state']
|
||||
UNKNOWN_ERROR = [13, 'unknown error']
|
||||
ELEMENT_IS_NOT_SELECTABLE = [15, 'element not selectable']
|
||||
JAVASCRIPT_ERROR = [17, 'javascript error']
|
||||
XPATH_LOOKUP_ERROR = [19, 'invalid selector']
|
||||
TIMEOUT = [21, 'timeout']
|
||||
NO_SUCH_WINDOW = [23, 'no such window']
|
||||
INVALID_COOKIE_DOMAIN = [24, 'invalid cookie domain']
|
||||
UNABLE_TO_SET_COOKIE = [25, 'unable to set cookie']
|
||||
UNEXPECTED_ALERT_OPEN = [26, 'unexpected alert open']
|
||||
NO_ALERT_OPEN = [27, 'no such alert']
|
||||
SCRIPT_TIMEOUT = [28, 'script timeout']
|
||||
INVALID_ELEMENT_COORDINATES = [29, 'invalid element coordinates']
|
||||
IME_NOT_AVAILABLE = [30, 'ime not available']
|
||||
IME_ENGINE_ACTIVATION_FAILED = [31, 'ime engine activation failed']
|
||||
INVALID_SELECTOR = [32, 'invalid selector']
|
||||
SESSION_NOT_CREATED = [33, 'session not created']
|
||||
MOVE_TARGET_OUT_OF_BOUNDS = [34, 'move target out of bounds']
|
||||
INVALID_XPATH_SELECTOR = [51, 'invalid selector']
|
||||
INVALID_XPATH_SELECTOR_RETURN_TYPER = [52, 'invalid selector']
|
||||
|
||||
ELEMENT_NOT_INTERACTABLE = [60, 'element not interactable']
|
||||
INSECURE_CERTIFICATE = ['insecure certificate']
|
||||
INVALID_ARGUMENT = [61, 'invalid argument']
|
||||
INVALID_COORDINATES = ['invalid coordinates']
|
||||
INVALID_SESSION_ID = ['invalid session id']
|
||||
NO_SUCH_COOKIE = [62, 'no such cookie']
|
||||
UNABLE_TO_CAPTURE_SCREEN = [63, 'unable to capture screen']
|
||||
ELEMENT_CLICK_INTERCEPTED = [64, 'element click intercepted']
|
||||
UNKNOWN_METHOD = ['unknown method exception']
|
||||
|
||||
METHOD_NOT_ALLOWED = [405, 'unsupported operation']
|
||||
|
||||
|
||||
class ErrorHandler(object):
|
||||
"""
|
||||
Handles errors returned by the WebDriver server.
|
||||
"""
|
||||
def check_response(self, response):
|
||||
"""
|
||||
Checks that a JSON response from the WebDriver does not have an error.
|
||||
|
||||
:Args:
|
||||
- response - The JSON response from the WebDriver server as a dictionary
|
||||
object.
|
||||
|
||||
:Raises: If the response contains an error message.
|
||||
"""
|
||||
status = response.get('status', None)
|
||||
if status is None or status == ErrorCode.SUCCESS:
|
||||
return
|
||||
value = None
|
||||
message = response.get("message", "")
|
||||
screen = response.get("screen", "")
|
||||
stacktrace = None
|
||||
if isinstance(status, int):
|
||||
value_json = response.get('value', None)
|
||||
if value_json and isinstance(value_json, basestring):
|
||||
import json
|
||||
try:
|
||||
value = json.loads(value_json)
|
||||
if len(value.keys()) == 1:
|
||||
value = value['value']
|
||||
status = value.get('error', None)
|
||||
if status is None:
|
||||
status = value["status"]
|
||||
message = value["value"]
|
||||
if not isinstance(message, basestring):
|
||||
value = message
|
||||
message = message.get('message')
|
||||
else:
|
||||
message = value.get('message', None)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
exception_class = ErrorInResponseException
|
||||
if status in ErrorCode.NO_SUCH_ELEMENT:
|
||||
exception_class = NoSuchElementException
|
||||
elif status in ErrorCode.NO_SUCH_FRAME:
|
||||
exception_class = NoSuchFrameException
|
||||
elif status in ErrorCode.NO_SUCH_WINDOW:
|
||||
exception_class = NoSuchWindowException
|
||||
elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
|
||||
exception_class = StaleElementReferenceException
|
||||
elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
|
||||
exception_class = ElementNotVisibleException
|
||||
elif status in ErrorCode.INVALID_ELEMENT_STATE:
|
||||
exception_class = InvalidElementStateException
|
||||
elif status in ErrorCode.INVALID_SELECTOR \
|
||||
or status in ErrorCode.INVALID_XPATH_SELECTOR \
|
||||
or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
|
||||
exception_class = InvalidSelectorException
|
||||
elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
|
||||
exception_class = ElementNotSelectableException
|
||||
elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
|
||||
exception_class = ElementNotInteractableException
|
||||
elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
|
||||
exception_class = InvalidCookieDomainException
|
||||
elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
|
||||
exception_class = UnableToSetCookieException
|
||||
elif status in ErrorCode.TIMEOUT:
|
||||
exception_class = TimeoutException
|
||||
elif status in ErrorCode.SCRIPT_TIMEOUT:
|
||||
exception_class = TimeoutException
|
||||
elif status in ErrorCode.UNKNOWN_ERROR:
|
||||
exception_class = WebDriverException
|
||||
elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
|
||||
exception_class = UnexpectedAlertPresentException
|
||||
elif status in ErrorCode.NO_ALERT_OPEN:
|
||||
exception_class = NoAlertPresentException
|
||||
elif status in ErrorCode.IME_NOT_AVAILABLE:
|
||||
exception_class = ImeNotAvailableException
|
||||
elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
|
||||
exception_class = ImeActivationFailedException
|
||||
elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
|
||||
exception_class = MoveTargetOutOfBoundsException
|
||||
elif status in ErrorCode.JAVASCRIPT_ERROR:
|
||||
exception_class = JavascriptException
|
||||
elif status in ErrorCode.SESSION_NOT_CREATED:
|
||||
exception_class = SessionNotCreatedException
|
||||
elif status in ErrorCode.INVALID_ARGUMENT:
|
||||
exception_class = InvalidArgumentException
|
||||
elif status in ErrorCode.NO_SUCH_COOKIE:
|
||||
exception_class = NoSuchCookieException
|
||||
elif status in ErrorCode.UNABLE_TO_CAPTURE_SCREEN:
|
||||
exception_class = ScreenshotException
|
||||
elif status in ErrorCode.ELEMENT_CLICK_INTERCEPTED:
|
||||
exception_class = ElementClickInterceptedException
|
||||
elif status in ErrorCode.INSECURE_CERTIFICATE:
|
||||
exception_class = InsecureCertificateException
|
||||
elif status in ErrorCode.INVALID_COORDINATES:
|
||||
exception_class = InvalidCoordinatesException
|
||||
elif status in ErrorCode.INVALID_SESSION_ID:
|
||||
exception_class = InvalidSessionIdException
|
||||
elif status in ErrorCode.UNKNOWN_METHOD:
|
||||
exception_class = UnknownMethodException
|
||||
else:
|
||||
exception_class = WebDriverException
|
||||
if value == '' or value is None:
|
||||
value = response['value']
|
||||
if isinstance(value, basestring):
|
||||
if exception_class == ErrorInResponseException:
|
||||
raise exception_class(response, value)
|
||||
raise exception_class(value)
|
||||
if message == "" and 'message' in value:
|
||||
message = value['message']
|
||||
|
||||
screen = None
|
||||
if 'screen' in value:
|
||||
screen = value['screen']
|
||||
|
||||
stacktrace = None
|
||||
if 'stackTrace' in value and value['stackTrace']:
|
||||
stacktrace = []
|
||||
try:
|
||||
for frame in value['stackTrace']:
|
||||
line = self._value_or_default(frame, 'lineNumber', '')
|
||||
file = self._value_or_default(frame, 'fileName', '<anonymous>')
|
||||
if line:
|
||||
file = "%s:%s" % (file, line)
|
||||
meth = self._value_or_default(frame, 'methodName', '<anonymous>')
|
||||
if 'className' in frame:
|
||||
meth = "%s.%s" % (frame['className'], meth)
|
||||
msg = " at %s (%s)"
|
||||
msg = msg % (meth, file)
|
||||
stacktrace.append(msg)
|
||||
except TypeError:
|
||||
pass
|
||||
if exception_class == ErrorInResponseException:
|
||||
raise exception_class(response, message)
|
||||
elif exception_class == UnexpectedAlertPresentException:
|
||||
alert_text = None
|
||||
if 'data' in value:
|
||||
alert_text = value['data'].get('text')
|
||||
elif 'alert' in value:
|
||||
alert_text = value['alert'].get('text')
|
||||
raise exception_class(message, screen, stacktrace, alert_text)
|
||||
raise exception_class(message, screen, stacktrace)
|
||||
|
||||
def _value_or_default(self, obj, key, default):
|
||||
return obj[key] if key in obj else default
|
58
youtube_dl/selenium/webdriver/remote/file_detector.py
Normal file
58
youtube_dl/selenium/webdriver/remote/file_detector.py
Normal file
@ -0,0 +1,58 @@
|
||||
# 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 abc
|
||||
import os
|
||||
from selenium.webdriver.common.utils import keys_to_typing
|
||||
|
||||
|
||||
class FileDetector(object):
|
||||
"""
|
||||
Used for identifying whether a sequence of chars represents the path to a
|
||||
file.
|
||||
"""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_local_file(self, *keys):
|
||||
return
|
||||
|
||||
|
||||
class UselessFileDetector(FileDetector):
|
||||
"""
|
||||
A file detector that never finds anything.
|
||||
"""
|
||||
def is_local_file(self, *keys):
|
||||
return None
|
||||
|
||||
|
||||
class LocalFileDetector(FileDetector):
|
||||
"""
|
||||
Detects files on the local disk.
|
||||
"""
|
||||
def is_local_file(self, *keys):
|
||||
file_path = ''.join(keys_to_typing(keys))
|
||||
|
||||
if not file_path:
|
||||
return None
|
||||
|
||||
try:
|
||||
if os.path.isfile(file_path):
|
||||
return file_path
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
8
youtube_dl/selenium/webdriver/remote/getAttribute.js
Normal file
8
youtube_dl/selenium/webdriver/remote/getAttribute.js
Normal file
@ -0,0 +1,8 @@
|
||||
function(){return function(){var d=this;function f(a){return"string"==typeof a};function h(a,b){this.code=a;this.a=l[a]||m;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(a){return a.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}
|
||||
(function(){var a=Error;function b(){}b.prototype=a.prototype;h.b=a.prototype;h.prototype=new b;h.prototype.constructor=h;h.a=function(b,c,g){for(var e=Array(arguments.length-2),k=2;k<arguments.length;k++)e[k-2]=arguments[k];return a.prototype[c].apply(b,e)}})();var m="unknown error",l={15:"element not selectable",11:"element not visible"};l[31]=m;l[30]=m;l[24]="invalid cookie domain";l[29]="invalid element coordinates";l[12]="invalid element state";l[32]="invalid selector";l[51]="invalid selector";
|
||||
l[52]="invalid selector";l[17]="javascript error";l[405]="unsupported operation";l[34]="move target out of bounds";l[27]="no such alert";l[7]="no such element";l[8]="no such frame";l[23]="no such window";l[28]="script timeout";l[33]="session not created";l[10]="stale element reference";l[21]="timeout";l[25]="unable to set cookie";l[26]="unexpected alert open";l[13]=m;l[9]="unknown command";h.prototype.toString=function(){return this.name+": "+this.message};var n;a:{var p=d.navigator;if(p){var q=p.userAgent;if(q){n=q;break a}}n=""}function r(a){return-1!=n.indexOf(a)};function t(a,b){for(var e=a.length,c=f(a)?a.split(""):a,g=0;g<e;g++)g in c&&b.call(void 0,c[g],g,a)};function v(){return r("iPhone")&&!r("iPod")&&!r("iPad")};function w(){return(r("Chrome")||r("CriOS"))&&!r("Edge")};var x=r("Opera"),y=r("Trident")||r("MSIE"),z=r("Edge"),A=r("Gecko")&&!(-1!=n.toLowerCase().indexOf("webkit")&&!r("Edge"))&&!(r("Trident")||r("MSIE"))&&!r("Edge"),aa=-1!=n.toLowerCase().indexOf("webkit")&&!r("Edge");function B(){var a=d.document;return a?a.documentMode:void 0}var C;
|
||||
a:{var D="",E=function(){var a=n;if(A)return/rv\:([^\);]+)(\)|;)/.exec(a);if(z)return/Edge\/([\d\.]+)/.exec(a);if(y)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(aa)return/WebKit\/(\S+)/.exec(a);if(x)return/(?:Version)[ \/]?(\S+)/.exec(a)}();E&&(D=E?E[1]:"");if(y){var F=B();if(null!=F&&F>parseFloat(D)){C=String(F);break a}}C=D}var G;var H=d.document;G=H&&y?B()||("CSS1Compat"==H.compatMode?parseInt(C,10):5):void 0;var ba=r("Firefox"),ca=v()||r("iPod"),da=r("iPad"),I=r("Android")&&!(w()||r("Firefox")||r("Opera")||r("Silk")),ea=w(),J=r("Safari")&&!(w()||r("Coast")||r("Opera")||r("Edge")||r("Silk")||r("Android"))&&!(v()||r("iPad")||r("iPod"));function K(a){return(a=a.exec(n))?a[1]:""}(function(){if(ba)return K(/Firefox\/([0-9.]+)/);if(y||z||x)return C;if(ea)return v()||r("iPad")||r("iPod")?K(/CriOS\/([0-9.]+)/):K(/Chrome\/([0-9.]+)/);if(J&&!(v()||r("iPad")||r("iPod")))return K(/Version\/([0-9.]+)/);if(ca||da){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(n);if(a)return a[1]+"."+a[2]}else if(I)return(a=K(/Android\s+([0-9.]+)/))?a:K(/Version\/([0-9.]+)/);return""})();var L,M=function(){if(!A)return!1;var a=d.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(g){return!1}var b=a.classes,a=a.interfaces,e=b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),c=b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).version;L=function(a){e.compare(c,""+a)};return!0}(),N=y&&!(8<=Number(G)),fa=y&&!(9<=Number(G));I&&M&&L(2.3);I&&M&&L(4);J&&M&&L(6);var ga={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},O={IMG:" ",BR:"\n"};function P(a,b,e){if(!(a.nodeName in ga))if(3==a.nodeType)e?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in O)b.push(O[a.nodeName]);else for(a=a.firstChild;a;)P(a,b,e),a=a.nextSibling};function Q(a,b){b=b.toLowerCase();return"style"==b?ha(a.style.cssText):N&&"value"==b&&R(a,"INPUT")?a.value:fa&&!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var ia=/[;]+(?=(?:(?:[^"]*"){2})*[^"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\([^()]*\))*[^()]*$)/;
|
||||
function ha(a){var b=[];t(a.split(ia),function(a){var c=a.indexOf(":");0<c&&(a=[a.slice(0,c),a.slice(c+1)],2==a.length&&b.push(a[0].toLowerCase(),":",a[1],";"))});b=b.join("");return b=";"==b.charAt(b.length-1)?b:b+";"}function S(a,b){N&&"value"==b&&R(a,"OPTION")&&null===Q(a,"value")?(b=[],P(a,b,!1),a=b.join("")):a=a[b];return a}function R(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}
|
||||
function T(a){return R(a,"OPTION")?!0:R(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):!1};var ja={"class":"className",readonly:"readOnly"},U="async autofocus autoplay checked compact complete controls declare defaultchecked defaultselected defer disabled draggable ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref noresize noshade novalidate nowrap open paused pubdate readonly required reversed scoped seamless seeking selected spellcheck truespeed willvalidate".split(" ");function V(a,b){var e=null,c=b.toLowerCase();if("style"==c)return(e=a.style)&&!f(e)&&(e=e.cssText),e;if(("selected"==c||"checked"==c)&&T(a)){if(!T(a))throw new h(15,"Element is not selectable");b="selected";e=a.type&&a.type.toLowerCase();if("checkbox"==e||"radio"==e)b="checked";return S(a,b)?"true":null}var g=R(a,"A");if(R(a,"IMG")&&"src"==c||g&&"href"==c)return(e=Q(a,c))&&(e=S(a,c)),e;if("spellcheck"==c){e=Q(a,c);if(null!==e){if("false"==e.toLowerCase())return"false";if("true"==e.toLowerCase())return"true"}return S(a,
|
||||
c)+""}g=ja[b]||b;a:if(f(U))c=f(c)&&1==c.length?U.indexOf(c,0):-1;else{for(var u=0;u<U.length;u++)if(u in U&&U[u]===c){c=u;break a}c=-1}if(0<=c)return(e=null!==Q(a,b)||S(a,g))?"true":null;try{var k=S(a,g)}catch(ka){}(c=null==k)||(c=typeof k,c="object"==c&&null!=k||"function"==c);c?e=Q(a,b):e=k;return null!=e?e.toString():null}var W=["_"],X=d;W[0]in X||!X.execScript||X.execScript("var "+W[0]);
|
||||
for(var Y;W.length&&(Y=W.shift());){var Z;if(Z=!W.length)Z=void 0!==V;Z?X[Y]=V:X[Y]&&X[Y]!==Object.prototype[Y]?X=X[Y]:X=X[Y]={}};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}
|
101
youtube_dl/selenium/webdriver/remote/isDisplayed.js
Normal file
101
youtube_dl/selenium/webdriver/remote/isDisplayed.js
Normal file
@ -0,0 +1,101 @@
|
||||
function(){return function(){var k=this;function l(a){return void 0!==a}function m(a){return"string"==typeof a}function aa(a,b){a=a.split(".");var c=k;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&l(b)?c[d]=b:c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}}
|
||||
function ba(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
|
||||
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function ca(a,b,c){return a.call.apply(a.bind,arguments)}function da(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}
|
||||
function ea(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ea=ca:ea=da;return ea.apply(null,arguments)}function fa(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}}
|
||||
function p(a,b){function c(){}c.prototype=b.prototype;a.L=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.K=function(a,c,f){for(var d=Array(arguments.length-2),e=2;e<arguments.length;e++)d[e-2]=arguments[e];return b.prototype[c].apply(a,d)}};function ga(a,b){this.code=a;this.a=q[a]||ha;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(a){return a.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}p(ga,Error);var ha="unknown error",q={15:"element not selectable",11:"element not visible"};q[31]=ha;q[30]=ha;q[24]="invalid cookie domain";q[29]="invalid element coordinates";q[12]="invalid element state";
|
||||
q[32]="invalid selector";q[51]="invalid selector";q[52]="invalid selector";q[17]="javascript error";q[405]="unsupported operation";q[34]="move target out of bounds";q[27]="no such alert";q[7]="no such element";q[8]="no such frame";q[23]="no such window";q[28]="script timeout";q[33]="session not created";q[10]="stale element reference";q[21]="timeout";q[25]="unable to set cookie";q[26]="unexpected alert open";q[13]=ha;q[9]="unknown command";ga.prototype.toString=function(){return this.name+": "+this.message};var ia={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
|
||||
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
|
||||
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
|
||||
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
|
||||
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
|
||||
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};function ja(a,b){this.width=a;this.height=b}ja.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};ja.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};ja.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};ja.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function ka(a,b){var c=la;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var ma=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};function na(a,b){return a<b?-1:a>b?1:0}function oa(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};/*
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Cybozu Labs, Inc.
|
||||
Copyright (c) 2012 Google Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
function pa(a,b,c){this.a=a;this.b=b||1;this.f=c||1};function qa(a){this.b=a;this.a=0}function ra(a){a=a.match(sa);for(var b=0;b<a.length;b++)ta.test(a[b])&&a.splice(b,1);return new qa(a)}var sa=/\$?(?:(?![0-9-\.])(?:\*|[\w-\.]+):)?(?![0-9-\.])(?:\*|[\w-\.]+)|\/\/|\.\.|::|\d+(?:\.\d*)?|\.\d+|"[^"]*"|'[^']*'|[!<>]=|\s+|./g,ta=/^\s/;function t(a,b){return a.b[a.a+(b||0)]}function u(a){return a.b[a.a++]}function ua(a){return a.b.length<=a.a};var v;a:{var va=k.navigator;if(va){var wa=va.userAgent;if(wa){v=wa;break a}}v=""}function x(a){return-1!=v.indexOf(a)};function y(a,b){this.h=a;this.c=l(b)?b:null;this.b=null;switch(a){case "comment":this.b=8;break;case "text":this.b=3;break;case "processing-instruction":this.b=7;break;case "node":break;default:throw Error("Unexpected argument");}}function xa(a){return"comment"==a||"text"==a||"processing-instruction"==a||"node"==a}y.prototype.a=function(a){return null===this.b||this.b==a.nodeType};y.prototype.f=function(){return this.h};
|
||||
y.prototype.toString=function(){var a="Kind Test: "+this.h;null===this.c||(a+=z(this.c));return a};function ya(a,b){this.j=a.toLowerCase();a="*"==this.j?"*":"http://www.w3.org/1999/xhtml";this.c=b?b.toLowerCase():a}ya.prototype.a=function(a){var b=a.nodeType;if(1!=b&&2!=b)return!1;b=l(a.localName)?a.localName:a.nodeName;return"*"!=this.j&&this.j!=b.toLowerCase()?!1:"*"==this.c?!0:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():"http://www.w3.org/1999/xhtml")};ya.prototype.f=function(){return this.j};
|
||||
ya.prototype.toString=function(){return"Name Test: "+("http://www.w3.org/1999/xhtml"==this.c?"":this.c+":")+this.j};function za(a){switch(a.nodeType){case 1:return fa(Aa,a);case 9:return za(a.documentElement);case 11:case 10:case 6:case 12:return Ba;default:return a.parentNode?za(a.parentNode):Ba}}function Ba(){return null}function Aa(a,b){if(a.prefix==b)return a.namespaceURI||"http://www.w3.org/1999/xhtml";var c=a.getAttributeNode("xmlns:"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?Aa(a.parentNode,b):null};function Ca(a,b){if(m(a))return m(b)&&1==b.length?a.indexOf(b,0):-1;for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1}function A(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)}function Da(a,b){for(var c=a.length,d=[],e=0,f=m(a)?a.split(""):a,g=0;g<c;g++)if(g in f){var h=f[g];b.call(void 0,h,g,a)&&(d[e++]=h)}return d}function Ea(a,b,c){var d=c;A(a,function(c,f){d=b.call(void 0,d,c,f,a)});return d}
|
||||
function Fa(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1}function Ga(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0}function Ha(a,b){a:{for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:m(a)?a.charAt(b):a[b]}function Ia(a){return Array.prototype.concat.apply([],arguments)}
|
||||
function Ja(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};function Ka(){return x("iPhone")&&!x("iPod")&&!x("iPad")};var La="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),Ma=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,Na=/^#(?:[0-9a-f]{3}){1,2}$/i,Oa=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,Pa=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function Qa(){return(x("Chrome")||x("CriOS"))&&!x("Edge")};function Ra(a,b){this.x=l(a)?a:0;this.y=l(b)?b:0}Ra.prototype.toString=function(){return"("+this.x+", "+this.y+")"};Ra.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Ra.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Ra.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};var Sa=x("Opera"),C=x("Trident")||x("MSIE"),Ta=x("Edge"),Ua=x("Gecko")&&!(-1!=v.toLowerCase().indexOf("webkit")&&!x("Edge"))&&!(x("Trident")||x("MSIE"))&&!x("Edge"),Va=-1!=v.toLowerCase().indexOf("webkit")&&!x("Edge");function Wa(){var a=k.document;return a?a.documentMode:void 0}var Xa;
|
||||
a:{var Ya="",Za=function(){var a=v;if(Ua)return/rv\:([^\);]+)(\)|;)/.exec(a);if(Ta)return/Edge\/([\d\.]+)/.exec(a);if(C)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(Va)return/WebKit\/(\S+)/.exec(a);if(Sa)return/(?:Version)[ \/]?(\S+)/.exec(a)}();Za&&(Ya=Za?Za[1]:"");if(C){var $a=Wa();if(null!=$a&&$a>parseFloat(Ya)){Xa=String($a);break a}}Xa=Ya}var la={};
|
||||
function ab(a){return ka(a,function(){for(var b=0,c=ma(String(Xa)).split("."),d=ma(String(a)).split("."),e=Math.max(c.length,d.length),f=0;!b&&f<e;f++){var g=c[f]||"",h=d[f]||"";do{g=/(\d*)(\D*)(.*)/.exec(g)||["","","",""];h=/(\d*)(\D*)(.*)/.exec(h)||["","","",""];if(0==g[0].length&&0==h[0].length)break;b=na(0==g[1].length?0:parseInt(g[1],10),0==h[1].length?0:parseInt(h[1],10))||na(0==g[2].length,0==h[2].length)||na(g[2],h[2]);g=g[3];h=h[3]}while(!b)}return 0<=b})}var bb;var cb=k.document;
|
||||
bb=cb&&C?Wa()||("CSS1Compat"==cb.compatMode?parseInt(Xa,10):5):void 0;function db(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}db.prototype.toString=function(){return"("+this.c+"t, "+this.a+"r, "+this.b+"b, "+this.f+"l)"};db.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};db.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};
|
||||
db.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};var eb=x("Firefox"),fb=Ka()||x("iPod"),gb=x("iPad"),hb=x("Android")&&!(Qa()||x("Firefox")||x("Opera")||x("Silk")),ib=Qa(),jb=x("Safari")&&!(Qa()||x("Coast")||x("Opera")||x("Edge")||x("Silk")||x("Android"))&&!(Ka()||x("iPad")||x("iPod"));var D=C&&!(9<=Number(bb)),kb=C&&!(8<=Number(bb));function E(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}E.prototype.toString=function(){return"("+this.a+", "+this.b+" - "+this.width+"w x "+this.height+"h)"};E.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};E.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
|
||||
E.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function lb(a){return(a=a.exec(v))?a[1]:""}(function(){if(eb)return lb(/Firefox\/([0-9.]+)/);if(C||Ta||Sa)return Xa;if(ib)return Ka()||x("iPad")||x("iPod")?lb(/CriOS\/([0-9.]+)/):lb(/Chrome\/([0-9.]+)/);if(jb&&!(Ka()||x("iPad")||x("iPod")))return lb(/Version\/([0-9.]+)/);if(fb||gb){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(v);if(a)return a[1]+"."+a[2]}else if(hb)return(a=lb(/Android\s+([0-9.]+)/))?a:lb(/Version\/([0-9.]+)/);return""})();function mb(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function nb(a,b){var c=kb&&"href"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new mb(b,a,b.nodeName,c)};var ob,pb=function(){if(!Ua)return!1;var a=k.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(e){return!1}var b=a.classes,a=a.interfaces,c=b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),d=b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).version;ob=function(a){c.compare(d,""+a)};return!0}(),qb=C&&!(9<=Number(bb));hb&&pb&&ob(2.3);hb&&pb&&ob(4);jb&&pb&&ob(6);function rb(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
|
||||
function sb(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(C&&!(9<=Number(bb))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?tb(a,b):!c&&rb(e,b)?-1*ub(a,b):!d&&rb(f,a)?ub(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=F(a);c=d.createRange();
|
||||
c.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function ub(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return tb(b,a)}function tb(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function F(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function vb(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}
|
||||
function wb(a){this.a=a||k.document||document}wb.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function G(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(D&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;for(var c=0,d=[],b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),D&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return""+b}
|
||||
function H(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}kb&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function xb(a,b,c,d,e){return(D?yb:zb).call(null,a,b,m(c)?c:null,m(d)?d:null,e||new I)}
|
||||
function yb(a,b,c,d,e){if(a instanceof ya||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;var g=Ab(a);if("*"!=g&&(f=b.getElementsByTagName(g),!f))return e;if(c){var h=[];for(a=0;b=f[a++];)H(b,c,d)&&h.push(b);f=h}for(a=0;b=f[a++];)"*"==g&&"!"==b.tagName||J(e,b);return e}Bb(a,b,c,d,e);return e}
|
||||
function zb(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!C?(b=b.getElementsByName(d),A(b,function(b){a.a(b)&&J(e,b)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),A(b,function(b){b.className==d&&a.a(b)&&J(e,b)})):a instanceof y?Bb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),A(b,function(a){H(a,c,d)&&J(e,a)}));return e}
|
||||
function Cb(a,b,c,d,e){var f;if((a instanceof ya||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=Ab(a);if("*"!=g&&(f=Da(f,function(a){return a.tagName&&a.tagName.toLowerCase()==g}),!f))return e;c&&(f=Da(f,function(a){return H(a,c,d)}));A(f,function(a){"*"==g&&("!"==a.tagName||"*"==g&&1!=a.nodeType)||J(e,a)});return e}return Db(a,b,c,d,e)}function Db(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&J(e,b);return e}
|
||||
function Bb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&J(e,b),Bb(a,b,c,d,e)}function Ab(a){if(a instanceof y){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function K(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function I(){this.b=this.a=null;this.l=0}function Eb(a){this.node=a;this.a=this.b=null}function Fb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.node;var g=b.node;e==g||e instanceof mb&&g instanceof mb&&e.a==g.a?(e=c,c=c.a,b=b.a):0<sb(c.node,b.node)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function Gb(a,b){b=new Eb(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++}
|
||||
function J(a,b){b=new Eb(b);b.b=a.b;a.a?a.b.a=b:a.a=a.b=b;a.b=b;a.l++}function Hb(a){return(a=a.a)?a.node:null}function Ib(a){return(a=Hb(a))?G(a):""}function L(a,b){return new Jb(a,!!b)}function Jb(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function N(a){var b=a.b;if(b){var c=a.a=b;a.b=a.s?b.b:b.a;return c.node}return null};function O(a){this.i=a;this.b=this.g=!1;this.f=null}function z(a){return"\n "+a.toString().split("\n").join("\n ")}function Kb(a,b){a.g=b}function Lb(a,b){a.b=b}function Q(a,b){a=a.a(b);return a instanceof I?+Ib(a):+a}function R(a,b){a=a.a(b);return a instanceof I?Ib(a):""+a}function Mb(a,b){a=a.a(b);return a instanceof I?!!a.l:!!a};function Nb(a,b,c){O.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==Ob&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}p(Nb,O);
|
||||
function Pb(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof I&&c instanceof I){b=L(b);for(d=N(b);d;d=N(b))for(e=L(c),f=N(e);f;f=N(e))if(a(G(d),G(f)))return!0;return!1}if(b instanceof I||c instanceof I){b instanceof I?(e=b,d=c):(e=c,d=b);f=L(e);for(var g=typeof d,h=N(f);h;h=N(f)){switch(g){case "number":h=+G(h);break;case "boolean":h=!!G(h);break;case "string":h=G(h);break;default:throw Error("Illegal primitive type for comparison.");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?"boolean"==
|
||||
typeof b||"boolean"==typeof c?a(!!b,!!c):"number"==typeof b||"number"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}Nb.prototype.a=function(a){return this.c.m(this.h,this.o,a)};Nb.prototype.toString=function(){var a="Binary Expression: "+this.c,a=a+z(this.h);return a+=z(this.o)};function Qb(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}Qb.prototype.toString=function(){return this.I};var Rb={};
|
||||
function S(a,b,c,d){if(Rb.hasOwnProperty(a))throw Error("Binary operator already created: "+a);a=new Qb(a,b,c,d);return Rb[a.toString()]=a}S("div",6,1,function(a,b,c){return Q(a,c)/Q(b,c)});S("mod",6,1,function(a,b,c){return Q(a,c)%Q(b,c)});S("*",6,1,function(a,b,c){return Q(a,c)*Q(b,c)});S("+",5,1,function(a,b,c){return Q(a,c)+Q(b,c)});S("-",5,1,function(a,b,c){return Q(a,c)-Q(b,c)});S("<",4,2,function(a,b,c){return Pb(function(a,b){return a<b},a,b,c)});
|
||||
S(">",4,2,function(a,b,c){return Pb(function(a,b){return a>b},a,b,c)});S("<=",4,2,function(a,b,c){return Pb(function(a,b){return a<=b},a,b,c)});S(">=",4,2,function(a,b,c){return Pb(function(a,b){return a>=b},a,b,c)});var Ob=S("=",3,2,function(a,b,c){return Pb(function(a,b){return a==b},a,b,c,!0)});S("!=",3,2,function(a,b,c){return Pb(function(a,b){return a!=b},a,b,c,!0)});S("and",2,2,function(a,b,c){return Mb(a,c)&&Mb(b,c)});S("or",1,2,function(a,b,c){return Mb(a,c)||Mb(b,c)});function Sb(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");O.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}p(Sb,O);Sb.prototype.a=function(a){a=this.c.a(a);return Tb(this.h,a)};Sb.prototype.toString=function(){var a="Filter:"+z(this.c);return a+=z(this.h)};function Ub(a,b){if(b.length<a.C)throw Error("Function "+a.j+" expects at least"+a.C+" arguments, "+b.length+" given");if(null!==a.A&&b.length>a.A)throw Error("Function "+a.j+" expects at most "+a.A+" arguments, "+b.length+" given");a.H&&A(b,function(b,d){if(4!=b.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+b);});O.call(this,a.i);this.v=a;this.c=b;Kb(this,a.g||Fa(b,function(a){return a.g}));Lb(this,a.G&&!b.length||a.F&&!!b.length||Fa(b,function(a){return a.b}))}
|
||||
p(Ub,O);Ub.prototype.a=function(a){return this.v.m.apply(null,Ia(a,this.c))};Ub.prototype.toString=function(){var a="Function: "+this.v;if(this.c.length)var b=Ea(this.c,function(a,b){return a+z(b)},"Arguments:"),a=a+z(b);return a};function Vb(a,b,c,d,e,f,g,h,r){this.j=a;this.i=b;this.g=c;this.G=d;this.F=e;this.m=f;this.C=g;this.A=l(h)?h:g;this.H=!!r}Vb.prototype.toString=function(){return this.j};var Wb={};
|
||||
function T(a,b,c,d,e,f,g,h){if(Wb.hasOwnProperty(a))throw Error("Function already created: "+a+".");Wb[a]=new Vb(a,b,c,d,!1,e,f,g,h)}T("boolean",2,!1,!1,function(a,b){return Mb(b,a)},1);T("ceiling",1,!1,!1,function(a,b){return Math.ceil(Q(b,a))},1);T("concat",3,!1,!1,function(a,b){return Ea(Ja(arguments,1),function(b,d){return b+R(d,a)},"")},2,null);T("contains",2,!1,!1,function(a,b,c){b=R(b,a);a=R(c,a);return-1!=b.indexOf(a)},2);T("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);
|
||||
T("false",2,!1,!1,function(){return!1},0);T("floor",1,!1,!1,function(a,b){return Math.floor(Q(b,a))},1);T("id",4,!1,!1,function(a,b){function c(a){if(D){var b=e.all[a];if(b){if(b.nodeType&&a==b.id)return b;if(b.length)return Ha(b,function(b){return a==b.id})}return null}return e.getElementById(a)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=R(b,a).split(/\s+/);var f=[];A(a,function(a){a=c(a);!a||0<=Ca(f,a)||f.push(a)});f.sort(sb);var g=new I;A(f,function(a){J(g,a)});return g},1);
|
||||
T("lang",2,!1,!1,function(){return!1},1);T("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);T("local-name",3,!1,!0,function(a,b){return(a=b?Hb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():""},0,1,!0);T("name",3,!1,!0,function(a,b){return(a=b?Hb(b.a(a)):a.a)?a.nodeName.toLowerCase():""},0,1,!0);T("namespace-uri",3,!0,!1,function(){return""},0,1,!0);
|
||||
T("normalize-space",3,!1,!0,function(a,b){return(b?R(b,a):G(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);T("not",2,!1,!1,function(a,b){return!Mb(b,a)},1);T("number",1,!1,!0,function(a,b){return b?Q(b,a):+G(a.a)},0,1);T("position",1,!0,!1,function(a){return a.b},0);T("round",1,!1,!1,function(a,b){return Math.round(Q(b,a))},1);T("starts-with",2,!1,!1,function(a,b,c){b=R(b,a);a=R(c,a);return!b.lastIndexOf(a,0)},2);T("string",3,!1,!0,function(a,b){return b?R(b,a):G(a.a)},0,1);
|
||||
T("string-length",1,!1,!0,function(a,b){return(b?R(b,a):G(a.a)).length},0,1);T("substring",3,!1,!1,function(a,b,c,d){c=Q(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?Q(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=R(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);T("substring-after",3,!1,!1,function(a,b,c){b=R(b,a);a=R(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2);
|
||||
T("substring-before",3,!1,!1,function(a,b,c){b=R(b,a);a=R(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);T("sum",1,!1,!1,function(a,b){a=L(b.a(a));b=0;for(var c=N(a);c;c=N(a))b+=+G(c);return b},1,1,!0);T("translate",3,!1,!1,function(a,b,c,d){b=R(b,a);c=R(c,a);var e=R(d,a);d={};for(var f=0;f<c.length;f++)a=c.charAt(f),a in d||(d[a]=e.charAt(f));c="";for(f=0;f<b.length;f++)a=b.charAt(f),c+=a in d?d[a]:a;return c},3);T("true",2,!1,!1,function(){return!0},0);function Xb(a){O.call(this,3);this.c=a.substring(1,a.length-1)}p(Xb,O);Xb.prototype.a=function(){return this.c};Xb.prototype.toString=function(){return"Literal: "+this.c};function Yb(a){O.call(this,1);this.c=a}p(Yb,O);Yb.prototype.a=function(){return this.c};Yb.prototype.toString=function(){return"Number: "+this.c};function Zb(a,b){O.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;1==this.c.length&&(a=this.c[0],a.w||a.c!=$b||(a=a.o,"*"!=a.f()&&(this.f={name:a.f(),u:null})))}p(Zb,O);function ac(){O.call(this,4)}p(ac,O);ac.prototype.a=function(a){var b=new I;a=a.a;9==a.nodeType?J(b,a):J(b,a.ownerDocument);return b};ac.prototype.toString=function(){return"Root Helper Expression"};function bc(){O.call(this,4)}p(bc,O);bc.prototype.a=function(a){var b=new I;J(b,a.a);return b};bc.prototype.toString=function(){return"Context Helper Expression"};
|
||||
function cc(a){return"/"==a||"//"==a}Zb.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof I))throw Error("Filter expression must evaluate to nodeset.");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=L(b,e.c.s);if(e.g||e.c!=dc)if(e.g||e.c!=ec){var g=N(f);for(b=e.a(new pa(g));g=N(f);)g=e.a(new pa(g)),b=Fb(b,g)}else g=N(f),b=e.a(new pa(g));else{for(g=N(f);(b=N(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new pa(g))}}return b};
|
||||
Zb.prototype.toString=function(){var a="Path Expression:"+z(this.h);if(this.c.length){var b=Ea(this.c,function(a,b){return a+z(b)},"Steps:");a+=z(b)}return a};function fc(a,b){this.a=a;this.s=!!b}
|
||||
function Tb(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=L(b),f=b.l,g,h=0;g=N(e);h++){var r=a.s?f-h:h+1;g=d.a(new pa(g,r,f));if("number"==typeof g)r=r==g;else if("string"==typeof g||"boolean"==typeof g)r=!!g;else if(g instanceof I)r=0<g.l;else throw Error("Predicate.evaluate returned an unexpected type.");if(!r){r=e;g=r.f;var w=r.a;if(!w)throw Error("Next must be called at least once before remove.");var n=w.b,w=w.a;n?n.a=w:g.a=w;w?w.b=n:g.b=n;g.l--;r.a=null}}return b}
|
||||
fc.prototype.toString=function(){return Ea(this.a,function(a,b){return a+z(b)},"Predicates:")};function gc(a){O.call(this,1);this.c=a;this.g=a.g;this.b=a.b}p(gc,O);gc.prototype.a=function(a){return-Q(this.c,a)};gc.prototype.toString=function(){return"Unary Expression: -"+z(this.c)};function hc(a){O.call(this,4);this.c=a;Kb(this,Fa(this.c,function(a){return a.g}));Lb(this,Fa(this.c,function(a){return a.b}))}p(hc,O);hc.prototype.a=function(a){var b=new I;A(this.c,function(c){c=c.a(a);if(!(c instanceof I))throw Error("Path expression must evaluate to NodeSet.");b=Fb(b,c)});return b};hc.prototype.toString=function(){return Ea(this.c,function(a,b){return a+z(b)},"Union Expression:")};function U(a,b,c,d){O.call(this,4);this.c=a;this.o=b;this.h=c||new fc([]);this.w=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.J&&b&&(a=b.name,a=D?a.toLowerCase():a,this.f={name:a,u:b.u});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}p(U,O);
|
||||
U.prototype.a=function(a){var b=a.a,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.u?R(c.u,a):null,f=1);if(this.w)if(this.g||this.c!=ic)if(b=L((new U(jc,new y("node"))).a(a)),c=N(b))for(a=this.m(c,d,e,f);c=N(b);)a=Fb(a,this.m(c,d,e,f));else a=new I;else a=xb(this.o,b,d,e),a=Tb(this.h,a,f);else a=this.m(a.a,d,e,f);return a};U.prototype.m=function(a,b,c,d){a=this.c.v(this.o,a,b,c);return a=Tb(this.h,a,d)};
|
||||
U.prototype.toString=function(){var a="Step:"+z("Operator: "+(this.w?"//":"/"));this.c.j&&(a+=z("Axis: "+this.c));a+=z(this.o);if(this.h.a.length){var b=Ea(this.h.a,function(a,b){return a+z(b)},"Predicates:");a+=z(b)}return a};function kc(a,b,c,d){this.j=a;this.v=b;this.s=c;this.J=d}kc.prototype.toString=function(){return this.j};var lc={};function V(a,b,c,d){if(lc.hasOwnProperty(a))throw Error("Axis already created: "+a);b=new kc(a,b,c,!!d);return lc[a]=b}
|
||||
V("ancestor",function(a,b){for(var c=new I;b=b.parentNode;)a.a(b)&&Gb(c,b);return c},!0);V("ancestor-or-self",function(a,b){var c=new I;do a.a(b)&&Gb(c,b);while(b=b.parentNode);return c},!0);
|
||||
var $b=V("attribute",function(a,b){var c=new I,d=a.f();if("style"==d&&D&&b.style)return J(c,new mb(b.style,b,"style",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof y&&null===a.b||"*"==d)for(d=0;a=e[d];d++)D?a.nodeValue&&J(c,nb(b,a)):J(c,a);else(a=e.getNamedItem(d))&&(D?a.nodeValue&&J(c,nb(b,a)):J(c,a));return c},!1),ic=V("child",function(a,b,c,d,e){return(D?Cb:Db).call(null,a,b,m(c)?c:null,m(d)?d:null,e||new I)},!1,!0);V("descendant",xb,!1,!0);
|
||||
var jc=V("descendant-or-self",function(a,b,c,d){var e=new I;H(b,c,d)&&a.a(b)&&J(e,b);return xb(a,b,c,d,e)},!1,!0),dc=V("following",function(a,b,c,d){var e=new I;do for(var f=b;f=f.nextSibling;)H(f,c,d)&&a.a(f)&&J(e,f),e=xb(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);V("following-sibling",function(a,b){for(var c=new I;b=b.nextSibling;)a.a(b)&&J(c,b);return c},!1);V("namespace",function(){return new I},!1);
|
||||
var mc=V("parent",function(a,b){var c=new I;if(9==b.nodeType)return c;if(2==b.nodeType)return J(c,b.ownerElement),c;b=b.parentNode;a.a(b)&&J(c,b);return c},!1),ec=V("preceding",function(a,b,c,d){var e=new I,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,h=f.length;g<h;g++){var r=[];for(b=f[g];b=b.previousSibling;)r.unshift(b);for(var w=0,n=r.length;w<n;w++)b=r[w],H(b,c,d)&&a.a(b)&&J(e,b),e=xb(a,b,c,d,e)}return e},!0,!0);
|
||||
V("preceding-sibling",function(a,b){for(var c=new I;b=b.previousSibling;)a.a(b)&&Gb(c,b);return c},!0);var nc=V("self",function(a,b){var c=new I;a.a(b)&&J(c,b);return c},!1);function oc(a,b){this.a=a;this.b=b}function pc(a){for(var b,c=[];;){W(a,"Missing right hand side of binary expression.");b=qc(a);var d=u(a.a);if(!d)break;var e=(d=Rb[d]||null)&&d.D;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].D;)b=new Nb(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new Nb(c.pop(),c.pop(),b);return b}function W(a,b){if(ua(a.a))throw Error(b);}function rc(a,b){a=u(a.a);if(a!=b)throw Error("Bad token, expected: "+b+" got: "+a);}
|
||||
function sc(a){a=u(a.a);if(")"!=a)throw Error("Bad token: "+a);}function tc(a){a=u(a.a);if(2>a.length)throw Error("Unclosed literal string");return new Xb(a)}
|
||||
function uc(a){var b=[];if(cc(t(a.a))){var c=u(a.a);var d=t(a.a);if("/"==c&&(ua(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new ac;d=new ac;W(a,"Missing next location step.");c=vc(a,c);b.push(c)}else{a:{c=t(a.a);d=c.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":u(a.a);c=pc(a);W(a,'unclosed "("');rc(a,")");break;case '"':case "'":c=tc(a);break;default:if(isNaN(+c))if(!xa(c)&&/(?![0-9])[\w]/.test(d)&&"("==t(a.a,1)){c=u(a.a);
|
||||
c=Wb[c]||null;u(a.a);for(d=[];")"!=t(a.a);){W(a,"Missing function argument list.");d.push(pc(a));if(","!=t(a.a))break;u(a.a)}W(a,"Unclosed function argument list.");sc(a);c=new Ub(c,d)}else{c=null;break a}else c=new Yb(+u(a.a))}"["==t(a.a)&&(d=new fc(wc(a)),c=new Sb(c,d))}if(c)if(cc(t(a.a)))d=c;else return c;else c=vc(a,"/"),d=new bc,b.push(c)}for(;cc(t(a.a));)c=u(a.a),W(a,"Missing next location step."),c=vc(a,c),b.push(c);return new Zb(d,b)}
|
||||
function vc(a,b){if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==t(a.a)){var c=new U(nc,new y("node"));u(a.a);return c}if(".."==t(a.a))return c=new U(mc,new y("node")),u(a.a),c;if("@"==t(a.a)){var d=$b;u(a.a);W(a,"Missing attribute name")}else if("::"==t(a.a,1)){if(!/(?![0-9])[\w]/.test(t(a.a).charAt(0)))throw Error("Bad token: "+u(a.a));var e=u(a.a);d=lc[e]||null;if(!d)throw Error("No axis with name: "+e);u(a.a);W(a,"Missing node name")}else d=ic;e=t(a.a);if(/(?![0-9])[\w\*]/.test(e.charAt(0)))if("("==
|
||||
t(a.a,1)){if(!xa(e))throw Error("Invalid node type: "+e);e=u(a.a);if(!xa(e))throw Error("Invalid type name: "+e);rc(a,"(");W(a,"Bad nodetype");var f=t(a.a).charAt(0),g=null;if('"'==f||"'"==f)g=tc(a);W(a,"Bad nodetype");sc(a);e=new y(e,g)}else if(e=u(a.a),f=e.indexOf(":"),-1==f)e=new ya(e);else{var g=e.substring(0,f);if("*"==g)var h="*";else if(h=a.b(g),!h)throw Error("Namespace prefix not declared: "+g);e=e.substr(f+1);e=new ya(e,h)}else throw Error("Bad token: "+u(a.a));a=new fc(wc(a),d.s);return c||
|
||||
new U(d,e,a,"//"==b)}function wc(a){for(var b=[];"["==t(a.a);){u(a.a);W(a,"Missing predicate expression.");var c=pc(a);b.push(c);W(a,"Unclosed predicate expression.");rc(a,"]")}return b}function qc(a){if("-"==t(a.a))return u(a.a),new gc(qc(a));var b=uc(a);if("|"!=t(a.a))a=b;else{for(b=[b];"|"==u(a.a);)W(a,"Missing next union location path."),b.push(uc(a));a.a.a--;a=new hc(b)}return a};function xc(a,b){if(!a.length)throw Error("Empty XPath expression.");a=ra(a);if(ua(a))throw Error("Invalid XPath expression.");b?"function"==ba(b)||(b=ea(b.lookupNamespaceURI,b)):b=function(){return null};var c=pc(new oc(a,b));if(!ua(a))throw Error("Bad token: "+u(a));this.evaluate=function(a,b){a=c.a(new pa(a));return new X(a,b)}}
|
||||
function X(a,b){if(!b)if(a instanceof I)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof I))throw Error("value could not be converted to the specified type");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof I?Ib(a):""+a;break;case 1:this.numberValue=a instanceof I?+Ib(a):+a;break;case 3:this.booleanValue=a instanceof I?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var c=
|
||||
L(a);var d=[];for(var e=N(c);e;e=N(c))d.push(e instanceof mb?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:a=Hb(a);this.singleNodeValue=a instanceof mb?a.a:a;break;default:throw Error("Unknown XPathResult type.");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error("iterateNext called with wrong result type");return f>=d.length?null:d[f++]};this.snapshotItem=function(a){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return a>=d.length||
|
||||
0>a?null:d[a]}}X.ANY_TYPE=0;X.NUMBER_TYPE=1;X.STRING_TYPE=2;X.BOOLEAN_TYPE=3;X.UNORDERED_NODE_ITERATOR_TYPE=4;X.ORDERED_NODE_ITERATOR_TYPE=5;X.UNORDERED_NODE_SNAPSHOT_TYPE=6;X.ORDERED_NODE_SNAPSHOT_TYPE=7;X.ANY_UNORDERED_NODE_TYPE=8;X.FIRST_ORDERED_NODE_TYPE=9;function yc(a){this.lookupNamespaceURI=za(a)}
|
||||
function zc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=X,c.evaluate=function(a,b,c,g){return(new xc(a,c)).evaluate(b,g)},c.createExpression=function(a,b){return new xc(a,b)},c.createNSResolver=function(a){return new yc(a)}}aa("wgxpath.install",zc);var Ac=function(){var a={M:"http://www.w3.org/2000/svg"};return function(b){return a[b]||null}}();
|
||||
function Bc(a,b){var c=F(a);if(!c.documentElement)return null;(C||hb)&&zc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Ac;if(C&&!ab(7))return c.evaluate.call(c,b,a,d,9,null);if(!C||9<=Number(bb)){for(var e={},f=c.getElementsByTagName("*"),g=0;g<f.length;++g){var h=f[g],r=h.namespaceURI;if(r&&!e[r]){var w=h.lookupPrefix(r);if(!w)var n=r.match(".*/(\\w+)/?$"),w=n?n[1]:"xhtml";e[r]=w}}var B={},M;for(M in e)B[e[M]]=M;d=function(a){return B[a]||
|
||||
null}}try{return c.evaluate(b,a,d,9,null)}catch(P){if("TypeError"===P.name)return d=c.createNSResolver?c.createNSResolver(c.documentElement):Ac,c.evaluate(b,a,d,9,null);throw P;}}catch(P){if(!Ua||"NS_ERROR_ILLEGAL_VALUE"!=P.name)throw new ga(32,"Unable to locate an element with the xpath expression "+b+" because of the following error:\n"+P);}}
|
||||
function Cc(a,b){var c=function(){var c=Bc(b,a);return c?c.singleNodeValue||null:b.selectSingleNode?(c=F(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new ga(32,'The result of the xpath expression "'+a+'" is: '+c+". It should be an element.");return c};var Dc="function"===typeof ShadowRoot;function Ec(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return K(a)?a:null}
|
||||
function Y(a,b){b=oa(b);if("float"==b||"cssFloat"==b||"styleFloat"==b)b=qb?"styleFloat":"cssFloat";a:{var c=b;var d=F(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||"";break a}c=""}a=c||Fc(a,b);if(null===a)a=null;else if(0<=Ca(La,b)){b:{var e=a.match(Oa);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(Pa))if(b=
|
||||
Number(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=ia[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(Ma,"#$1$1$2$2$3$3")),!Na.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
|
||||
function Fc(a,b){var c=a.currentStyle||a.style,d=c[b];!l(d)&&"function"==ba(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?l(d)?d:null:(a=Ec(a))?Fc(a,b):null}
|
||||
function Gc(a,b,c){function d(a){var b=Hc(a);return 0<b.height&&0<b.width?!0:K(a,"PATH")&&(0<b.height||0<b.width)?(a=Y(a,"stroke-width"),!!a&&0<parseInt(a,10)):"hidden"!=Y(a,"overflow")&&Fa(a.childNodes,function(a){return 3==a.nodeType||K(a)&&d(a)})}function e(a){return Ic(a)==Z&&Ga(a.childNodes,function(a){return!K(a)||e(a)||!d(a)})}if(!K(a))throw Error("Argument to isShown must be of type Element");if(K(a,"BODY"))return!0;if(K(a,"OPTION")||K(a,"OPTGROUP"))return a=vb(a,function(a){return K(a,"SELECT")}),
|
||||
!!a&&Gc(a,!0,c);var f=Jc(a);if(f)return!!f.B&&0<f.rect.width&&0<f.rect.height&&Gc(f.B,b,c);if(K(a,"INPUT")&&"hidden"==a.type.toLowerCase()||K(a,"NOSCRIPT"))return!1;f=Y(a,"visibility");return"collapse"!=f&&"hidden"!=f&&c(a)&&(b||Kc(a))&&d(a)?!e(a):!1}var Z="hidden";
|
||||
function Ic(a){function b(a){function b(a){return a==g?!0:!Y(a,"display").lastIndexOf("inline",0)||"absolute"==c&&"static"==Y(a,"position")?!1:!0}var c=Y(a,"position");if("fixed"==c)return w=!0,a==g?null:g;for(a=Ec(a);a&&!b(a);)a=Ec(a);return a}function c(a){var b=a;if("visible"==r)if(a==g&&h)b=h;else if(a==h)return{x:"visible",y:"visible"};b={x:Y(b,"overflow-x"),y:Y(b,"overflow-y")};a==g&&(b.x="visible"==b.x?"auto":b.x,b.y="visible"==b.y?"auto":b.y);return b}function d(a){if(a==g){var b=(new wb(f)).a;
|
||||
a=b.scrollingElement?b.scrollingElement:Va||"CSS1Compat"!=b.compatMode?b.body||b.documentElement:b.documentElement;b=b.parentWindow||b.defaultView;a=C&&ab("10")&&b.pageYOffset!=a.scrollTop?new Ra(a.scrollLeft,a.scrollTop):new Ra(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}else a=new Ra(a.scrollLeft,a.scrollTop);return a}var e=Lc(a);var f=F(a),g=f.documentElement,h=f.body,r=Y(g,"overflow"),w;for(a=b(a);a;a=b(a)){var n=c(a);if("visible"!=n.x||"visible"!=n.y){var B=Hc(a);if(!B.width||!B.height)return Z;
|
||||
var M=e.a<B.a,P=e.b<B.b;if(M&&"hidden"==n.x||P&&"hidden"==n.y)return Z;if(M&&"visible"!=n.x||P&&"visible"!=n.y){M=d(a);P=e.b<B.b-M.y;if(e.a<B.a-M.x&&"visible"!=n.x||P&&"visible"!=n.x)return Z;e=Ic(a);return e==Z?Z:"scroll"}M=e.f>=B.a+B.width;B=e.c>=B.b+B.height;if(M&&"hidden"==n.x||B&&"hidden"==n.y)return Z;if(M&&"visible"!=n.x||B&&"visible"!=n.y){if(w&&(n=d(a),e.f>=g.scrollWidth-n.x||e.a>=g.scrollHeight-n.y))return Z;e=Ic(a);return e==Z?Z:"scroll"}}}return"none"}
|
||||
function Hc(a){var b=Jc(a);if(b)return b.rect;if(K(a,"HTML"))return a=F(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new ja(a.clientWidth,a.clientHeight),new E(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new E(0,0,0,0)}b=new E(c.left,c.top,c.right-c.left,c.bottom-c.top);C&&a.ownerDocument.body&&(a=F(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);
|
||||
return b}function Jc(a){var b=K(a,"MAP");if(!b&&!K(a,"AREA"))return null;var c=b?a:K(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Cc('/descendant::*[@usemap = "#'+c.name+'"]',F(c)))&&(e=Hc(d),b||"default"==a.shape.toLowerCase()||(a=Mc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new E(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{B:d,rect:e||new E(0,0,0,0)}}
|
||||
function Mc(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){var b=a[0],c=a[1];return new E(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new E(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2<a.length){for(var b=a[0],c=a[1],d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new E(b,c,d-b,e-c)}return new E(0,0,0,0)}function Lc(a){a=Hc(a);return new db(a.b,a.a+a.width,a.b+a.height,a.a)}
|
||||
function Kc(a){if(qb){if("relative"==Y(a,"position"))return 1;a=Y(a,"filter");return(a=a.match(/^alpha\(opacity=(\d*)\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\(Opacity=(\d*)\)/))?Number(a[1])/100:1}return Nc(a)}function Nc(a){var b=1,c=Y(a,"opacity");c&&(b=Number(c));(a=Ec(a))&&(b*=Nc(a));return b};aa("_",function(a,b){var c=Dc?function(b){if("none"==Y(b,"display"))return!1;do{var d=b.parentNode;if(b.getDestinationInsertionPoints){var f=b.getDestinationInsertionPoints();0<f.length&&(d=f[f.length-1])}if(d instanceof ShadowRoot){if(d.host.shadowRoot!=d)return!1;d=d.host}else!d||9!=d.nodeType&&11!=d.nodeType||(d=null)}while(a&&1!=a.nodeType);return!d||c(d)}:function(a){if("none"==Y(a,"display"))return!1;a=Ec(a);return!a||c(a)};return Gc(a,!!b,c)});; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}
|
85
youtube_dl/selenium/webdriver/remote/mobile.py
Normal file
85
youtube_dl/selenium/webdriver/remote/mobile.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 .command import Command
|
||||
|
||||
|
||||
class Mobile(object):
|
||||
|
||||
class ConnectionType(object):
|
||||
|
||||
def __init__(self, mask):
|
||||
self.mask = mask
|
||||
|
||||
@property
|
||||
def airplane_mode(self):
|
||||
return self.mask % 2 == 1
|
||||
|
||||
@property
|
||||
def wifi(self):
|
||||
return (self.mask / 2) % 2 == 1
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return (self.mask / 4) > 0
|
||||
|
||||
ALL_NETWORK = ConnectionType(6)
|
||||
WIFI_NETWORK = ConnectionType(2)
|
||||
DATA_NETWORK = ConnectionType(4)
|
||||
AIRPLANE_MODE = ConnectionType(1)
|
||||
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
|
||||
@property
|
||||
def network_connection(self):
|
||||
return self.ConnectionType(self._driver.execute(Command.GET_NETWORK_CONNECTION)['value'])
|
||||
|
||||
def set_network_connection(self, network):
|
||||
"""
|
||||
Set the network connection for the remote device.
|
||||
|
||||
Example of setting airplane mode::
|
||||
|
||||
driver.mobile.set_network_connection(driver.mobile.AIRPLANE_MODE)
|
||||
"""
|
||||
mode = network.mask if isinstance(network, self.ConnectionType) else network
|
||||
return self.ConnectionType(self._driver.execute(
|
||||
Command.SET_NETWORK_CONNECTION, {
|
||||
'name': 'network_connection',
|
||||
'parameters': {'type': mode}})['value'])
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
"""
|
||||
returns the current context (Native or WebView).
|
||||
"""
|
||||
return self._driver.execute(Command.CURRENT_CONTEXT_HANDLE)
|
||||
|
||||
@property
|
||||
def contexts(self):
|
||||
"""
|
||||
returns a list of available contexts
|
||||
"""
|
||||
return self._driver.execute(Command.CONTEXT_HANDLES)
|
||||
|
||||
@context.setter
|
||||
def context(self, new_context):
|
||||
"""
|
||||
sets the current context
|
||||
"""
|
||||
self._driver.execute(Command.SWITCH_TO_CONTEXT, {"name": new_context})
|
568
youtube_dl/selenium/webdriver/remote/remote_connection.py
Normal file
568
youtube_dl/selenium/webdriver/remote/remote_connection.py
Normal file
@ -0,0 +1,568 @@
|
||||
# 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 socket
|
||||
import string
|
||||
import base64
|
||||
|
||||
try:
|
||||
import http.client as httplib
|
||||
from urllib import request as url_request
|
||||
from urllib import parse
|
||||
except ImportError: # above is available in py3+, below is py2.7
|
||||
import httplib as httplib
|
||||
import urllib2 as url_request
|
||||
import urlparse as parse
|
||||
|
||||
from selenium.webdriver.common import utils as common_utils
|
||||
from .command import Command
|
||||
from .errorhandler import ErrorCode
|
||||
from . import utils
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Request(url_request.Request):
|
||||
"""
|
||||
Extends the url_request.Request to support all HTTP request types.
|
||||
"""
|
||||
|
||||
def __init__(self, url, data=None, method=None):
|
||||
"""
|
||||
Initialise a new HTTP request.
|
||||
|
||||
:Args:
|
||||
- url - String for the URL to send the request to.
|
||||
- data - Data to send with the request.
|
||||
"""
|
||||
if method is None:
|
||||
method = data is not None and 'POST' or 'GET'
|
||||
elif method != 'POST' and method != 'PUT':
|
||||
data = None
|
||||
self._method = method
|
||||
url_request.Request.__init__(self, url, data=data)
|
||||
|
||||
def get_method(self):
|
||||
"""
|
||||
Returns the HTTP method used by this request.
|
||||
"""
|
||||
return self._method
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""
|
||||
Represents an HTTP response.
|
||||
"""
|
||||
|
||||
def __init__(self, fp, code, headers, url):
|
||||
"""
|
||||
Initialise a new Response.
|
||||
|
||||
:Args:
|
||||
- fp - The response body file object.
|
||||
- code - The HTTP status code returned by the server.
|
||||
- headers - A dictionary of headers returned by the server.
|
||||
- url - URL of the retrieved resource represented by this Response.
|
||||
"""
|
||||
self.fp = fp
|
||||
self.read = fp.read
|
||||
self.code = code
|
||||
self.headers = headers
|
||||
self.url = url
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the response body file object.
|
||||
"""
|
||||
self.read = None
|
||||
self.fp = None
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
Returns the response headers.
|
||||
"""
|
||||
return self.headers
|
||||
|
||||
def geturl(self):
|
||||
"""
|
||||
Returns the URL for the resource returned in this response.
|
||||
"""
|
||||
return self.url
|
||||
|
||||
|
||||
class HttpErrorHandler(url_request.HTTPDefaultErrorHandler):
|
||||
"""
|
||||
A custom HTTP error handler.
|
||||
|
||||
Used to return Response objects instead of raising an HTTPError exception.
|
||||
"""
|
||||
|
||||
def http_error_default(self, req, fp, code, msg, headers):
|
||||
"""
|
||||
Default HTTP error handler.
|
||||
|
||||
:Args:
|
||||
- req - The original Request object.
|
||||
- fp - The response body file object.
|
||||
- code - The HTTP status code returned by the server.
|
||||
- msg - The HTTP status message returned by the server.
|
||||
- headers - The response headers.
|
||||
|
||||
:Returns:
|
||||
A new Response object.
|
||||
"""
|
||||
return Response(fp, code, headers, req.get_full_url())
|
||||
|
||||
|
||||
class RemoteConnection(object):
|
||||
"""A connection with the Remote WebDriver server.
|
||||
|
||||
Communicates with the server using the WebDriver wire protocol:
|
||||
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol"""
|
||||
|
||||
_timeout = socket._GLOBAL_DEFAULT_TIMEOUT
|
||||
|
||||
@classmethod
|
||||
def get_timeout(cls):
|
||||
"""
|
||||
:Returns:
|
||||
Timeout value in seconds for all http requests made to the Remote Connection
|
||||
"""
|
||||
return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout
|
||||
|
||||
@classmethod
|
||||
def set_timeout(cls, timeout):
|
||||
"""
|
||||
Override the default timeout
|
||||
|
||||
:Args:
|
||||
- timeout - timeout value for http requests in seconds
|
||||
"""
|
||||
cls._timeout = timeout
|
||||
|
||||
@classmethod
|
||||
def reset_timeout(cls):
|
||||
"""
|
||||
Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT
|
||||
"""
|
||||
cls._timeout = socket._GLOBAL_DEFAULT_TIMEOUT
|
||||
|
||||
@classmethod
|
||||
def get_remote_connection_headers(cls, parsed_url, keep_alive=False):
|
||||
"""
|
||||
Get headers for remote request.
|
||||
|
||||
:Args:
|
||||
- parsed_url - The parsed url
|
||||
- keep_alive (Boolean) - Is this a keep-alive connection (default: False)
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
'User-Agent': 'Python http auth'
|
||||
}
|
||||
|
||||
if parsed_url.username:
|
||||
base64string = base64.b64encode('{0.username}:{0.password}'.format(parsed_url).encode())
|
||||
headers.update({
|
||||
'Authorization': 'Basic {}'.format(base64string.decode())
|
||||
})
|
||||
|
||||
if keep_alive:
|
||||
headers.update({
|
||||
'Connection': 'keep-alive'
|
||||
})
|
||||
|
||||
return headers
|
||||
|
||||
def __init__(self, remote_server_addr, keep_alive=False, resolve_ip=True):
|
||||
# Attempt to resolve the hostname and get an IP address.
|
||||
self.keep_alive = keep_alive
|
||||
parsed_url = parse.urlparse(remote_server_addr)
|
||||
addr = parsed_url.hostname
|
||||
if parsed_url.hostname and resolve_ip:
|
||||
port = parsed_url.port or None
|
||||
if parsed_url.scheme == "https":
|
||||
ip = parsed_url.hostname
|
||||
elif port and not common_utils.is_connectable(port, parsed_url.hostname):
|
||||
ip = None
|
||||
LOGGER.info('Could not connect to port {} on host '
|
||||
'{}'.format(port, parsed_url.hostname))
|
||||
else:
|
||||
ip = common_utils.find_connectable_ip(parsed_url.hostname,
|
||||
port=port)
|
||||
if ip:
|
||||
netloc = ip
|
||||
addr = netloc
|
||||
if parsed_url.port:
|
||||
netloc = common_utils.join_host_port(netloc,
|
||||
parsed_url.port)
|
||||
if parsed_url.username:
|
||||
auth = parsed_url.username
|
||||
if parsed_url.password:
|
||||
auth += ':%s' % parsed_url.password
|
||||
netloc = '%s@%s' % (auth, netloc)
|
||||
remote_server_addr = parse.urlunparse(
|
||||
(parsed_url.scheme, netloc, parsed_url.path,
|
||||
parsed_url.params, parsed_url.query, parsed_url.fragment))
|
||||
else:
|
||||
LOGGER.info('Could not get IP address for host: %s' %
|
||||
parsed_url.hostname)
|
||||
|
||||
self._url = remote_server_addr
|
||||
if keep_alive:
|
||||
self._conn = httplib.HTTPConnection(
|
||||
str(addr), str(parsed_url.port), timeout=self._timeout)
|
||||
|
||||
self._commands = {
|
||||
Command.STATUS: ('GET', '/status'),
|
||||
Command.NEW_SESSION: ('POST', '/session'),
|
||||
Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
|
||||
Command.QUIT: ('DELETE', '/session/$sessionId'),
|
||||
Command.GET_CURRENT_WINDOW_HANDLE:
|
||||
('GET', '/session/$sessionId/window_handle'),
|
||||
Command.W3C_GET_CURRENT_WINDOW_HANDLE:
|
||||
('GET', '/session/$sessionId/window'),
|
||||
Command.GET_WINDOW_HANDLES:
|
||||
('GET', '/session/$sessionId/window_handles'),
|
||||
Command.W3C_GET_WINDOW_HANDLES:
|
||||
('GET', '/session/$sessionId/window/handles'),
|
||||
Command.GET: ('POST', '/session/$sessionId/url'),
|
||||
Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
|
||||
Command.GO_BACK: ('POST', '/session/$sessionId/back'),
|
||||
Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
|
||||
Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'),
|
||||
Command.W3C_EXECUTE_SCRIPT:
|
||||
('POST', '/session/$sessionId/execute/sync'),
|
||||
Command.W3C_EXECUTE_SCRIPT_ASYNC:
|
||||
('POST', '/session/$sessionId/execute/async'),
|
||||
Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
|
||||
Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
|
||||
Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
|
||||
Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
|
||||
Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
|
||||
Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
|
||||
Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
|
||||
Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
|
||||
Command.GET_ACTIVE_ELEMENT:
|
||||
('POST', '/session/$sessionId/element/active'),
|
||||
Command.FIND_CHILD_ELEMENT:
|
||||
('POST', '/session/$sessionId/element/$id/element'),
|
||||
Command.FIND_CHILD_ELEMENTS:
|
||||
('POST', '/session/$sessionId/element/$id/elements'),
|
||||
Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'),
|
||||
Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'),
|
||||
Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'),
|
||||
Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'),
|
||||
Command.SEND_KEYS_TO_ELEMENT:
|
||||
('POST', '/session/$sessionId/element/$id/value'),
|
||||
Command.SEND_KEYS_TO_ACTIVE_ELEMENT:
|
||||
('POST', '/session/$sessionId/keys'),
|
||||
Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"),
|
||||
Command.GET_ELEMENT_VALUE:
|
||||
('GET', '/session/$sessionId/element/$id/value'),
|
||||
Command.GET_ELEMENT_TAG_NAME:
|
||||
('GET', '/session/$sessionId/element/$id/name'),
|
||||
Command.IS_ELEMENT_SELECTED:
|
||||
('GET', '/session/$sessionId/element/$id/selected'),
|
||||
Command.SET_ELEMENT_SELECTED:
|
||||
('POST', '/session/$sessionId/element/$id/selected'),
|
||||
Command.IS_ELEMENT_ENABLED:
|
||||
('GET', '/session/$sessionId/element/$id/enabled'),
|
||||
Command.IS_ELEMENT_DISPLAYED:
|
||||
('GET', '/session/$sessionId/element/$id/displayed'),
|
||||
Command.GET_ELEMENT_LOCATION:
|
||||
('GET', '/session/$sessionId/element/$id/location'),
|
||||
Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW:
|
||||
('GET', '/session/$sessionId/element/$id/location_in_view'),
|
||||
Command.GET_ELEMENT_SIZE:
|
||||
('GET', '/session/$sessionId/element/$id/size'),
|
||||
Command.GET_ELEMENT_RECT:
|
||||
('GET', '/session/$sessionId/element/$id/rect'),
|
||||
Command.GET_ELEMENT_ATTRIBUTE:
|
||||
('GET', '/session/$sessionId/element/$id/attribute/$name'),
|
||||
Command.GET_ELEMENT_PROPERTY:
|
||||
('GET', '/session/$sessionId/element/$id/property/$name'),
|
||||
Command.ELEMENT_EQUALS:
|
||||
('GET', '/session/$sessionId/element/$id/equals/$other'),
|
||||
Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'),
|
||||
Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'),
|
||||
Command.DELETE_ALL_COOKIES:
|
||||
('DELETE', '/session/$sessionId/cookie'),
|
||||
Command.DELETE_COOKIE:
|
||||
('DELETE', '/session/$sessionId/cookie/$name'),
|
||||
Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'),
|
||||
Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'),
|
||||
Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'),
|
||||
Command.CLOSE: ('DELETE', '/session/$sessionId/window'),
|
||||
Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY:
|
||||
('GET', '/session/$sessionId/element/$id/css/$propertyName'),
|
||||
Command.IMPLICIT_WAIT:
|
||||
('POST', '/session/$sessionId/timeouts/implicit_wait'),
|
||||
Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'),
|
||||
Command.SET_SCRIPT_TIMEOUT:
|
||||
('POST', '/session/$sessionId/timeouts/async_script'),
|
||||
Command.SET_TIMEOUTS:
|
||||
('POST', '/session/$sessionId/timeouts'),
|
||||
Command.DISMISS_ALERT:
|
||||
('POST', '/session/$sessionId/dismiss_alert'),
|
||||
Command.W3C_DISMISS_ALERT:
|
||||
('POST', '/session/$sessionId/alert/dismiss'),
|
||||
Command.ACCEPT_ALERT:
|
||||
('POST', '/session/$sessionId/accept_alert'),
|
||||
Command.W3C_ACCEPT_ALERT:
|
||||
('POST', '/session/$sessionId/alert/accept'),
|
||||
Command.SET_ALERT_VALUE:
|
||||
('POST', '/session/$sessionId/alert_text'),
|
||||
Command.W3C_SET_ALERT_VALUE:
|
||||
('POST', '/session/$sessionId/alert/text'),
|
||||
Command.GET_ALERT_TEXT:
|
||||
('GET', '/session/$sessionId/alert_text'),
|
||||
Command.W3C_GET_ALERT_TEXT:
|
||||
('GET', '/session/$sessionId/alert/text'),
|
||||
Command.SET_ALERT_CREDENTIALS:
|
||||
('POST', '/session/$sessionId/alert/credentials'),
|
||||
Command.CLICK:
|
||||
('POST', '/session/$sessionId/click'),
|
||||
Command.W3C_ACTIONS:
|
||||
('POST', '/session/$sessionId/actions'),
|
||||
Command.W3C_CLEAR_ACTIONS:
|
||||
('DELETE', '/session/$sessionId/actions'),
|
||||
Command.DOUBLE_CLICK:
|
||||
('POST', '/session/$sessionId/doubleclick'),
|
||||
Command.MOUSE_DOWN:
|
||||
('POST', '/session/$sessionId/buttondown'),
|
||||
Command.MOUSE_UP:
|
||||
('POST', '/session/$sessionId/buttonup'),
|
||||
Command.MOVE_TO:
|
||||
('POST', '/session/$sessionId/moveto'),
|
||||
Command.GET_WINDOW_SIZE:
|
||||
('GET', '/session/$sessionId/window/$windowHandle/size'),
|
||||
Command.SET_WINDOW_SIZE:
|
||||
('POST', '/session/$sessionId/window/$windowHandle/size'),
|
||||
Command.GET_WINDOW_POSITION:
|
||||
('GET', '/session/$sessionId/window/$windowHandle/position'),
|
||||
Command.SET_WINDOW_POSITION:
|
||||
('POST', '/session/$sessionId/window/$windowHandle/position'),
|
||||
Command.SET_WINDOW_RECT:
|
||||
('POST', '/session/$sessionId/window/rect'),
|
||||
Command.GET_WINDOW_RECT:
|
||||
('GET', '/session/$sessionId/window/rect'),
|
||||
Command.MAXIMIZE_WINDOW:
|
||||
('POST', '/session/$sessionId/window/$windowHandle/maximize'),
|
||||
Command.W3C_MAXIMIZE_WINDOW:
|
||||
('POST', '/session/$sessionId/window/maximize'),
|
||||
Command.SET_SCREEN_ORIENTATION:
|
||||
('POST', '/session/$sessionId/orientation'),
|
||||
Command.GET_SCREEN_ORIENTATION:
|
||||
('GET', '/session/$sessionId/orientation'),
|
||||
Command.SINGLE_TAP:
|
||||
('POST', '/session/$sessionId/touch/click'),
|
||||
Command.TOUCH_DOWN:
|
||||
('POST', '/session/$sessionId/touch/down'),
|
||||
Command.TOUCH_UP:
|
||||
('POST', '/session/$sessionId/touch/up'),
|
||||
Command.TOUCH_MOVE:
|
||||
('POST', '/session/$sessionId/touch/move'),
|
||||
Command.TOUCH_SCROLL:
|
||||
('POST', '/session/$sessionId/touch/scroll'),
|
||||
Command.DOUBLE_TAP:
|
||||
('POST', '/session/$sessionId/touch/doubleclick'),
|
||||
Command.LONG_PRESS:
|
||||
('POST', '/session/$sessionId/touch/longclick'),
|
||||
Command.FLICK:
|
||||
('POST', '/session/$sessionId/touch/flick'),
|
||||
Command.EXECUTE_SQL:
|
||||
('POST', '/session/$sessionId/execute_sql'),
|
||||
Command.GET_LOCATION:
|
||||
('GET', '/session/$sessionId/location'),
|
||||
Command.SET_LOCATION:
|
||||
('POST', '/session/$sessionId/location'),
|
||||
Command.GET_APP_CACHE:
|
||||
('GET', '/session/$sessionId/application_cache'),
|
||||
Command.GET_APP_CACHE_STATUS:
|
||||
('GET', '/session/$sessionId/application_cache/status'),
|
||||
Command.CLEAR_APP_CACHE:
|
||||
('DELETE', '/session/$sessionId/application_cache/clear'),
|
||||
Command.GET_NETWORK_CONNECTION:
|
||||
('GET', '/session/$sessionId/network_connection'),
|
||||
Command.SET_NETWORK_CONNECTION:
|
||||
('POST', '/session/$sessionId/network_connection'),
|
||||
Command.GET_LOCAL_STORAGE_ITEM:
|
||||
('GET', '/session/$sessionId/local_storage/key/$key'),
|
||||
Command.REMOVE_LOCAL_STORAGE_ITEM:
|
||||
('DELETE', '/session/$sessionId/local_storage/key/$key'),
|
||||
Command.GET_LOCAL_STORAGE_KEYS:
|
||||
('GET', '/session/$sessionId/local_storage'),
|
||||
Command.SET_LOCAL_STORAGE_ITEM:
|
||||
('POST', '/session/$sessionId/local_storage'),
|
||||
Command.CLEAR_LOCAL_STORAGE:
|
||||
('DELETE', '/session/$sessionId/local_storage'),
|
||||
Command.GET_LOCAL_STORAGE_SIZE:
|
||||
('GET', '/session/$sessionId/local_storage/size'),
|
||||
Command.GET_SESSION_STORAGE_ITEM:
|
||||
('GET', '/session/$sessionId/session_storage/key/$key'),
|
||||
Command.REMOVE_SESSION_STORAGE_ITEM:
|
||||
('DELETE', '/session/$sessionId/session_storage/key/$key'),
|
||||
Command.GET_SESSION_STORAGE_KEYS:
|
||||
('GET', '/session/$sessionId/session_storage'),
|
||||
Command.SET_SESSION_STORAGE_ITEM:
|
||||
('POST', '/session/$sessionId/session_storage'),
|
||||
Command.CLEAR_SESSION_STORAGE:
|
||||
('DELETE', '/session/$sessionId/session_storage'),
|
||||
Command.GET_SESSION_STORAGE_SIZE:
|
||||
('GET', '/session/$sessionId/session_storage/size'),
|
||||
Command.GET_LOG:
|
||||
('POST', '/session/$sessionId/log'),
|
||||
Command.GET_AVAILABLE_LOG_TYPES:
|
||||
('GET', '/session/$sessionId/log/types'),
|
||||
Command.CURRENT_CONTEXT_HANDLE:
|
||||
('GET', '/session/$sessionId/context'),
|
||||
Command.CONTEXT_HANDLES:
|
||||
('GET', '/session/$sessionId/contexts'),
|
||||
Command.SWITCH_TO_CONTEXT:
|
||||
('POST', '/session/$sessionId/context'),
|
||||
Command.FULLSCREEN_WINDOW:
|
||||
('POST', '/session/$sessionId/window/fullscreen'),
|
||||
Command.MINIMIZE_WINDOW:
|
||||
('POST', '/session/$sessionId/window/minimize')
|
||||
}
|
||||
|
||||
def execute(self, command, params):
|
||||
"""
|
||||
Send a command to the remote server.
|
||||
|
||||
Any path subtitutions required for the URL mapped to the command should be
|
||||
included in the command parameters.
|
||||
|
||||
:Args:
|
||||
- command - A string specifying the command to execute.
|
||||
- params - A dictionary of named parameters to send with the command as
|
||||
its JSON payload.
|
||||
"""
|
||||
command_info = self._commands[command]
|
||||
assert command_info is not None, 'Unrecognised command %s' % command
|
||||
path = string.Template(command_info[1]).substitute(params)
|
||||
if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:
|
||||
del params['sessionId']
|
||||
data = utils.dump_json(params)
|
||||
url = '%s%s' % (self._url, path)
|
||||
return self._request(command_info[0], url, body=data)
|
||||
|
||||
def _request(self, method, url, body=None):
|
||||
"""
|
||||
Send an HTTP request to the remote server.
|
||||
|
||||
:Args:
|
||||
- method - A string for the HTTP method to send the request with.
|
||||
- url - A string for the URL to send the request to.
|
||||
- body - A string for request body. Ignored unless method is POST or PUT.
|
||||
|
||||
:Returns:
|
||||
A dictionary with the server's parsed JSON response.
|
||||
"""
|
||||
LOGGER.debug('%s %s %s' % (method, url, body))
|
||||
|
||||
parsed_url = parse.urlparse(url)
|
||||
headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
|
||||
|
||||
if self.keep_alive:
|
||||
if body and method != 'POST' and method != 'PUT':
|
||||
body = None
|
||||
try:
|
||||
self._conn.request(method, parsed_url.path, body, headers)
|
||||
resp = self._conn.getresponse()
|
||||
except (httplib.HTTPException, socket.error):
|
||||
self._conn.close()
|
||||
raise
|
||||
|
||||
statuscode = resp.status
|
||||
else:
|
||||
password_manager = None
|
||||
if parsed_url.username:
|
||||
netloc = parsed_url.hostname
|
||||
if parsed_url.port:
|
||||
netloc += ":%s" % parsed_url.port
|
||||
cleaned_url = parse.urlunparse((
|
||||
parsed_url.scheme,
|
||||
netloc,
|
||||
parsed_url.path,
|
||||
parsed_url.params,
|
||||
parsed_url.query,
|
||||
parsed_url.fragment))
|
||||
password_manager = url_request.HTTPPasswordMgrWithDefaultRealm()
|
||||
password_manager.add_password(None,
|
||||
"%s://%s" % (parsed_url.scheme, netloc),
|
||||
parsed_url.username,
|
||||
parsed_url.password)
|
||||
request = Request(cleaned_url, data=body.encode('utf-8'), method=method)
|
||||
else:
|
||||
request = Request(url, data=body.encode('utf-8'), method=method)
|
||||
|
||||
for key, val in headers.items():
|
||||
request.add_header(key, val)
|
||||
|
||||
if password_manager:
|
||||
opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
|
||||
HttpErrorHandler(),
|
||||
url_request.HTTPBasicAuthHandler(password_manager))
|
||||
else:
|
||||
opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
|
||||
HttpErrorHandler())
|
||||
resp = opener.open(request, timeout=self._timeout)
|
||||
statuscode = resp.code
|
||||
if not hasattr(resp, 'getheader'):
|
||||
if hasattr(resp.headers, 'getheader'):
|
||||
resp.getheader = lambda x: resp.headers.getheader(x)
|
||||
elif hasattr(resp.headers, 'get'):
|
||||
resp.getheader = lambda x: resp.headers.get(x)
|
||||
|
||||
data = resp.read()
|
||||
try:
|
||||
if 300 <= statuscode < 304:
|
||||
return self._request('GET', resp.getheader('location'))
|
||||
body = data.decode('utf-8').replace('\x00', '').strip()
|
||||
if 399 < statuscode <= 500:
|
||||
return {'status': statuscode, 'value': body}
|
||||
content_type = []
|
||||
if resp.getheader('Content-Type') is not None:
|
||||
content_type = resp.getheader('Content-Type').split(';')
|
||||
if not any([x.startswith('image/png') for x in content_type]):
|
||||
try:
|
||||
data = utils.load_json(body.strip())
|
||||
except ValueError:
|
||||
if 199 < statuscode < 300:
|
||||
status = ErrorCode.SUCCESS
|
||||
else:
|
||||
status = ErrorCode.UNKNOWN_ERROR
|
||||
return {'status': status, 'value': body.strip()}
|
||||
|
||||
assert type(data) is dict, (
|
||||
'Invalid server response body: %s' % body)
|
||||
# Some of the drivers incorrectly return a response
|
||||
# with no 'value' field when they should return null.
|
||||
if 'value' not in data:
|
||||
data['value'] = None
|
||||
return data
|
||||
else:
|
||||
data = {'status': 0, 'value': body.strip()}
|
||||
return data
|
||||
finally:
|
||||
LOGGER.debug("Finished Request")
|
||||
resp.close()
|
134
youtube_dl/selenium/webdriver/remote/switch_to.py
Normal file
134
youtube_dl/selenium/webdriver/remote/switch_to.py
Normal file
@ -0,0 +1,134 @@
|
||||
# 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 .command import Command
|
||||
from selenium.webdriver.common.alert import Alert
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.common.exceptions import NoSuchElementException, NoSuchFrameException, NoSuchWindowException
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str
|
||||
|
||||
|
||||
class SwitchTo:
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
|
||||
@property
|
||||
def active_element(self):
|
||||
"""
|
||||
Returns the element with focus, or BODY if nothing has focus.
|
||||
|
||||
:Usage:
|
||||
element = driver.switch_to.active_element
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
return self._driver.execute(Command.W3C_GET_ACTIVE_ELEMENT)['value']
|
||||
else:
|
||||
return self._driver.execute(Command.GET_ACTIVE_ELEMENT)['value']
|
||||
|
||||
@property
|
||||
def alert(self):
|
||||
"""
|
||||
Switches focus to an alert on the page.
|
||||
|
||||
:Usage:
|
||||
alert = driver.switch_to.alert
|
||||
"""
|
||||
alert = Alert(self._driver)
|
||||
alert.text
|
||||
return alert
|
||||
|
||||
def default_content(self):
|
||||
"""
|
||||
Switch focus to the default frame.
|
||||
|
||||
:Usage:
|
||||
driver.switch_to.default_content()
|
||||
"""
|
||||
self._driver.execute(Command.SWITCH_TO_FRAME, {'id': None})
|
||||
|
||||
def frame(self, frame_reference):
|
||||
"""
|
||||
Switches focus to the specified frame, by index, name, or webelement.
|
||||
|
||||
:Args:
|
||||
- frame_reference: The name of the window to switch to, an integer representing the index,
|
||||
or a webelement that is an (i)frame to switch to.
|
||||
|
||||
:Usage:
|
||||
driver.switch_to.frame('frame_name')
|
||||
driver.switch_to.frame(1)
|
||||
driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0])
|
||||
"""
|
||||
if isinstance(frame_reference, basestring) and self._driver.w3c:
|
||||
try:
|
||||
frame_reference = self._driver.find_element(By.ID, frame_reference)
|
||||
except NoSuchElementException:
|
||||
try:
|
||||
frame_reference = self._driver.find_element(By.NAME, frame_reference)
|
||||
except NoSuchElementException:
|
||||
raise NoSuchFrameException(frame_reference)
|
||||
|
||||
self._driver.execute(Command.SWITCH_TO_FRAME, {'id': frame_reference})
|
||||
|
||||
def parent_frame(self):
|
||||
"""
|
||||
Switches focus to the parent context. If the current context is the top
|
||||
level browsing context, the context remains unchanged.
|
||||
|
||||
:Usage:
|
||||
driver.switch_to.parent_frame()
|
||||
"""
|
||||
self._driver.execute(Command.SWITCH_TO_PARENT_FRAME)
|
||||
|
||||
def window(self, window_name):
|
||||
"""
|
||||
Switches focus to the specified window.
|
||||
|
||||
:Args:
|
||||
- window_name: The name or window handle of the window to switch to.
|
||||
|
||||
:Usage:
|
||||
driver.switch_to.window('main')
|
||||
"""
|
||||
if self._driver.w3c:
|
||||
self._w3c_window(window_name)
|
||||
return
|
||||
data = {'name': window_name}
|
||||
self._driver.execute(Command.SWITCH_TO_WINDOW, data)
|
||||
|
||||
def _w3c_window(self, window_name):
|
||||
def send_handle(h):
|
||||
self._driver.execute(Command.SWITCH_TO_WINDOW, {'handle': h})
|
||||
|
||||
try:
|
||||
# Try using it as a handle first.
|
||||
send_handle(window_name)
|
||||
except NoSuchWindowException as e:
|
||||
# Check every window to try to find the given window name.
|
||||
original_handle = self._driver.current_window_handle
|
||||
handles = self._driver.window_handles
|
||||
for handle in handles:
|
||||
send_handle(handle)
|
||||
current_name = self._driver.execute_script('return window.name')
|
||||
if window_name == current_name:
|
||||
return
|
||||
send_handle(original_handle)
|
||||
raise e
|
113
youtube_dl/selenium/webdriver/remote/utils.py
Normal file
113
youtube_dl/selenium/webdriver/remote/utils.py
Normal file
@ -0,0 +1,113 @@
|
||||
# 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 json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def format_json(json_struct):
|
||||
return json.dumps(json_struct, indent=4)
|
||||
|
||||
|
||||
def dump_json(json_struct):
|
||||
return json.dumps(json_struct)
|
||||
|
||||
|
||||
def load_json(s):
|
||||
return json.loads(s)
|
||||
|
||||
|
||||
def handle_find_element_exception(e):
|
||||
if ("Unable to find" in e.response["value"]["message"] or "Unable to locate" in e.response["value"]["message"]):
|
||||
raise NoSuchElementException("Unable to locate element:")
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def return_value_if_exists(resp):
|
||||
if resp and "value" in resp:
|
||||
return resp["value"]
|
||||
|
||||
|
||||
def get_root_parent(elem):
|
||||
parent = elem.parent
|
||||
while True:
|
||||
try:
|
||||
parent.parent
|
||||
parent = parent.parent
|
||||
except AttributeError:
|
||||
return parent
|
||||
|
||||
|
||||
def unzip_to_temp_dir(zip_file_name):
|
||||
"""Unzip zipfile to a temporary directory.
|
||||
|
||||
The directory of the unzipped files is returned if success,
|
||||
otherwise None is returned. """
|
||||
if not zip_file_name or not os.path.exists(zip_file_name):
|
||||
return None
|
||||
|
||||
zf = zipfile.ZipFile(zip_file_name)
|
||||
|
||||
if zf.testzip() is not None:
|
||||
return None
|
||||
|
||||
# Unzip the files into a temporary directory
|
||||
LOGGER.info("Extracting zipped file: %s" % zip_file_name)
|
||||
tempdir = tempfile.mkdtemp()
|
||||
|
||||
try:
|
||||
# Create directories that don't exist
|
||||
for zip_name in zf.namelist():
|
||||
# We have no knowledge on the os where the zipped file was
|
||||
# created, so we restrict to zip files with paths without
|
||||
# charactor "\" and "/".
|
||||
name = (zip_name.replace("\\", os.path.sep).
|
||||
replace("/", os.path.sep))
|
||||
dest = os.path.join(tempdir, name)
|
||||
if (name.endswith(os.path.sep) and not os.path.exists(dest)):
|
||||
os.mkdir(dest)
|
||||
LOGGER.debug("Directory %s created." % dest)
|
||||
|
||||
# Copy files
|
||||
for zip_name in zf.namelist():
|
||||
# We have no knowledge on the os where the zipped file was
|
||||
# created, so we restrict to zip files with paths without
|
||||
# charactor "\" and "/".
|
||||
name = (zip_name.replace("\\", os.path.sep).
|
||||
replace("/", os.path.sep))
|
||||
dest = os.path.join(tempdir, name)
|
||||
if not (name.endswith(os.path.sep)):
|
||||
LOGGER.debug("Copying file %s......" % dest)
|
||||
outfile = open(dest, 'wb')
|
||||
outfile.write(zf.read(zip_name))
|
||||
outfile.close()
|
||||
LOGGER.debug("File %s copied." % dest)
|
||||
|
||||
LOGGER.info("Unzipped file can be found at %s" % tempdir)
|
||||
return tempdir
|
||||
|
||||
except IOError as err:
|
||||
LOGGER.error("Error in extracting webdriver.xpi: %s" % err)
|
||||
return None
|
1238
youtube_dl/selenium/webdriver/remote/webdriver.py
Normal file
1238
youtube_dl/selenium/webdriver/remote/webdriver.py
Normal file
File diff suppressed because it is too large
Load Diff
701
youtube_dl/selenium/webdriver/remote/webelement.py
Normal file
701
youtube_dl/selenium/webdriver/remote/webelement.py
Normal file
@ -0,0 +1,701 @@
|
||||
# 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 hashlib
|
||||
import os
|
||||
import pkgutil
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.utils import keys_to_typing
|
||||
from .command import Command
|
||||
|
||||
# Python 3 imports
|
||||
try:
|
||||
str = basestring
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from StringIO import StringIO as IOStream
|
||||
except ImportError: # 3+
|
||||
from io import BytesIO as IOStream
|
||||
|
||||
# not relying on __package__ here as it can be `None` in some situations (see #4558)
|
||||
_pkg = '.'.join(__name__.split('.')[:-1])
|
||||
getAttribute_js = pkgutil.get_data(_pkg, 'getAttribute.js').decode('utf8')
|
||||
isDisplayed_js = pkgutil.get_data(_pkg, 'isDisplayed.js').decode('utf8')
|
||||
|
||||
|
||||
class WebElement(object):
|
||||
"""Represents a DOM element.
|
||||
|
||||
Generally, all interesting operations that interact with a document will be
|
||||
performed through this interface.
|
||||
|
||||
All method calls will do a freshness check to ensure that the element
|
||||
reference is still valid. This essentially determines whether or not the
|
||||
element is still attached to the DOM. If this test fails, then an
|
||||
``StaleElementReferenceException`` is thrown, and all future calls to this
|
||||
instance will fail."""
|
||||
|
||||
def __init__(self, parent, id_, w3c=False):
|
||||
self._parent = parent
|
||||
self._id = id_
|
||||
self._w3c = w3c
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
|
||||
type(self), self._parent.session_id, self._id)
|
||||
|
||||
@property
|
||||
def tag_name(self):
|
||||
"""This element's ``tagName`` property."""
|
||||
return self._execute(Command.GET_ELEMENT_TAG_NAME)['value']
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""The text of the element."""
|
||||
return self._execute(Command.GET_ELEMENT_TEXT)['value']
|
||||
|
||||
def click(self):
|
||||
"""Clicks the element."""
|
||||
self._execute(Command.CLICK_ELEMENT)
|
||||
|
||||
def submit(self):
|
||||
"""Submits a form."""
|
||||
if self._w3c:
|
||||
form = self.find_element(By.XPATH, "./ancestor-or-self::form")
|
||||
self._parent.execute_script(
|
||||
"var e = arguments[0].ownerDocument.createEvent('Event');"
|
||||
"e.initEvent('submit', true, true);"
|
||||
"if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }", form)
|
||||
else:
|
||||
self._execute(Command.SUBMIT_ELEMENT)
|
||||
|
||||
def clear(self):
|
||||
"""Clears the text if it's a text entry element."""
|
||||
self._execute(Command.CLEAR_ELEMENT)
|
||||
|
||||
def get_property(self, name):
|
||||
"""
|
||||
Gets the given property of the element.
|
||||
|
||||
:Args:
|
||||
- name - Name of the property to retrieve.
|
||||
|
||||
Example::
|
||||
|
||||
text_length = target_element.get_property("text_length")
|
||||
"""
|
||||
try:
|
||||
return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"]
|
||||
except WebDriverException:
|
||||
# if we hit an end point that doesnt understand getElementProperty lets fake it
|
||||
return self.parent.execute_script('return arguments[0][arguments[1]]', self, name)
|
||||
|
||||
def get_attribute(self, name):
|
||||
"""Gets the given attribute or property of the element.
|
||||
|
||||
This method will first try to return the value of a property with the
|
||||
given name. If a property with that name doesn't exist, it returns the
|
||||
value of the attribute with the same name. If there's no attribute with
|
||||
that name, ``None`` is returned.
|
||||
|
||||
Values which are considered truthy, that is equals "true" or "false",
|
||||
are returned as booleans. All other non-``None`` values are returned
|
||||
as strings. For attributes or properties which do not exist, ``None``
|
||||
is returned.
|
||||
|
||||
:Args:
|
||||
- name - Name of the attribute/property to retrieve.
|
||||
|
||||
Example::
|
||||
|
||||
# Check if the "active" CSS class is applied to an element.
|
||||
is_active = "active" in target_element.get_attribute("class")
|
||||
|
||||
"""
|
||||
|
||||
attributeValue = ''
|
||||
if self._w3c:
|
||||
attributeValue = self.parent.execute_script(
|
||||
"return (%s).apply(null, arguments);" % getAttribute_js,
|
||||
self, name)
|
||||
else:
|
||||
resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name': name})
|
||||
attributeValue = resp.get('value')
|
||||
if attributeValue is not None:
|
||||
if name != 'value' and attributeValue.lower() in ('true', 'false'):
|
||||
attributeValue = attributeValue.lower()
|
||||
return attributeValue
|
||||
|
||||
def is_selected(self):
|
||||
"""Returns whether the element is selected.
|
||||
|
||||
Can be used to check if a checkbox or radio button is selected.
|
||||
"""
|
||||
return self._execute(Command.IS_ELEMENT_SELECTED)['value']
|
||||
|
||||
def is_enabled(self):
|
||||
"""Returns whether the element is enabled."""
|
||||
return self._execute(Command.IS_ELEMENT_ENABLED)['value']
|
||||
|
||||
def find_element_by_id(self, id_):
|
||||
"""Finds element within this element's children by ID.
|
||||
|
||||
:Args:
|
||||
- id\_ - ID of child element to locate.
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
foo_element = element.find_element_by_id('foo')
|
||||
"""
|
||||
return self.find_element(by=By.ID, value=id_)
|
||||
|
||||
def find_elements_by_id(self, id_):
|
||||
"""Finds a list of elements within this element's children by ID.
|
||||
Will return a list of webelements if found, or an empty list if not.
|
||||
|
||||
:Args:
|
||||
- id\_ - Id of child element to find.
|
||||
|
||||
:Returns:
|
||||
- list of WebElement - a list with elements if any was found. An
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_id('foo')
|
||||
"""
|
||||
return self.find_elements(by=By.ID, value=id_)
|
||||
|
||||
def find_element_by_name(self, name):
|
||||
"""Finds element within this element's children by name.
|
||||
|
||||
:Args:
|
||||
- name - name property of the element to find.
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_name('foo')
|
||||
"""
|
||||
return self.find_element(by=By.NAME, value=name)
|
||||
|
||||
def find_elements_by_name(self, name):
|
||||
"""Finds a list of elements within this element's children by name.
|
||||
|
||||
:Args:
|
||||
- name - name property to search for.
|
||||
|
||||
:Returns:
|
||||
- list of webelement - a list with elements if any was found. an
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_name('foo')
|
||||
"""
|
||||
return self.find_elements(by=By.NAME, value=name)
|
||||
|
||||
def find_element_by_link_text(self, link_text):
|
||||
"""Finds element within this element's children by visible link text.
|
||||
|
||||
:Args:
|
||||
- link_text - Link text string to search for.
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_link_text('Sign In')
|
||||
"""
|
||||
return self.find_element(by=By.LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_link_text(self, link_text):
|
||||
"""Finds a list of elements within this element's children by visible link text.
|
||||
|
||||
:Args:
|
||||
- link_text - Link text string to search for.
|
||||
|
||||
:Returns:
|
||||
- list of webelement - a list with elements if any was found. an
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_link_text('Sign In')
|
||||
"""
|
||||
return self.find_elements(by=By.LINK_TEXT, value=link_text)
|
||||
|
||||
def find_element_by_partial_link_text(self, link_text):
|
||||
"""Finds element within this element's children by partially visible link text.
|
||||
|
||||
:Args:
|
||||
- link_text: The text of the element to partially match on.
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_partial_link_text('Sign')
|
||||
"""
|
||||
return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_partial_link_text(self, link_text):
|
||||
"""Finds a list of elements within this element's children by link text.
|
||||
|
||||
:Args:
|
||||
- link_text: The text of the element to partial match on.
|
||||
|
||||
:Returns:
|
||||
- list of webelement - a list with elements if any was found. an
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_partial_link_text('Sign')
|
||||
"""
|
||||
return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_element_by_tag_name(self, name):
|
||||
"""Finds element within this element's children by tag name.
|
||||
|
||||
:Args:
|
||||
- name - name of html tag (eg: h1, a, span)
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_tag_name('h1')
|
||||
"""
|
||||
return self.find_element(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_elements_by_tag_name(self, name):
|
||||
"""Finds a list of elements within this element's children by tag name.
|
||||
|
||||
:Args:
|
||||
- name - name of html tag (eg: h1, a, span)
|
||||
|
||||
:Returns:
|
||||
- list of WebElement - a list with elements if any was found. An
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_tag_name('h1')
|
||||
"""
|
||||
return self.find_elements(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_element_by_xpath(self, xpath):
|
||||
"""Finds element by xpath.
|
||||
|
||||
:Args:
|
||||
- xpath - xpath of element to locate. "//input[@class='myelement']"
|
||||
|
||||
Note: The base path will be relative to this element's location.
|
||||
|
||||
This will select the first link under this element.
|
||||
|
||||
::
|
||||
|
||||
myelement.find_element_by_xpath(".//a")
|
||||
|
||||
However, this will select the first link on the page.
|
||||
|
||||
::
|
||||
|
||||
myelement.find_element_by_xpath("//a")
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_xpath('//div/td[1]')
|
||||
"""
|
||||
return self.find_element(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_elements_by_xpath(self, xpath):
|
||||
"""Finds elements within the element by xpath.
|
||||
|
||||
:Args:
|
||||
- xpath - xpath locator string.
|
||||
|
||||
Note: The base path will be relative to this element's location.
|
||||
|
||||
This will select all links under this element.
|
||||
|
||||
::
|
||||
|
||||
myelement.find_elements_by_xpath(".//a")
|
||||
|
||||
However, this will select all links in the page itself.
|
||||
|
||||
::
|
||||
|
||||
myelement.find_elements_by_xpath("//a")
|
||||
|
||||
:Returns:
|
||||
- list of WebElement - a list with elements if any was found. An
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_xpath("//div[contains(@class, 'foo')]")
|
||||
|
||||
"""
|
||||
return self.find_elements(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_element_by_class_name(self, name):
|
||||
"""Finds element within this element's children by class name.
|
||||
|
||||
:Args:
|
||||
- name: The class name of the element to find.
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_class_name('foo')
|
||||
"""
|
||||
return self.find_element(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_elements_by_class_name(self, name):
|
||||
"""Finds a list of elements within this element's children by class name.
|
||||
|
||||
:Args:
|
||||
- name: The class name of the elements to find.
|
||||
|
||||
:Returns:
|
||||
- list of WebElement - a list with elements if any was found. An
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_class_name('foo')
|
||||
"""
|
||||
return self.find_elements(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_element_by_css_selector(self, css_selector):
|
||||
"""Finds element within this element's children by CSS selector.
|
||||
|
||||
:Args:
|
||||
- css_selector - CSS selector string, ex: 'a.nav#home'
|
||||
|
||||
:Returns:
|
||||
- WebElement - the element if it was found
|
||||
|
||||
:Raises:
|
||||
- NoSuchElementException - if the element wasn't found
|
||||
|
||||
:Usage:
|
||||
element = element.find_element_by_css_selector('#foo')
|
||||
"""
|
||||
return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def find_elements_by_css_selector(self, css_selector):
|
||||
"""Finds a list of elements within this element's children by CSS selector.
|
||||
|
||||
:Args:
|
||||
- css_selector - CSS selector string, ex: 'a.nav#home'
|
||||
|
||||
:Returns:
|
||||
- list of WebElement - a list with elements if any was found. An
|
||||
empty list if not
|
||||
|
||||
:Usage:
|
||||
elements = element.find_elements_by_css_selector('.foo')
|
||||
"""
|
||||
return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def send_keys(self, *value):
|
||||
"""Simulates typing into the element.
|
||||
|
||||
:Args:
|
||||
- value - A string for typing, or setting form fields. For setting
|
||||
file inputs, this could be a local file path.
|
||||
|
||||
Use this to send simple key events or to fill out form fields::
|
||||
|
||||
form_textfield = driver.find_element_by_name('username')
|
||||
form_textfield.send_keys("admin")
|
||||
|
||||
This can also be used to set file inputs.
|
||||
|
||||
::
|
||||
|
||||
file_input = driver.find_element_by_name('profilePic')
|
||||
file_input.send_keys("path/to/profilepic.gif")
|
||||
# Generally it's better to wrap the file path in one of the methods
|
||||
# in os.path to return the actual path to support cross OS testing.
|
||||
# file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
|
||||
|
||||
"""
|
||||
# transfer file to another machine only if remote driver is used
|
||||
# the same behaviour as for java binding
|
||||
if self.parent._is_remote:
|
||||
local_file = self.parent.file_detector.is_local_file(*value)
|
||||
if local_file is not None:
|
||||
value = self._upload(local_file)
|
||||
|
||||
self._execute(Command.SEND_KEYS_TO_ELEMENT,
|
||||
{'text': "".join(keys_to_typing(value)),
|
||||
'value': keys_to_typing(value)})
|
||||
|
||||
# RenderedWebElement Items
|
||||
def is_displayed(self):
|
||||
"""Whether the element is visible to a user."""
|
||||
# Only go into this conditional for browsers that don't use the atom themselves
|
||||
if self._w3c and self.parent.capabilities['browserName'] == 'safari':
|
||||
return self.parent.execute_script(
|
||||
"return (%s).apply(null, arguments);" % isDisplayed_js,
|
||||
self)
|
||||
else:
|
||||
return self._execute(Command.IS_ELEMENT_DISPLAYED)['value']
|
||||
|
||||
@property
|
||||
def location_once_scrolled_into_view(self):
|
||||
"""THIS PROPERTY MAY CHANGE WITHOUT WARNING. Use this to discover
|
||||
where on the screen an element is so that we can click it. This method
|
||||
should cause the element to be scrolled into view.
|
||||
|
||||
Returns the top lefthand corner location on the screen, or ``None`` if
|
||||
the element is not visible.
|
||||
|
||||
"""
|
||||
if self._w3c:
|
||||
old_loc = self._execute(Command.W3C_EXECUTE_SCRIPT, {
|
||||
'script': "arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect()",
|
||||
'args': [self]})['value']
|
||||
return {"x": round(old_loc['x']),
|
||||
"y": round(old_loc['y'])}
|
||||
else:
|
||||
return self._execute(Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW)['value']
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""The size of the element."""
|
||||
size = {}
|
||||
if self._w3c:
|
||||
size = self._execute(Command.GET_ELEMENT_RECT)['value']
|
||||
else:
|
||||
size = self._execute(Command.GET_ELEMENT_SIZE)['value']
|
||||
new_size = {"height": size["height"],
|
||||
"width": size["width"]}
|
||||
return new_size
|
||||
|
||||
def value_of_css_property(self, property_name):
|
||||
"""The value of a CSS property."""
|
||||
return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {
|
||||
'propertyName': property_name})['value']
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
"""The location of the element in the renderable canvas."""
|
||||
if self._w3c:
|
||||
old_loc = self._execute(Command.GET_ELEMENT_RECT)['value']
|
||||
else:
|
||||
old_loc = self._execute(Command.GET_ELEMENT_LOCATION)['value']
|
||||
new_loc = {"x": round(old_loc['x']),
|
||||
"y": round(old_loc['y'])}
|
||||
return new_loc
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
"""A dictionary with the size and location of the element."""
|
||||
return self._execute(Command.GET_ELEMENT_RECT)['value']
|
||||
|
||||
@property
|
||||
def screenshot_as_base64(self):
|
||||
"""
|
||||
Gets the screenshot of the current element as a base64 encoded string.
|
||||
|
||||
:Usage:
|
||||
img_b64 = element.screenshot_as_base64
|
||||
"""
|
||||
return self._execute(Command.ELEMENT_SCREENSHOT)['value']
|
||||
|
||||
@property
|
||||
def screenshot_as_png(self):
|
||||
"""
|
||||
Gets the screenshot of the current element as a binary data.
|
||||
|
||||
:Usage:
|
||||
element_png = element.screenshot_as_png
|
||||
"""
|
||||
return base64.b64decode(self.screenshot_as_base64.encode('ascii'))
|
||||
|
||||
def screenshot(self, filename):
|
||||
"""
|
||||
Saves a screenshot of the current element to a PNG image file. Returns
|
||||
False if there is any IOError, else returns True. Use full paths in
|
||||
your filename.
|
||||
|
||||
:Args:
|
||||
- filename: The full path you wish to save your screenshot to. This
|
||||
should end with a `.png` extension.
|
||||
|
||||
:Usage:
|
||||
element.screenshot('/Screenshots/foo.png')
|
||||
"""
|
||||
if not filename.lower().endswith('.png'):
|
||||
warnings.warn("name used for saved screenshot does not match file "
|
||||
"type. It should end with a `.png` extension", UserWarning)
|
||||
png = self.screenshot_as_png
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(png)
|
||||
except IOError:
|
||||
return False
|
||||
finally:
|
||||
del png
|
||||
return True
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
"""Internal reference to the WebDriver instance this element was found from."""
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Internal ID used by selenium.
|
||||
|
||||
This is mainly for internal use. Simple use cases such as checking if 2
|
||||
webelements refer to the same element, can be done using ``==``::
|
||||
|
||||
if element1 == element2:
|
||||
print("These 2 are equal")
|
||||
|
||||
"""
|
||||
return self._id
|
||||
|
||||
def __eq__(self, element):
|
||||
return hasattr(element, 'id') and self._id == element.id
|
||||
|
||||
def __ne__(self, element):
|
||||
return not self.__eq__(element)
|
||||
|
||||
# Private Methods
|
||||
def _execute(self, command, params=None):
|
||||
"""Executes a command against the underlying HTML element.
|
||||
|
||||
Args:
|
||||
command: The name of the command to _execute as a string.
|
||||
params: A dictionary of named parameters to send with the command.
|
||||
|
||||
Returns:
|
||||
The command's JSON response loaded into a dictionary object.
|
||||
"""
|
||||
if not params:
|
||||
params = {}
|
||||
params['id'] = self._id
|
||||
return self._parent.execute(command, params)
|
||||
|
||||
def find_element(self, by=By.ID, value=None):
|
||||
"""
|
||||
'Private' method used by the find_element_by_* methods.
|
||||
|
||||
:Usage:
|
||||
Use the corresponding find_element_by_* instead of this.
|
||||
|
||||
:rtype: WebElement
|
||||
"""
|
||||
if self._w3c:
|
||||
if by == By.ID:
|
||||
by = By.CSS_SELECTOR
|
||||
value = '[id="%s"]' % value
|
||||
elif by == By.TAG_NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
elif by == By.CLASS_NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
value = ".%s" % value
|
||||
elif by == By.NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
value = '[name="%s"]' % value
|
||||
|
||||
return self._execute(Command.FIND_CHILD_ELEMENT,
|
||||
{"using": by, "value": value})['value']
|
||||
|
||||
def find_elements(self, by=By.ID, value=None):
|
||||
"""
|
||||
'Private' method used by the find_elements_by_* methods.
|
||||
|
||||
:Usage:
|
||||
Use the corresponding find_elements_by_* instead of this.
|
||||
|
||||
:rtype: list of WebElement
|
||||
"""
|
||||
if self._w3c:
|
||||
if by == By.ID:
|
||||
by = By.CSS_SELECTOR
|
||||
value = '[id="%s"]' % value
|
||||
elif by == By.TAG_NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
elif by == By.CLASS_NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
value = ".%s" % value
|
||||
elif by == By.NAME:
|
||||
by = By.CSS_SELECTOR
|
||||
value = '[name="%s"]' % value
|
||||
|
||||
return self._execute(Command.FIND_CHILD_ELEMENTS,
|
||||
{"using": by, "value": value})['value']
|
||||
|
||||
def __hash__(self):
|
||||
return int(hashlib.md5(self._id.encode('utf-8')).hexdigest(), 16)
|
||||
|
||||
def _upload(self, filename):
|
||||
fp = IOStream()
|
||||
zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED)
|
||||
zipped.write(filename, os.path.split(filename)[1])
|
||||
zipped.close()
|
||||
content = base64.encodestring(fp.getvalue())
|
||||
if not isinstance(content, str):
|
||||
content = content.decode('utf-8')
|
||||
try:
|
||||
return self._execute(Command.UPLOAD_FILE, {'file': content})['value']
|
||||
except WebDriverException as e:
|
||||
if "Unrecognized command: POST" in e.__str__():
|
||||
return filename
|
||||
elif "Command not found: POST " in e.__str__():
|
||||
return filename
|
||||
elif '{"status":405,"value":["GET","HEAD","DELETE"]}' in e.__str__():
|
||||
return filename
|
||||
else:
|
||||
raise e
|
16
youtube_dl/selenium/webdriver/safari/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/safari/__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.
|
56
youtube_dl/selenium/webdriver/safari/service.py
Normal file
56
youtube_dl/selenium/webdriver/safari/service.py
Normal file
@ -0,0 +1,56 @@
|
||||
# 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
|
||||
from selenium.webdriver.common import service, utils
|
||||
from subprocess import PIPE
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
"""
|
||||
Object that manages the starting and stopping of the SafariDriver
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path, port=0, quiet=False):
|
||||
"""
|
||||
Creates a new instance of the Service
|
||||
|
||||
:Args:
|
||||
- executable_path : Path to the SafariDriver
|
||||
- port : Port the service is running on """
|
||||
|
||||
if not os.path.exists(executable_path):
|
||||
raise Exception("SafariDriver requires Safari 10 on OSX El Capitan or greater")
|
||||
|
||||
if port == 0:
|
||||
port = utils.free_port()
|
||||
|
||||
self.quiet = quiet
|
||||
log = PIPE
|
||||
if quiet:
|
||||
log = open(os.devnull, 'w')
|
||||
service.Service.__init__(self, executable_path, port, log)
|
||||
|
||||
def command_line_args(self):
|
||||
return ["-p", "%s" % self.port]
|
||||
|
||||
@property
|
||||
def service_url(self):
|
||||
"""
|
||||
Gets the url of the SafariDriver Service
|
||||
"""
|
||||
return "http://localhost:%d" % self.port
|
71
youtube_dl/selenium/webdriver/safari/webdriver.py
Normal file
71
youtube_dl/selenium/webdriver/safari/webdriver.py
Normal file
@ -0,0 +1,71 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
try:
|
||||
import http.client as http_client
|
||||
except ImportError:
|
||||
import httplib as http_client
|
||||
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from .service import Service ##
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
"""
|
||||
Controls the SafariDriver and allows you to drive the browser.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, port=0, executable_path="/usr/bin/safaridriver", reuse_service=False,
|
||||
desired_capabilities=DesiredCapabilities.SAFARI, quiet=False):
|
||||
"""
|
||||
|
||||
Creates a new Safari driver instance and launches or finds a running safaridriver service.
|
||||
|
||||
:Args:
|
||||
- port - The port on which the safaridriver service should listen for new connections. If zero, a free port will be found.
|
||||
- quiet - If True, the driver's stdout and stderr is suppressed.
|
||||
- executable_path - Path to a custom safaridriver executable to be used. If absent, /usr/bin/safaridriver is used.
|
||||
- desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Safari switches).
|
||||
- reuse_service - If True, do not spawn a safaridriver instance; instead, connect to an already-running service that was launched externally.
|
||||
"""
|
||||
|
||||
self._reuse_service = reuse_service
|
||||
self.service = Service(executable_path, port=port, quiet=False) ##
|
||||
|
||||
if not reuse_service:
|
||||
self.service.start()
|
||||
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=self.service.service_url,
|
||||
desired_capabilities=desired_capabilities)
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
"""
|
||||
Closes the browser and shuts down the SafariDriver executable
|
||||
that is started when starting the SafariDriver
|
||||
"""
|
||||
try:
|
||||
RemoteWebDriver.quit(self)
|
||||
except http_client.BadStatusLine:
|
||||
pass
|
||||
finally:
|
||||
if not self._reuse_service:
|
||||
self.service.stop()
|
16
youtube_dl/selenium/webdriver/support/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/support/__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.
|
@ -0,0 +1,79 @@
|
||||
# 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 AbstractEventListener(object):
|
||||
"""
|
||||
Event listener must subclass and implement this fully or partially
|
||||
"""
|
||||
|
||||
def before_navigate_to(self, url, driver):
|
||||
pass
|
||||
|
||||
def after_navigate_to(self, url, driver):
|
||||
pass
|
||||
|
||||
def before_navigate_back(self, driver):
|
||||
pass
|
||||
|
||||
def after_navigate_back(self, driver):
|
||||
pass
|
||||
|
||||
def before_navigate_forward(self, driver):
|
||||
pass
|
||||
|
||||
def after_navigate_forward(self, driver):
|
||||
pass
|
||||
|
||||
def before_find(self, by, value, driver):
|
||||
pass
|
||||
|
||||
def after_find(self, by, value, driver):
|
||||
pass
|
||||
|
||||
def before_click(self, element, driver):
|
||||
pass
|
||||
|
||||
def after_click(self, element, driver):
|
||||
pass
|
||||
|
||||
def before_change_value_of(self, element, driver):
|
||||
pass
|
||||
|
||||
def after_change_value_of(self, element, driver):
|
||||
pass
|
||||
|
||||
def before_execute_script(self, script, driver):
|
||||
pass
|
||||
|
||||
def after_execute_script(self, script, driver):
|
||||
pass
|
||||
|
||||
def before_close(self, driver):
|
||||
pass
|
||||
|
||||
def after_close(self, driver):
|
||||
pass
|
||||
|
||||
def before_quit(self, driver):
|
||||
pass
|
||||
|
||||
def after_quit(self, driver):
|
||||
pass
|
||||
|
||||
def on_exception(self, exception, driver):
|
||||
pass
|
310
youtube_dl/selenium/webdriver/support/color.py
Normal file
310
youtube_dl/selenium/webdriver/support/color.py
Normal file
@ -0,0 +1,310 @@
|
||||
# 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.
|
||||
|
||||
RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$"
|
||||
RGB_PCT_PATTERN = r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$"
|
||||
RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
RGBA_PCT_PATTERN = r"^\s*rgba\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
HEX_PATTERN = r"#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})"
|
||||
HEX3_PATTERN = r"#([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])"
|
||||
HSL_PATTERN = r"^\s*hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)\s*$"
|
||||
HSLA_PATTERN = r"^\s*hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
|
||||
|
||||
|
||||
class Color(object):
|
||||
"""
|
||||
Color conversion support class
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from selenium.webdriver.support.color import Color
|
||||
|
||||
print(Color.from_string('#00ff33').rgba)
|
||||
print(Color.from_string('rgb(1, 255, 3)').hex)
|
||||
print(Color.from_string('blue').rgba)
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_string(str_):
|
||||
import re
|
||||
|
||||
class Matcher(object):
|
||||
def __init__(self):
|
||||
self.match_obj = None
|
||||
|
||||
def match(self, pattern, str_):
|
||||
self.match_obj = re.match(pattern, str_)
|
||||
return self.match_obj
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
return () if self.match_obj is None else self.match_obj.groups()
|
||||
|
||||
m = Matcher()
|
||||
|
||||
if m.match(RGB_PATTERN, str_):
|
||||
return Color(*m.groups)
|
||||
elif m.match(RGB_PCT_PATTERN, str_):
|
||||
rgb = tuple([float(each) / 100 * 255 for each in m.groups])
|
||||
return Color(*rgb)
|
||||
elif m.match(RGBA_PATTERN, str_):
|
||||
return Color(*m.groups)
|
||||
elif m.match(RGBA_PCT_PATTERN, str_):
|
||||
rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]])
|
||||
return Color(*rgba)
|
||||
elif m.match(HEX_PATTERN, str_):
|
||||
rgb = tuple([int(each, 16) for each in m.groups])
|
||||
return Color(*rgb)
|
||||
elif m.match(HEX3_PATTERN, str_):
|
||||
rgb = tuple([int(each * 2, 16) for each in m.groups])
|
||||
return Color(*rgb)
|
||||
elif m.match(HSL_PATTERN, str_) or m.match(HSLA_PATTERN, str_):
|
||||
return Color._from_hsl(*m.groups)
|
||||
elif str_.upper() in Colors.keys():
|
||||
return Colors[str_.upper()]
|
||||
else:
|
||||
raise ValueError("Could not convert %s into color" % str_)
|
||||
|
||||
@staticmethod
|
||||
def _from_hsl(h, s, l, a=1):
|
||||
h = float(h) / 360
|
||||
s = float(s) / 100
|
||||
l = float(l) / 100
|
||||
|
||||
if s == 0:
|
||||
r = l
|
||||
g = r
|
||||
b = r
|
||||
else:
|
||||
luminocity2 = l * (1 + s) if l < 0.5 else l + s - l * s
|
||||
luminocity1 = 2 * l - luminocity2
|
||||
|
||||
def hue_to_rgb(lum1, lum2, hue):
|
||||
if hue < 0.0:
|
||||
hue += 1
|
||||
if hue > 1.0:
|
||||
hue -= 1
|
||||
|
||||
if hue < 1.0 / 6.0:
|
||||
return (lum1 + (lum2 - lum1) * 6.0 * hue)
|
||||
elif hue < 1.0 / 2.0:
|
||||
return lum2
|
||||
elif hue < 2.0 / 3.0:
|
||||
return lum1 + (lum2 - lum1) * ((2.0 / 3.0) - hue) * 6.0
|
||||
else:
|
||||
return lum1
|
||||
|
||||
r = hue_to_rgb(luminocity1, luminocity2, h + 1.0 / 3.0)
|
||||
g = hue_to_rgb(luminocity1, luminocity2, h)
|
||||
b = hue_to_rgb(luminocity1, luminocity2, h - 1.0 / 3.0)
|
||||
|
||||
return Color(round(r * 255), round(g * 255), round(b * 255), a)
|
||||
|
||||
def __init__(self, red, green, blue, alpha=1):
|
||||
self.red = int(red)
|
||||
self.green = int(green)
|
||||
self.blue = int(blue)
|
||||
self.alpha = "1" if float(alpha) == 1 else str(float(alpha) or 0)
|
||||
|
||||
@property
|
||||
def rgb(self):
|
||||
return "rgb(%d, %d, %d)" % (self.red, self.green, self.blue)
|
||||
|
||||
@property
|
||||
def rgba(self):
|
||||
return "rgba(%d, %d, %d, %s)" % (self.red, self.green, self.blue, self.alpha)
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
return "#%02x%02x%02x" % (self.red, self.green, self.blue)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Color):
|
||||
return self.rgba == other.rgba
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
result = self.__eq__(other)
|
||||
if result is NotImplemented:
|
||||
return result
|
||||
return not result
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.red, self.green, self.blue, self.alpha))
|
||||
|
||||
def __repr__(self):
|
||||
return "Color(red=%d, green=%d, blue=%d, alpha=%s)" % (self.red, self.green, self.blue, self.alpha)
|
||||
|
||||
def __str__(self):
|
||||
return "Color: %s" % self.rgba
|
||||
|
||||
|
||||
# Basic, extended and transparent colour keywords as defined by the W3C HTML4 spec
|
||||
# See http://www.w3.org/TR/css3-color/#html4
|
||||
Colors = {
|
||||
"TRANSPARENT": Color(0, 0, 0, 0),
|
||||
"ALICEBLUE": Color(240, 248, 255),
|
||||
"ANTIQUEWHITE": Color(250, 235, 215),
|
||||
"AQUA": Color(0, 255, 255),
|
||||
"AQUAMARINE": Color(127, 255, 212),
|
||||
"AZURE": Color(240, 255, 255),
|
||||
"BEIGE": Color(245, 245, 220),
|
||||
"BISQUE": Color(255, 228, 196),
|
||||
"BLACK": Color(0, 0, 0),
|
||||
"BLANCHEDALMOND": Color(255, 235, 205),
|
||||
"BLUE": Color(0, 0, 255),
|
||||
"BLUEVIOLET": Color(138, 43, 226),
|
||||
"BROWN": Color(165, 42, 42),
|
||||
"BURLYWOOD": Color(222, 184, 135),
|
||||
"CADETBLUE": Color(95, 158, 160),
|
||||
"CHARTREUSE": Color(127, 255, 0),
|
||||
"CHOCOLATE": Color(210, 105, 30),
|
||||
"CORAL": Color(255, 127, 80),
|
||||
"CORNFLOWERBLUE": Color(100, 149, 237),
|
||||
"CORNSILK": Color(255, 248, 220),
|
||||
"CRIMSON": Color(220, 20, 60),
|
||||
"CYAN": Color(0, 255, 255),
|
||||
"DARKBLUE": Color(0, 0, 139),
|
||||
"DARKCYAN": Color(0, 139, 139),
|
||||
"DARKGOLDENROD": Color(184, 134, 11),
|
||||
"DARKGRAY": Color(169, 169, 169),
|
||||
"DARKGREEN": Color(0, 100, 0),
|
||||
"DARKGREY": Color(169, 169, 169),
|
||||
"DARKKHAKI": Color(189, 183, 107),
|
||||
"DARKMAGENTA": Color(139, 0, 139),
|
||||
"DARKOLIVEGREEN": Color(85, 107, 47),
|
||||
"DARKORANGE": Color(255, 140, 0),
|
||||
"DARKORCHID": Color(153, 50, 204),
|
||||
"DARKRED": Color(139, 0, 0),
|
||||
"DARKSALMON": Color(233, 150, 122),
|
||||
"DARKSEAGREEN": Color(143, 188, 143),
|
||||
"DARKSLATEBLUE": Color(72, 61, 139),
|
||||
"DARKSLATEGRAY": Color(47, 79, 79),
|
||||
"DARKSLATEGREY": Color(47, 79, 79),
|
||||
"DARKTURQUOISE": Color(0, 206, 209),
|
||||
"DARKVIOLET": Color(148, 0, 211),
|
||||
"DEEPPINK": Color(255, 20, 147),
|
||||
"DEEPSKYBLUE": Color(0, 191, 255),
|
||||
"DIMGRAY": Color(105, 105, 105),
|
||||
"DIMGREY": Color(105, 105, 105),
|
||||
"DODGERBLUE": Color(30, 144, 255),
|
||||
"FIREBRICK": Color(178, 34, 34),
|
||||
"FLORALWHITE": Color(255, 250, 240),
|
||||
"FORESTGREEN": Color(34, 139, 34),
|
||||
"FUCHSIA": Color(255, 0, 255),
|
||||
"GAINSBORO": Color(220, 220, 220),
|
||||
"GHOSTWHITE": Color(248, 248, 255),
|
||||
"GOLD": Color(255, 215, 0),
|
||||
"GOLDENROD": Color(218, 165, 32),
|
||||
"GRAY": Color(128, 128, 128),
|
||||
"GREY": Color(128, 128, 128),
|
||||
"GREEN": Color(0, 128, 0),
|
||||
"GREENYELLOW": Color(173, 255, 47),
|
||||
"HONEYDEW": Color(240, 255, 240),
|
||||
"HOTPINK": Color(255, 105, 180),
|
||||
"INDIANRED": Color(205, 92, 92),
|
||||
"INDIGO": Color(75, 0, 130),
|
||||
"IVORY": Color(255, 255, 240),
|
||||
"KHAKI": Color(240, 230, 140),
|
||||
"LAVENDER": Color(230, 230, 250),
|
||||
"LAVENDERBLUSH": Color(255, 240, 245),
|
||||
"LAWNGREEN": Color(124, 252, 0),
|
||||
"LEMONCHIFFON": Color(255, 250, 205),
|
||||
"LIGHTBLUE": Color(173, 216, 230),
|
||||
"LIGHTCORAL": Color(240, 128, 128),
|
||||
"LIGHTCYAN": Color(224, 255, 255),
|
||||
"LIGHTGOLDENRODYELLOW": Color(250, 250, 210),
|
||||
"LIGHTGRAY": Color(211, 211, 211),
|
||||
"LIGHTGREEN": Color(144, 238, 144),
|
||||
"LIGHTGREY": Color(211, 211, 211),
|
||||
"LIGHTPINK": Color(255, 182, 193),
|
||||
"LIGHTSALMON": Color(255, 160, 122),
|
||||
"LIGHTSEAGREEN": Color(32, 178, 170),
|
||||
"LIGHTSKYBLUE": Color(135, 206, 250),
|
||||
"LIGHTSLATEGRAY": Color(119, 136, 153),
|
||||
"LIGHTSLATEGREY": Color(119, 136, 153),
|
||||
"LIGHTSTEELBLUE": Color(176, 196, 222),
|
||||
"LIGHTYELLOW": Color(255, 255, 224),
|
||||
"LIME": Color(0, 255, 0),
|
||||
"LIMEGREEN": Color(50, 205, 50),
|
||||
"LINEN": Color(250, 240, 230),
|
||||
"MAGENTA": Color(255, 0, 255),
|
||||
"MAROON": Color(128, 0, 0),
|
||||
"MEDIUMAQUAMARINE": Color(102, 205, 170),
|
||||
"MEDIUMBLUE": Color(0, 0, 205),
|
||||
"MEDIUMORCHID": Color(186, 85, 211),
|
||||
"MEDIUMPURPLE": Color(147, 112, 219),
|
||||
"MEDIUMSEAGREEN": Color(60, 179, 113),
|
||||
"MEDIUMSLATEBLUE": Color(123, 104, 238),
|
||||
"MEDIUMSPRINGGREEN": Color(0, 250, 154),
|
||||
"MEDIUMTURQUOISE": Color(72, 209, 204),
|
||||
"MEDIUMVIOLETRED": Color(199, 21, 133),
|
||||
"MIDNIGHTBLUE": Color(25, 25, 112),
|
||||
"MINTCREAM": Color(245, 255, 250),
|
||||
"MISTYROSE": Color(255, 228, 225),
|
||||
"MOCCASIN": Color(255, 228, 181),
|
||||
"NAVAJOWHITE": Color(255, 222, 173),
|
||||
"NAVY": Color(0, 0, 128),
|
||||
"OLDLACE": Color(253, 245, 230),
|
||||
"OLIVE": Color(128, 128, 0),
|
||||
"OLIVEDRAB": Color(107, 142, 35),
|
||||
"ORANGE": Color(255, 165, 0),
|
||||
"ORANGERED": Color(255, 69, 0),
|
||||
"ORCHID": Color(218, 112, 214),
|
||||
"PALEGOLDENROD": Color(238, 232, 170),
|
||||
"PALEGREEN": Color(152, 251, 152),
|
||||
"PALETURQUOISE": Color(175, 238, 238),
|
||||
"PALEVIOLETRED": Color(219, 112, 147),
|
||||
"PAPAYAWHIP": Color(255, 239, 213),
|
||||
"PEACHPUFF": Color(255, 218, 185),
|
||||
"PERU": Color(205, 133, 63),
|
||||
"PINK": Color(255, 192, 203),
|
||||
"PLUM": Color(221, 160, 221),
|
||||
"POWDERBLUE": Color(176, 224, 230),
|
||||
"PURPLE": Color(128, 0, 128),
|
||||
"REBECCAPURPLE": Color(128, 51, 153),
|
||||
"RED": Color(255, 0, 0),
|
||||
"ROSYBROWN": Color(188, 143, 143),
|
||||
"ROYALBLUE": Color(65, 105, 225),
|
||||
"SADDLEBROWN": Color(139, 69, 19),
|
||||
"SALMON": Color(250, 128, 114),
|
||||
"SANDYBROWN": Color(244, 164, 96),
|
||||
"SEAGREEN": Color(46, 139, 87),
|
||||
"SEASHELL": Color(255, 245, 238),
|
||||
"SIENNA": Color(160, 82, 45),
|
||||
"SILVER": Color(192, 192, 192),
|
||||
"SKYBLUE": Color(135, 206, 235),
|
||||
"SLATEBLUE": Color(106, 90, 205),
|
||||
"SLATEGRAY": Color(112, 128, 144),
|
||||
"SLATEGREY": Color(112, 128, 144),
|
||||
"SNOW": Color(255, 250, 250),
|
||||
"SPRINGGREEN": Color(0, 255, 127),
|
||||
"STEELBLUE": Color(70, 130, 180),
|
||||
"TAN": Color(210, 180, 140),
|
||||
"TEAL": Color(0, 128, 128),
|
||||
"THISTLE": Color(216, 191, 216),
|
||||
"TOMATO": Color(255, 99, 71),
|
||||
"TURQUOISE": Color(64, 224, 208),
|
||||
"VIOLET": Color(238, 130, 238),
|
||||
"WHEAT": Color(245, 222, 179),
|
||||
"WHITE": Color(255, 255, 255),
|
||||
"WHITESMOKE": Color(245, 245, 245),
|
||||
"YELLOW": Color(255, 255, 0),
|
||||
"YELLOWGREEN": Color(154, 205, 50)
|
||||
}
|
322
youtube_dl/selenium/webdriver/support/event_firing_webdriver.py
Normal file
322
youtube_dl/selenium/webdriver/support/event_firing_webdriver.py
Normal file
@ -0,0 +1,322 @@
|
||||
# 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 WebDriverException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from .abstract_event_listener import AbstractEventListener
|
||||
|
||||
|
||||
def _wrap_elements(result, ef_driver):
|
||||
if isinstance(result, WebElement):
|
||||
return EventFiringWebElement(result, ef_driver)
|
||||
elif isinstance(result, list):
|
||||
return [_wrap_elements(item, ef_driver) for item in result]
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
class EventFiringWebDriver(object):
|
||||
"""
|
||||
A wrapper around an arbitrary WebDriver instance which supports firing events
|
||||
"""
|
||||
|
||||
def __init__(self, driver, event_listener):
|
||||
"""
|
||||
Creates a new instance of the EventFiringWebDriver
|
||||
|
||||
:Args:
|
||||
- driver : A WebDriver instance
|
||||
- event_listener : Instance of a class that subclasses AbstractEventListener and implements it fully or partially
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener
|
||||
|
||||
class MyListener(AbstractEventListener):
|
||||
def before_navigate_to(self, url, driver):
|
||||
print("Before navigate to %s" % url)
|
||||
def after_navigate_to(self, url, driver):
|
||||
print("After navigate to %s" % url)
|
||||
|
||||
driver = Firefox()
|
||||
ef_driver = EventFiringWebDriver(driver, MyListener())
|
||||
ef_driver.get("http://www.google.co.in/")
|
||||
"""
|
||||
if not isinstance(driver, WebDriver):
|
||||
raise WebDriverException("A WebDriver instance must be supplied")
|
||||
if not isinstance(event_listener, AbstractEventListener):
|
||||
raise WebDriverException("Event listener must be a subclass of AbstractEventListener")
|
||||
self._driver = driver
|
||||
self._driver._wrap_value = self._wrap_value
|
||||
self._listener = event_listener
|
||||
|
||||
@property
|
||||
def wrapped_driver(self):
|
||||
"""Returns the WebDriver instance wrapped by this EventsFiringWebDriver"""
|
||||
return self._driver
|
||||
|
||||
def get(self, url):
|
||||
self._dispatch("navigate_to", (url, self._driver), "get", (url, ))
|
||||
|
||||
def back(self):
|
||||
self._dispatch("navigate_back", (self._driver,), "back", ())
|
||||
|
||||
def forward(self):
|
||||
self._dispatch("navigate_forward", (self._driver,), "forward", ())
|
||||
|
||||
def execute_script(self, script, *args):
|
||||
unwrapped_args = (script,) + self._unwrap_element_args(args)
|
||||
return self._dispatch("execute_script", (script, self._driver), "execute_script", unwrapped_args)
|
||||
|
||||
def execute_async_script(self, script, *args):
|
||||
unwrapped_args = (script,) + self._unwrap_element_args(args)
|
||||
return self._dispatch("execute_script", (script, self._driver), "execute_async_script", unwrapped_args)
|
||||
|
||||
def close(self):
|
||||
self._dispatch("close", (self._driver,), "close", ())
|
||||
|
||||
def quit(self):
|
||||
self._dispatch("quit", (self._driver,), "quit", ())
|
||||
|
||||
def find_element(self, by=By.ID, value=None):
|
||||
return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))
|
||||
|
||||
def find_elements(self, by=By.ID, value=None):
|
||||
return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))
|
||||
|
||||
def find_element_by_id(self, id_):
|
||||
return self.find_element(by=By.ID, value=id_)
|
||||
|
||||
def find_elements_by_id(self, id_):
|
||||
return self.find_elements(by=By.ID, value=id_)
|
||||
|
||||
def find_element_by_xpath(self, xpath):
|
||||
return self.find_element(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_elements_by_xpath(self, xpath):
|
||||
return self.find_elements(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_element_by_link_text(self, link_text):
|
||||
return self.find_element(by=By.LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_link_text(self, text):
|
||||
return self.find_elements(by=By.LINK_TEXT, value=text)
|
||||
|
||||
def find_element_by_partial_link_text(self, link_text):
|
||||
return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_partial_link_text(self, link_text):
|
||||
return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_element_by_name(self, name):
|
||||
return self.find_element(by=By.NAME, value=name)
|
||||
|
||||
def find_elements_by_name(self, name):
|
||||
return self.find_elements(by=By.NAME, value=name)
|
||||
|
||||
def find_element_by_tag_name(self, name):
|
||||
return self.find_element(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_elements_by_tag_name(self, name):
|
||||
return self.find_elements(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_element_by_class_name(self, name):
|
||||
return self.find_element(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_elements_by_class_name(self, name):
|
||||
return self.find_elements(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_element_by_css_selector(self, css_selector):
|
||||
return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def find_elements_by_css_selector(self, css_selector):
|
||||
return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def _dispatch(self, l_call, l_args, d_call, d_args):
|
||||
getattr(self._listener, "before_%s" % l_call)(*l_args)
|
||||
try:
|
||||
result = getattr(self._driver, d_call)(*d_args)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise e
|
||||
getattr(self._listener, "after_%s" % l_call)(*l_args)
|
||||
return _wrap_elements(result, self)
|
||||
|
||||
def _unwrap_element_args(self, args):
|
||||
if isinstance(args, EventFiringWebElement):
|
||||
return args.wrapped_element
|
||||
elif isinstance(args, tuple):
|
||||
return tuple([self._unwrap_element_args(item) for item in args])
|
||||
elif isinstance(args, list):
|
||||
return [self._unwrap_element_args(item) for item in args]
|
||||
else:
|
||||
return args
|
||||
|
||||
def _wrap_value(self, value):
|
||||
if isinstance(value, EventFiringWebElement):
|
||||
return WebDriver._wrap_value(self._driver, value.wrapped_element)
|
||||
return WebDriver._wrap_value(self._driver, value)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
if item.startswith("_") or not hasattr(self._driver, item):
|
||||
object.__setattr__(self, item, value)
|
||||
else:
|
||||
try:
|
||||
object.__setattr__(self._driver, item, value)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise e
|
||||
|
||||
def __getattr__(self, name):
|
||||
def _wrap(*args, **kwargs):
|
||||
try:
|
||||
result = attrib(*args, **kwargs)
|
||||
return _wrap_elements(result, self)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise
|
||||
|
||||
try:
|
||||
attrib = getattr(self._driver, name)
|
||||
return _wrap if callable(attrib) else attrib
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise
|
||||
|
||||
|
||||
class EventFiringWebElement(object):
|
||||
""""
|
||||
A wrapper around WebElement instance which supports firing events
|
||||
"""
|
||||
|
||||
def __init__(self, webelement, ef_driver):
|
||||
"""
|
||||
Creates a new instance of the EventFiringWebElement
|
||||
"""
|
||||
self._webelement = webelement
|
||||
self._ef_driver = ef_driver
|
||||
self._driver = ef_driver.wrapped_driver
|
||||
self._listener = ef_driver._listener
|
||||
|
||||
@property
|
||||
def wrapped_element(self):
|
||||
"""Returns the WebElement wrapped by this EventFiringWebElement instance"""
|
||||
return self._webelement
|
||||
|
||||
def click(self):
|
||||
self._dispatch("click", (self._webelement, self._driver), "click", ())
|
||||
|
||||
def clear(self):
|
||||
self._dispatch("change_value_of", (self._webelement, self._driver), "clear", ())
|
||||
|
||||
def send_keys(self, *value):
|
||||
self._dispatch("change_value_of", (self._webelement, self._driver), "send_keys", value)
|
||||
|
||||
def find_element(self, by=By.ID, value=None):
|
||||
return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))
|
||||
|
||||
def find_elements(self, by=By.ID, value=None):
|
||||
return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))
|
||||
|
||||
def find_element_by_id(self, id_):
|
||||
return self.find_element(by=By.ID, value=id_)
|
||||
|
||||
def find_elements_by_id(self, id_):
|
||||
return self.find_elements(by=By.ID, value=id_)
|
||||
|
||||
def find_element_by_name(self, name):
|
||||
return self.find_element(by=By.NAME, value=name)
|
||||
|
||||
def find_elements_by_name(self, name):
|
||||
return self.find_elements(by=By.NAME, value=name)
|
||||
|
||||
def find_element_by_link_text(self, link_text):
|
||||
return self.find_element(by=By.LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_link_text(self, link_text):
|
||||
return self.find_elements(by=By.LINK_TEXT, value=link_text)
|
||||
|
||||
def find_element_by_partial_link_text(self, link_text):
|
||||
return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_elements_by_partial_link_text(self, link_text):
|
||||
return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
|
||||
|
||||
def find_element_by_tag_name(self, name):
|
||||
return self.find_element(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_elements_by_tag_name(self, name):
|
||||
return self.find_elements(by=By.TAG_NAME, value=name)
|
||||
|
||||
def find_element_by_xpath(self, xpath):
|
||||
return self.find_element(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_elements_by_xpath(self, xpath):
|
||||
return self.find_elements(by=By.XPATH, value=xpath)
|
||||
|
||||
def find_element_by_class_name(self, name):
|
||||
return self.find_element(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_elements_by_class_name(self, name):
|
||||
return self.find_elements(by=By.CLASS_NAME, value=name)
|
||||
|
||||
def find_element_by_css_selector(self, css_selector):
|
||||
return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def find_elements_by_css_selector(self, css_selector):
|
||||
return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
|
||||
|
||||
def _dispatch(self, l_call, l_args, d_call, d_args):
|
||||
getattr(self._listener, "before_%s" % l_call)(*l_args)
|
||||
try:
|
||||
result = getattr(self._webelement, d_call)(*d_args)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise e
|
||||
getattr(self._listener, "after_%s" % l_call)(*l_args)
|
||||
return _wrap_elements(result, self._ef_driver)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
if item.startswith("_") or not hasattr(self._webelement, item):
|
||||
object.__setattr__(self, item, value)
|
||||
else:
|
||||
try:
|
||||
object.__setattr__(self._webelement, item, value)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise e
|
||||
|
||||
def __getattr__(self, name):
|
||||
def _wrap(*args, **kwargs):
|
||||
try:
|
||||
result = attrib(*args, **kwargs)
|
||||
return _wrap_elements(result, self._ef_driver)
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise
|
||||
|
||||
try:
|
||||
attrib = getattr(self._webelement, name)
|
||||
return _wrap if callable(attrib) else attrib
|
||||
except Exception as e:
|
||||
self._listener.on_exception(e, self._driver)
|
||||
raise
|
19
youtube_dl/selenium/webdriver/support/events.py
Normal file
19
youtube_dl/selenium/webdriver/support/events.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.
|
||||
|
||||
from .abstract_event_listener import AbstractEventListener # noqa
|
||||
from .event_firing_webdriver import EventFiringWebDriver # noqa
|
408
youtube_dl/selenium/webdriver/support/expected_conditions.py
Normal file
408
youtube_dl/selenium/webdriver/support/expected_conditions.py
Normal file
@ -0,0 +1,408 @@
|
||||
# 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 NoSuchElementException
|
||||
from selenium.common.exceptions import NoSuchFrameException
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.common.exceptions import NoAlertPresentException
|
||||
|
||||
"""
|
||||
* Canned "Expected Conditions" which are generally useful within webdriver
|
||||
* tests.
|
||||
"""
|
||||
|
||||
|
||||
class title_is(object):
|
||||
"""An expectation for checking the title of a page.
|
||||
title is the expected title, which must be an exact match
|
||||
returns True if the title matches, false otherwise."""
|
||||
def __init__(self, title):
|
||||
self.title = title
|
||||
|
||||
def __call__(self, driver):
|
||||
return self.title == driver.title
|
||||
|
||||
|
||||
class title_contains(object):
|
||||
""" An expectation for checking that the title contains a case-sensitive
|
||||
substring. title is the fragment of title expected
|
||||
returns True when the title matches, False otherwise
|
||||
"""
|
||||
def __init__(self, title):
|
||||
self.title = title
|
||||
|
||||
def __call__(self, driver):
|
||||
return self.title in driver.title
|
||||
|
||||
|
||||
class presence_of_element_located(object):
|
||||
""" An expectation for checking that an element is present on the DOM
|
||||
of a page. This does not necessarily mean that the element is visible.
|
||||
locator - used to find the element
|
||||
returns the WebElement once it is located
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
return _find_element(driver, self.locator)
|
||||
|
||||
|
||||
class url_contains(object):
|
||||
""" An expectation for checking that the current url contains a
|
||||
case-sensitive substring.
|
||||
url is the fragment of url expected,
|
||||
returns True when the title matches, False otherwise
|
||||
"""
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
|
||||
def __call__(self, driver):
|
||||
return self.url in driver.current_url
|
||||
|
||||
|
||||
class url_matches(object):
|
||||
"""An expectation for checking the current url.
|
||||
pattern is the expected pattern, which must be an exact match
|
||||
returns True if the title matches, false otherwise."""
|
||||
def __init__(self, pattern):
|
||||
self.pattern = pattern
|
||||
|
||||
def __call__(self, driver):
|
||||
import re
|
||||
match = re.search(self.pattern, driver.current_url)
|
||||
|
||||
return match is not None
|
||||
|
||||
|
||||
class url_to_be(object):
|
||||
"""An expectation for checking the current url.
|
||||
url is the expected url, which must be an exact match
|
||||
returns True if the title matches, false otherwise."""
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
|
||||
def __call__(self, driver):
|
||||
return self.url == driver.current_url
|
||||
|
||||
|
||||
class url_changes(object):
|
||||
"""An expectation for checking the current url.
|
||||
url is the expected url, which must not be an exact match
|
||||
returns True if the url is different, false otherwise."""
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
|
||||
def __call__(self, driver):
|
||||
return self.url != driver.current_url
|
||||
|
||||
|
||||
class visibility_of_element_located(object):
|
||||
""" An expectation for checking that an element is present on the DOM of a
|
||||
page and visible. Visibility means that the element is not only displayed
|
||||
but also has a height and width that is greater than 0.
|
||||
locator - used to find the element
|
||||
returns the WebElement once it is located and visible
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
return _element_if_visible(_find_element(driver, self.locator))
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
|
||||
class visibility_of(object):
|
||||
""" An expectation for checking that an element, known to be present on the
|
||||
DOM of a page, is visible. Visibility means that the element is not only
|
||||
displayed but also has a height and width that is greater than 0.
|
||||
element is the WebElement
|
||||
returns the (same) WebElement once it is visible
|
||||
"""
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def __call__(self, ignored):
|
||||
return _element_if_visible(self.element)
|
||||
|
||||
|
||||
def _element_if_visible(element, visibility=True):
|
||||
return element if element.is_displayed() == visibility else False
|
||||
|
||||
|
||||
class presence_of_all_elements_located(object):
|
||||
""" An expectation for checking that there is at least one element present
|
||||
on a web page.
|
||||
locator is used to find the element
|
||||
returns the list of WebElements once they are located
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
return _find_elements(driver, self.locator)
|
||||
|
||||
|
||||
class visibility_of_any_elements_located(object):
|
||||
""" An expectation for checking that there is at least one element visible
|
||||
on a web page.
|
||||
locator is used to find the element
|
||||
returns the list of WebElements once they are located
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
return [element for element in _find_elements(driver, self.locator) if _element_if_visible(element)]
|
||||
|
||||
|
||||
class visibility_of_all_elements_located(object):
|
||||
""" An expectation for checking that all elements are present on the DOM of a
|
||||
page and visible. Visibility means that the elements are not only displayed
|
||||
but also has a height and width that is greater than 0.
|
||||
locator - used to find the elements
|
||||
returns the list of WebElements once they are located and visible
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
elements = _find_elements(driver, self.locator)
|
||||
for element in elements:
|
||||
if _element_if_visible(element, visibility=False):
|
||||
return False
|
||||
return elements
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
|
||||
class text_to_be_present_in_element(object):
|
||||
""" An expectation for checking if the given text is present in the
|
||||
specified element.
|
||||
locator, text
|
||||
"""
|
||||
def __init__(self, locator, text_):
|
||||
self.locator = locator
|
||||
self.text = text_
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
element_text = _find_element(driver, self.locator).text
|
||||
return self.text in element_text
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
|
||||
class text_to_be_present_in_element_value(object):
|
||||
"""
|
||||
An expectation for checking if the given text is present in the element's
|
||||
locator, text
|
||||
"""
|
||||
def __init__(self, locator, text_):
|
||||
self.locator = locator
|
||||
self.text = text_
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
element_text = _find_element(driver,
|
||||
self.locator).get_attribute("value")
|
||||
if element_text:
|
||||
return self.text in element_text
|
||||
else:
|
||||
return False
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
|
||||
class frame_to_be_available_and_switch_to_it(object):
|
||||
""" An expectation for checking whether the given frame is available to
|
||||
switch to. If the frame is available it switches the given driver to the
|
||||
specified frame.
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.frame_locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
if isinstance(self.frame_locator, tuple):
|
||||
driver.switch_to.frame(_find_element(driver,
|
||||
self.frame_locator))
|
||||
else:
|
||||
driver.switch_to.frame(self.frame_locator)
|
||||
return True
|
||||
except NoSuchFrameException:
|
||||
return False
|
||||
|
||||
|
||||
class invisibility_of_element_located(object):
|
||||
""" An Expectation for checking that an element is either invisible or not
|
||||
present on the DOM.
|
||||
|
||||
locator used to find the element
|
||||
"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
return _element_if_visible(_find_element(driver, self.locator), False)
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
# In the case of NoSuchElement, returns true because the element is
|
||||
# not present in DOM. The try block checks if the element is present
|
||||
# but is invisible.
|
||||
# In the case of StaleElementReference, returns true because stale
|
||||
# element reference implies that element is no longer visible.
|
||||
return True
|
||||
|
||||
|
||||
class element_to_be_clickable(object):
|
||||
""" An Expectation for checking an element is visible and enabled such that
|
||||
you can click it."""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
element = visibility_of_element_located(self.locator)(driver)
|
||||
if element and element.is_enabled():
|
||||
return element
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class staleness_of(object):
|
||||
""" Wait until an element is no longer attached to the DOM.
|
||||
element is the element to wait for.
|
||||
returns False if the element is still attached to the DOM, true otherwise.
|
||||
"""
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def __call__(self, ignored):
|
||||
try:
|
||||
# Calling any method forces a staleness check
|
||||
self.element.is_enabled()
|
||||
return False
|
||||
except StaleElementReferenceException:
|
||||
return True
|
||||
|
||||
|
||||
class element_to_be_selected(object):
|
||||
""" An expectation for checking the selection is selected.
|
||||
element is WebElement object
|
||||
"""
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def __call__(self, ignored):
|
||||
return self.element.is_selected()
|
||||
|
||||
|
||||
class element_located_to_be_selected(object):
|
||||
"""An expectation for the element to be located is selected.
|
||||
locator is a tuple of (by, path)"""
|
||||
def __init__(self, locator):
|
||||
self.locator = locator
|
||||
|
||||
def __call__(self, driver):
|
||||
return _find_element(driver, self.locator).is_selected()
|
||||
|
||||
|
||||
class element_selection_state_to_be(object):
|
||||
""" An expectation for checking if the given element is selected.
|
||||
element is WebElement object
|
||||
is_selected is a Boolean."
|
||||
"""
|
||||
def __init__(self, element, is_selected):
|
||||
self.element = element
|
||||
self.is_selected = is_selected
|
||||
|
||||
def __call__(self, ignored):
|
||||
return self.element.is_selected() == self.is_selected
|
||||
|
||||
|
||||
class element_located_selection_state_to_be(object):
|
||||
""" An expectation to locate an element and check if the selection state
|
||||
specified is in that state.
|
||||
locator is a tuple of (by, path)
|
||||
is_selected is a boolean
|
||||
"""
|
||||
def __init__(self, locator, is_selected):
|
||||
self.locator = locator
|
||||
self.is_selected = is_selected
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
element = _find_element(driver, self.locator)
|
||||
return element.is_selected() == self.is_selected
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
|
||||
class number_of_windows_to_be(object):
|
||||
""" An expectation for the number of windows to be a certain value."""
|
||||
|
||||
def __init__(self, num_windows):
|
||||
self.num_windows = num_windows
|
||||
|
||||
def __call__(self, driver):
|
||||
return len(driver.window_handles) == self.num_windows
|
||||
|
||||
|
||||
class new_window_is_opened(object):
|
||||
""" An expectation that a new window will be opened and have the number of
|
||||
windows handles increase"""
|
||||
|
||||
def __init__(self, current_handles):
|
||||
self.current_handles = current_handles
|
||||
|
||||
def __call__(self, driver):
|
||||
return len(driver.window_handles) > len(self.current_handles)
|
||||
|
||||
|
||||
class alert_is_present(object):
|
||||
""" Expect an alert to be present."""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, driver):
|
||||
try:
|
||||
alert = driver.switch_to.alert
|
||||
return alert
|
||||
except NoAlertPresentException:
|
||||
return False
|
||||
|
||||
|
||||
def _find_element(driver, by):
|
||||
"""Looks up an element. Logs and re-raises ``WebDriverException``
|
||||
if thrown."""
|
||||
try:
|
||||
return driver.find_element(*by)
|
||||
except NoSuchElementException as e:
|
||||
raise e
|
||||
except WebDriverException as e:
|
||||
raise e
|
||||
|
||||
|
||||
def _find_elements(driver, by):
|
||||
try:
|
||||
return driver.find_elements(*by)
|
||||
except WebDriverException as e:
|
||||
raise e
|
241
youtube_dl/selenium/webdriver/support/select.py
Normal file
241
youtube_dl/selenium/webdriver/support/select.py
Normal file
@ -0,0 +1,241 @@
|
||||
# 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.by import By
|
||||
from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException
|
||||
|
||||
|
||||
class Select(object):
|
||||
|
||||
def __init__(self, webelement):
|
||||
"""
|
||||
Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not,
|
||||
then an UnexpectedTagNameException is thrown.
|
||||
|
||||
:Args:
|
||||
- webelement - element SELECT element to wrap
|
||||
|
||||
Example:
|
||||
from selenium.webdriver.support.ui import Select \n
|
||||
Select(driver.find_element_by_tag_name("select")).select_by_index(2)
|
||||
"""
|
||||
if webelement.tag_name.lower() != "select":
|
||||
raise UnexpectedTagNameException(
|
||||
"Select only works on <select> elements, not on <%s>" %
|
||||
webelement.tag_name)
|
||||
self._el = webelement
|
||||
multi = self._el.get_attribute("multiple")
|
||||
self.is_multiple = multi and multi != "false"
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Returns a list of all options belonging to this select tag"""
|
||||
return self._el.find_elements(By.TAG_NAME, 'option')
|
||||
|
||||
@property
|
||||
def all_selected_options(self):
|
||||
"""Returns a list of all selected options belonging to this select tag"""
|
||||
ret = []
|
||||
for opt in self.options:
|
||||
if opt.is_selected():
|
||||
ret.append(opt)
|
||||
return ret
|
||||
|
||||
@property
|
||||
def first_selected_option(self):
|
||||
"""The first selected option in this select tag (or the currently selected option in a
|
||||
normal select)"""
|
||||
for opt in self.options:
|
||||
if opt.is_selected():
|
||||
return opt
|
||||
raise NoSuchElementException("No options are selected")
|
||||
|
||||
def select_by_value(self, value):
|
||||
"""Select all options that have a value matching the argument. That is, when given "foo" this
|
||||
would select an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- value - The value to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specisied value in SELECT
|
||||
"""
|
||||
css = "option[value =%s]" % self._escapeString(value)
|
||||
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||||
matched = False
|
||||
for opt in opts:
|
||||
self._setSelected(opt)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException("Cannot locate option with value: %s" % value)
|
||||
|
||||
def select_by_index(self, index):
|
||||
"""Select the option at the given index. This is done by examing the "index" attribute of an
|
||||
element, and not merely by counting.
|
||||
|
||||
:Args:
|
||||
- index - The option at this index will be selected
|
||||
|
||||
throws NoSuchElementException If there is no option with specisied index in SELECT
|
||||
"""
|
||||
match = str(index)
|
||||
for opt in self.options:
|
||||
if opt.get_attribute("index") == match:
|
||||
self._setSelected(opt)
|
||||
return
|
||||
raise NoSuchElementException("Could not locate element with index %d" % index)
|
||||
|
||||
def select_by_visible_text(self, text):
|
||||
"""Select all options that display text matching the argument. That is, when given "Bar" this
|
||||
would select an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- text - The visible text to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specisied text in SELECT
|
||||
"""
|
||||
xpath = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
|
||||
opts = self._el.find_elements(By.XPATH, xpath)
|
||||
matched = False
|
||||
for opt in opts:
|
||||
self._setSelected(opt)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
|
||||
if len(opts) == 0 and " " in text:
|
||||
subStringWithoutSpace = self._get_longest_token(text)
|
||||
if subStringWithoutSpace == "":
|
||||
candidates = self.options
|
||||
else:
|
||||
xpath = ".//option[contains(.,%s)]" % self._escapeString(subStringWithoutSpace)
|
||||
candidates = self._el.find_elements(By.XPATH, xpath)
|
||||
for candidate in candidates:
|
||||
if text == candidate.text:
|
||||
self._setSelected(candidate)
|
||||
if not self.is_multiple:
|
||||
return
|
||||
matched = True
|
||||
|
||||
if not matched:
|
||||
raise NoSuchElementException("Could not locate element with visible text: %s" % text)
|
||||
|
||||
def deselect_all(self):
|
||||
"""Clear all selected entries. This is only valid when the SELECT supports multiple selections.
|
||||
throws NotImplementedError If the SELECT does not support multiple selections
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect all options of a multi-select")
|
||||
for opt in self.options:
|
||||
self._unsetSelected(opt)
|
||||
|
||||
def deselect_by_value(self, value):
|
||||
"""Deselect all options that have a value matching the argument. That is, when given "foo" this
|
||||
would deselect an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- value - The value to match against
|
||||
|
||||
throws NoSuchElementException If there is no option with specisied value in SELECT
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
matched = False
|
||||
css = "option[value = %s]" % self._escapeString(value)
|
||||
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||||
for opt in opts:
|
||||
self._unsetSelected(opt)
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException("Could not locate element with value: %s" % value)
|
||||
|
||||
def deselect_by_index(self, index):
|
||||
"""Deselect the option at the given index. This is done by examing the "index" attribute of an
|
||||
element, and not merely by counting.
|
||||
|
||||
:Args:
|
||||
- index - The option at this index will be deselected
|
||||
|
||||
throws NoSuchElementException If there is no option with specisied index in SELECT
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
for opt in self.options:
|
||||
if opt.get_attribute("index") == str(index):
|
||||
self._unsetSelected(opt)
|
||||
return
|
||||
raise NoSuchElementException("Could not locate element with index %d" % index)
|
||||
|
||||
def deselect_by_visible_text(self, text):
|
||||
"""Deselect all options that display text matching the argument. That is, when given "Bar" this
|
||||
would deselect an option like:
|
||||
|
||||
<option value="foo">Bar</option>
|
||||
|
||||
:Args:
|
||||
- text - The visible text to match against
|
||||
"""
|
||||
if not self.is_multiple:
|
||||
raise NotImplementedError("You may only deselect options of a multi-select")
|
||||
matched = False
|
||||
xpath = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
|
||||
opts = self._el.find_elements(By.XPATH, xpath)
|
||||
for opt in opts:
|
||||
self._unsetSelected(opt)
|
||||
matched = True
|
||||
if not matched:
|
||||
raise NoSuchElementException("Could not locate element with visible text: %s" % text)
|
||||
|
||||
def _setSelected(self, option):
|
||||
if not option.is_selected():
|
||||
option.click()
|
||||
|
||||
def _unsetSelected(self, option):
|
||||
if option.is_selected():
|
||||
option.click()
|
||||
|
||||
def _escapeString(self, value):
|
||||
if '"' in value and "'" in value:
|
||||
substrings = value.split("\"")
|
||||
result = ["concat("]
|
||||
for substring in substrings:
|
||||
result.append("\"%s\"" % substring)
|
||||
result.append(", '\"', ")
|
||||
result = result[0:-1]
|
||||
if value.endswith('"'):
|
||||
result.append(", '\"'")
|
||||
return "".join(result) + ")"
|
||||
|
||||
if '"' in value:
|
||||
return "'%s'" % value
|
||||
|
||||
return "\"%s\"" % value
|
||||
|
||||
def _get_longest_token(self, value):
|
||||
items = value.split(" ")
|
||||
longest = ""
|
||||
for item in items:
|
||||
if len(item) > len(longest):
|
||||
longest = item
|
||||
return longest
|
19
youtube_dl/selenium/webdriver/support/ui.py
Normal file
19
youtube_dl/selenium/webdriver/support/ui.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.
|
||||
|
||||
from .select import Select # noqa
|
||||
from .wait import WebDriverWait # noqa
|
96
youtube_dl/selenium/webdriver/support/wait.py
Normal file
96
youtube_dl/selenium/webdriver/support/wait.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.
|
||||
|
||||
import time
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
POLL_FREQUENCY = 0.5 # How long to sleep inbetween calls to the method
|
||||
IGNORED_EXCEPTIONS = (NoSuchElementException,) # exceptions ignored during calls to the method
|
||||
|
||||
|
||||
class WebDriverWait(object):
|
||||
def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None):
|
||||
"""Constructor, takes a WebDriver instance and timeout in seconds.
|
||||
|
||||
:Args:
|
||||
- driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
|
||||
- timeout - Number of seconds before timing out
|
||||
- poll_frequency - sleep interval between calls
|
||||
By default, it is 0.5 second.
|
||||
- ignored_exceptions - iterable structure of exception classes ignored during calls.
|
||||
By default, it contains NoSuchElementException only.
|
||||
|
||||
Example:
|
||||
from selenium.webdriver.support.ui import WebDriverWait \n
|
||||
element = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_id("someId")) \n
|
||||
is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\ \n
|
||||
until_not(lambda x: x.find_element_by_id("someId").is_displayed())
|
||||
"""
|
||||
self._driver = driver
|
||||
self._timeout = timeout
|
||||
self._poll = poll_frequency
|
||||
# avoid the divide by zero
|
||||
if self._poll == 0:
|
||||
self._poll = POLL_FREQUENCY
|
||||
exceptions = list(IGNORED_EXCEPTIONS)
|
||||
if ignored_exceptions is not None:
|
||||
try:
|
||||
exceptions.extend(iter(ignored_exceptions))
|
||||
except TypeError: # ignored_exceptions is not iterable
|
||||
exceptions.append(ignored_exceptions)
|
||||
self._ignored_exceptions = tuple(exceptions)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0.__module__}.{0.__name__} (session="{1}")>'.format(
|
||||
type(self), self._driver.session_id)
|
||||
|
||||
def until(self, method, message=''):
|
||||
"""Calls the method provided with the driver as an argument until the \
|
||||
return value is not False."""
|
||||
screen = None
|
||||
stacktrace = None
|
||||
|
||||
end_time = time.time() + self._timeout
|
||||
while True:
|
||||
try:
|
||||
value = method(self._driver)
|
||||
if value:
|
||||
return value
|
||||
except self._ignored_exceptions as exc:
|
||||
screen = getattr(exc, 'screen', None)
|
||||
stacktrace = getattr(exc, 'stacktrace', None)
|
||||
time.sleep(self._poll)
|
||||
if time.time() > end_time:
|
||||
break
|
||||
raise TimeoutException(message, screen, stacktrace)
|
||||
|
||||
def until_not(self, method, message=''):
|
||||
"""Calls the method provided with the driver as an argument until the \
|
||||
return value is False."""
|
||||
end_time = time.time() + self._timeout
|
||||
while True:
|
||||
try:
|
||||
value = method(self._driver)
|
||||
if not value:
|
||||
return value
|
||||
except self._ignored_exceptions:
|
||||
return True
|
||||
time.sleep(self._poll)
|
||||
if time.time() > end_time:
|
||||
break
|
||||
raise TimeoutException(message)
|
16
youtube_dl/selenium/webdriver/webkitgtk/__init__.py
Normal file
16
youtube_dl/selenium/webdriver/webkitgtk/__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.
|
98
youtube_dl/selenium/webdriver/webkitgtk/options.py
Normal file
98
youtube_dl/selenium/webdriver/webkitgtk/options.py
Normal file
@ -0,0 +1,98 @@
|
||||
# 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
|
||||
|
||||
|
||||
class Options(object):
|
||||
KEY = 'webkitgtk:browserOptions'
|
||||
|
||||
def __init__(self):
|
||||
self._binary_location = ''
|
||||
self._arguments = []
|
||||
self._overlay_scrollbars_enabled = True
|
||||
|
||||
@property
|
||||
def binary_location(self):
|
||||
"""
|
||||
Returns the location of the browser binary otherwise an empty string
|
||||
"""
|
||||
return self._binary_location
|
||||
|
||||
@binary_location.setter
|
||||
def binary_location(self, value):
|
||||
"""
|
||||
Allows you to set the browser binary to launch
|
||||
|
||||
:Args:
|
||||
- value : path to the browser binary
|
||||
"""
|
||||
self._binary_location = value
|
||||
|
||||
@property
|
||||
def arguments(self):
|
||||
"""
|
||||
Returns a list of arguments needed for the browser
|
||||
"""
|
||||
return self._arguments
|
||||
|
||||
def add_argument(self, argument):
|
||||
"""
|
||||
Adds an argument to the list
|
||||
|
||||
:Args:
|
||||
- Sets the arguments
|
||||
"""
|
||||
if argument:
|
||||
self._arguments.append(argument)
|
||||
else:
|
||||
raise ValueError("argument can not be null")
|
||||
|
||||
@property
|
||||
def overlay_scrollbars_enabled(self):
|
||||
"""
|
||||
Returns whether overlay scrollbars should be enabled
|
||||
"""
|
||||
return self._overlay_scrollbars_enabled
|
||||
|
||||
@overlay_scrollbars_enabled.setter
|
||||
def overlay_scrollbars_enabled(self, value):
|
||||
"""
|
||||
Allows you to enable or disable overlay scrollbars
|
||||
|
||||
:Args:
|
||||
- value : True or False
|
||||
"""
|
||||
self._overlay_scrollbars_enabled = value
|
||||
|
||||
def to_capabilities(self):
|
||||
"""
|
||||
Creates a capabilities with all the options that have been set and
|
||||
returns a dictionary with everything
|
||||
"""
|
||||
webkitgtk = DesiredCapabilities.WEBKITGTK.copy()
|
||||
|
||||
browser_options = {}
|
||||
if self.binary_location:
|
||||
browser_options["binary"] = self.binary_location
|
||||
if self.arguments:
|
||||
browser_options["args"] = self.arguments
|
||||
browser_options["useOverlayScrollbars"] = self.overlay_scrollbars_enabled
|
||||
|
||||
webkitgtk[Options.KEY] = browser_options
|
||||
|
||||
return webkitgtk
|
42
youtube_dl/selenium/webdriver/webkitgtk/service.py
Normal file
42
youtube_dl/selenium/webdriver/webkitgtk/service.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.common import service
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
"""
|
||||
Object that manages the starting and stopping of the WebKitGTKDriver
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path, port=0, log_path=None):
|
||||
"""
|
||||
Creates a new instance of the Service
|
||||
|
||||
:Args:
|
||||
- executable_path : Path to the WebKitGTKDriver
|
||||
- port : Port the service is running on
|
||||
- log_path : Path for the WebKitGTKDriver service to log to
|
||||
"""
|
||||
log_file = open(log_path, "wb") if log_path is not None and log_path != "" else None
|
||||
service.Service.__init__(self, executable_path, port, log_file)
|
||||
|
||||
def command_line_args(self):
|
||||
return ["-p", "%d" % self.port]
|
||||
|
||||
def send_remote_shutdown_command(self):
|
||||
pass
|
72
youtube_dl/selenium/webdriver/webkitgtk/webdriver.py
Normal file
72
youtube_dl/selenium/webdriver/webkitgtk/webdriver.py
Normal file
@ -0,0 +1,72 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
try:
|
||||
import http.client as http_client
|
||||
except ImportError:
|
||||
import httplib as http_client
|
||||
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from .service import Service
|
||||
|
||||
|
||||
class WebDriver(RemoteWebDriver):
|
||||
"""
|
||||
Controls the WebKitGTKDriver and allows you to drive the browser.
|
||||
"""
|
||||
|
||||
def __init__(self, executable_path="WebKitWebDriver", port=0, options=None,
|
||||
desired_capabilities=DesiredCapabilities.WEBKITGTK,
|
||||
service_log_path=None):
|
||||
"""
|
||||
Creates a new instance of the WebKitGTK driver.
|
||||
|
||||
Starts the service and then creates new instance of WebKitGTK Driver.
|
||||
|
||||
:Args:
|
||||
- executable_path : path to the executable. If the default is used it assumes the executable is in the $PATH.
|
||||
- port : port you would like the service to run, if left as 0, a free port will be found.
|
||||
- options : an instance of WebKitGTKOptions
|
||||
- desired_capabilities : Dictionary object with desired capabilities
|
||||
- service_log_path : Path to write service stdout and stderr output.
|
||||
"""
|
||||
if options is not None:
|
||||
capabilities = options.to_capabilities()
|
||||
capabilities.update(desired_capabilities)
|
||||
desired_capabilities = capabilities
|
||||
|
||||
self.service = Service(executable_path, port=port, log_path=service_log_path)
|
||||
self.service.start()
|
||||
|
||||
RemoteWebDriver.__init__(
|
||||
self,
|
||||
command_executor=self.service.service_url,
|
||||
desired_capabilities=desired_capabilities)
|
||||
self._is_remote = False
|
||||
|
||||
def quit(self):
|
||||
"""
|
||||
Closes the browser and shuts down the WebKitGTKDriver executable
|
||||
that is started when starting the WebKitGTKDriver
|
||||
"""
|
||||
try:
|
||||
RemoteWebDriver.quit(self)
|
||||
except http_client.BadStatusLine:
|
||||
pass
|
||||
finally:
|
||||
self.service.stop()
|
Loading…
Reference in New Issue
Block a user