# pylint: disable=wildcard-import,unused-wildcard-import,function-redefined,too-many-ancestors
import copy
import functools
import logging
import time
from urllib.parse import urlparse, urlunparse, urlencode
import selenium.common.exceptions as selenium_exc
from selenium.webdriver import *
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.firefox.webelement import FirefoxWebElement
from selenium.webdriver.support.ui import Select, WebDriverWait
from .download import DownloadUrl, DownloadFile
from .errors import WebdriverWrapperErrorMixin
from .exceptions import _create_exception_msg
from .info import WebdriverWrapperInfoMixin
logging.basicConfig(level=logging.INFO)
__all__ = (
'Firefox',
'FirefoxProfile',
'Chrome',
'ChromeOptions',
'Ie',
'Opera',
'PhantomJS',
'Remote',
'DesiredCapabilities',
'ActionChains',
'TouchActions',
'Proxy',
)
class _ConvertToWebelementWrapper:
def __call__(self, f):
@functools.wraps(f)
def wrapper(driver_self, *args, **kwds):
res = f(driver_self, *args, **kwds)
res = self._convert_result(driver_self._driver, res)
return res
return wrapper
@classmethod
def _convert_result(cls, driver, res):
if type(res) is driver._web_element_cls:
res = cls._convert_into_webelementwrapper(res)
elif isinstance(res, (list, tuple)):
for index, item in enumerate(res):
res[index] = cls._convert_result(driver, item)
return res
@classmethod
def _convert_into_webelementwrapper(cls, webelement):
try:
if webelement.tag_name == 'form':
from webdriverwrapper.forms import Form
wrapped = Form(webelement)
elif webelement.tag_name == 'select':
wrapped = cls._make_instance(_SelectWrapper, webelement)
else:
wrapped = cls._make_instance(_WebElementWrapper, webelement)
except selenium_exc.StaleElementReferenceException:
return webelement
else:
return wrapped
@classmethod
def _make_instance(cls, element_class, webelement):
"""
Firefox uses another implementation of element. This method
switch base of wrapped element to firefox one.
"""
if isinstance(webelement, FirefoxWebElement):
element_class = copy.deepcopy(element_class)
element_class.__bases__ = tuple(
FirefoxWebElement if base is WebElement else base
for base in element_class.__bases__
)
return element_class(webelement)
[docs]class _WebdriverBaseWrapper:
"""
Class wrapping both
:py:class:`selenium.WebDriver <selenium.webdriver.remote.webdriver.WebDriver>`
and :py:class:`selenium.WebElement <selenium.webdriver.remote.webelement.WebElement>`.
"""
default_wait_timeout = 10
"""
Default timeout in seconds for wait* methods (such as wait_for_element).
.. versionadded:: 2.7
"""
[docs] def contains_text(self, text):
"""
Does page or element contains `text`?
Uses method :py:meth:`~._WebdriverBaseWrapper.get_elms`.
"""
return bool(self.get_elms(text=text))
[docs] def find_element_by_text(self, text):
"""
Returns first element on page or in element containing ``text``.
.. versionadded:: 2.0
.. deprecated:: 2.8
Use :py:meth:`~._WebdriverBaseWrapper.get_elm` instead.
"""
return self.get_elm(text=text)
[docs] def find_elements_by_text(self, text):
"""
Returns all elements on page or in element containing ``text``.
.. versionchanged:: 2.0
Searching in all text nodes. Before it wouldn't find string "text"
in HTML like ``<div>some<br />text in another text node</div>``.
.. deprecated:: 2.8
Use :py:meth:`~._WebdriverBaseWrapper.get_elms` instead.
"""
return self.get_elms(text=text)
[docs] def click(self, *args, **kwds):
"""
When you not pass any argument, it clicks on current element. If you
pass some arguments, it works as following snippet. For more info what
you can pass check out method :py:meth:`~._WebdriverBaseWrapper.get_elm`.
.. code-block:: python
driver.get_elm('someid').click()
"""
if args or kwds:
elm = self.get_elm(*args, **kwds)
elm.click()
else:
super().click()
[docs] def get_elm(
self,
id_=None, class_name=None, name=None, tag_name=None, text=None, xpath=None,
parent_id=None, parent_class_name=None, parent_name=None, parent_tag_name=None,
css_selector=None
):
"""
Returns first found element. This method uses
:py:meth:`~._WebdriverBaseWrapper.get_elms`.
"""
elms = self.get_elms(
id_, class_name, name, tag_name, text, xpath,
parent_id, parent_class_name, parent_name, parent_tag_name,
css_selector
)
if not elms:
raise selenium_exc.NoSuchElementException(_create_exception_msg(
id_, class_name, name, tag_name,
parent_id, parent_class_name, parent_name, parent_tag_name,
text, xpath, css_selector, self.current_url,
driver=self._driver,
))
return elms[0]
[docs] def get_elms(
self,
id_=None, class_name=None, name=None, tag_name=None, text=None, xpath=None,
parent_id=None, parent_class_name=None, parent_name=None, parent_tag_name=None,
css_selector=None
):
"""
Shortcut for :py:meth:`find_element* <selenium.webdriver.remote.webelement.WebElement.find_element>`
methods. It's shorter and you can quickly find element in element.
.. code-block:: python
elm = driver.find_element_by_id('someid')
elm.find_elements_by_class_name('someclasss')
# vs.
elm = driver.get_elm(parent_id='someid', class_name='someclass')
.. versionchanged:: 2.8
Added ``text`` param. Use it instead of old ``find_element[s]_by_text`` methods.
Thanks to that it can be used also in ``wait_for_element*`` methods.
"""
if parent_id or parent_class_name or parent_name or parent_tag_name:
parent = self.get_elm(parent_id, parent_class_name, parent_name, parent_tag_name)
else:
parent = self
if len([x for x in (id_, class_name, tag_name, text, xpath) if x is not None]) > 1:
raise Exception('You can find element only by one param.')
if id_ is not None:
return parent.find_elements_by_id(id_)
if class_name is not None:
return parent.find_elements_by_class_name(class_name)
if name is not None:
return parent.find_elements_by_name(name)
if tag_name is not None:
return parent.find_elements_by_tag_name(tag_name)
if text is not None:
xpath = './/*/text()[contains(., "{}") and not(ancestor-or-self::*[@data-selenium-not-search])]/..'.format(text)
if xpath is not None:
return parent.find_elements_by_xpath(xpath)
if css_selector is not None:
return parent.find_elements_by_css_selector(css_selector)
raise Exception('You must specify id or name of element on which you want to click.')
def find_element(self, by=By.ID, value=None):
callback = self._get_seleniums_driver_class().find_element
return self._find_element_or_elements(callback, by, value)
def find_elements(self, by=By.ID, value=None):
callback = self._get_seleniums_driver_class().find_elements
return self._find_element_or_elements(callback, by, value)
def _get_seleniums_driver_class(self):
next_is_selenium_driver_class = False
driver_class = None
for cls in self.__class__.mro():
if next_is_selenium_driver_class:
driver_class = cls
break
if cls is _WebdriverBaseWrapper:
next_is_selenium_driver_class = True
if not driver_class:
raise Exception('WebDriver class not found')
return driver_class
# Map from selenium's By class to name of params used in this wrapper. It's
#+ used for making helpful messages.
# Commented lines are not supported.
_by_to_string_param_map = {
By.ID: 'id_',
By.XPATH: 'xpath',
#By.LINK_TEXT: 'link_text',
#By.PARTIAL_LINK_TEXT: 'partial_link_text',
By.NAME: 'name',
By.TAG_NAME: 'tag_name',
By.CLASS_NAME: 'class_name',
#By.CSS_SELECTOR: 'css_selector',
}
@_ConvertToWebelementWrapper()
def _find_element_or_elements(self, callback, by, value):
try:
return callback(self, by, value)
except (
selenium_exc.NoSuchElementException,
selenium_exc.StaleElementReferenceException,
selenium_exc.InvalidElementStateException,
selenium_exc.ElementNotVisibleException,
selenium_exc.ElementNotSelectableException,
) as exc:
if by in self._by_to_string_param_map:
msg = _create_exception_msg(**{
self._by_to_string_param_map[by]: value,
'url': self.current_url,
'driver': self._driver,
})
else:
msg = ''
raise exc.__class__(msg)
[docs] def wait_for_element(self, timeout=None, message='', *args, **kwds):
"""
Shortcut for waiting for element. If it not ends with exception, it
returns that element. Detault timeout is `~.default_wait_timeout`.
Same as following:
.. code-block:: python
selenium.webdriver.support.wait.WebDriverWait(driver, timeout).until(lambda driver: driver.get_elm(...))
.. versionchanged:: 2.5
Waits only for visible elements.
.. versionchanged:: 2.6
Returned functionality back in favor of new method
:py:meth:`~._WebdriverBaseWrapper.wait_for_element_show`.
"""
if not timeout:
timeout = self.default_wait_timeout
if not message:
message = _create_exception_msg(*args, url=self.current_url, **kwds)
self.wait(timeout).until(lambda driver: driver.get_elm(*args, **kwds), message=message)
# Also return that element for which is waiting.
elm = self.get_elm(*args, **kwds)
return elm
[docs] def wait_for_element_show(self, timeout=None, message='', *args, **kwds):
"""
Shortcut for waiting for visible element. If it not ends with exception, it
returns that element. Detault timeout is `~.default_wait_timeout`.
Same as following:
.. code-block:: python
selenium.webdriver.support.wait.WebDriverWait(driver, timeout).until(lambda driver: driver.get_elm(...))
.. versionadded:: 2.6
"""
if not timeout:
timeout = self.default_wait_timeout
if not message:
message = _create_exception_msg(*args, url=self.current_url, **kwds)
def callback(driver):
elms = driver.get_elms(*args, **kwds)
if not elms:
return False
try:
if all(not elm.is_displayed() for elm in elms):
return False
except selenium_exc.StaleElementReferenceException:
# Some element can be out, but need to check all elements.
# Let's wait for another run.
return False
return True
self.wait(timeout).until(callback, message=message)
# Also return that element for which is waiting.
elm = self.get_elm(*args, **kwds)
return elm
[docs] def wait_for_element_hide(self, timeout=None, message='', *args, **kwds):
"""
Shortcut for waiting for hiding of element. Detault timeout is `~.default_wait_timeout`.
Same as following:
.. code-block:: python
selenium.webdriver.support.wait.WebDriverWait(driver, timeout).until(lambda driver: not driver.get_elm(...))
.. versionadded:: 2.0
"""
if not timeout:
timeout = self.default_wait_timeout
if not message:
message = 'Element {} still visible.'.format(_create_exception_msg(*args, url=self.current_url, **kwds))
def callback(driver):
elms = driver.get_elms(*args, **kwds)
if not elms:
return True
try:
if all(not elm.is_displayed() for elm in elms):
return True
except selenium_exc.StaleElementReferenceException:
# Some element can be out, but need to check all elements.
# Let's wait for another run.
return False
return False
self.wait(timeout).until(callback, message=message)
[docs] def wait(self, timeout=None):
"""
Calls following snippet so you don't have to remember what import. See
:py:obj:`WebDriverWait <selenium.webdriver.support.wait.WebDriverWait>` for more
information. Detault timeout is `~.default_wait_timeout`.
.. code-block:: python
selenium.webdriver.support.wait.WebDriverWait(driver, timeout)
Example:
.. code-block:: python
driver.wait().until(lambda driver: len(driver.find_element_by_id('elm')) > 10)
If you need to wait for element, consider using
:py:meth:`~._WebdriverWrapper.wait_for_element` instead.
"""
if not timeout:
timeout = self.default_wait_timeout
return WebDriverWait(self, timeout)
[docs]class _WebdriverWrapper(WebdriverWrapperErrorMixin, WebdriverWrapperInfoMixin, _WebdriverBaseWrapper):
"""
Class wrapping :py:class:`selenium.WebDriver <selenium.webdriver.remote.webdriver.WebDriver>`.
"""
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self.screenshot_path = None
@property
def _driver(self):
"""
Returns always driver, not element. Use it when you need driver
and variable can be driver or element.
"""
return self
@property
def html(self):
"""
Returns ``innerHTML`` of whole page. On page have to be tag ``body``.
.. versionadded:: 2.2
"""
try:
body = self.get_elm(tag_name='body')
except selenium_exc.NoSuchElementException:
return None
else:
return body.get_attribute('innerHTML')
[docs] def make_screenshot(self, screenshot_name=None):
"""
Shortcut for ``get_screenshot_as_file`` but with configured path. If you
are using base :py:class:`~webdriverwrapper.unittest.testcase.WebdriverTestCase`.
or pytest, ``screenshot_path`` is passed to driver automatically.
If ``screenshot_name`` is not passed, current timestamp is used.
.. versionadded:: 2.2
"""
if not self.screenshot_path:
raise Exception('Please, configure screenshot_path first or call get_screenshot_as_file manually')
if not screenshot_name:
screenshot_name = str(time.time())
self.get_screenshot_as_file('{}/{}.png'.format(self.screenshot_path, screenshot_name))
[docs] def break_point(self):
"""
Stops testing and wait for pressing enter to continue.
Useful when you need check Chrome console for some info for example.
.. versionadded:: 2.1
"""
logging.info('Break point. Type enter to continue.')
input()
[docs] def go_to(self, path=None, query=None):
"""
You have to pass absolute URL to method
:py:meth:`~selenium.webdriver.remote.webdriver.WebDriver.get`. This
method help you to pass only relative ``path`` and/or ``query``. See
method :py:meth:`.get_url` for more information.
.. versionchanged:: 2.0
Removed parameter ``domain``. If you want to go to completely
another web app, use absolute address.
"""
self.get(self.get_url(path, query))
[docs] def get_url(self, path=None, query=None):
"""
You have to pass absolute URL to method
:py:meth:`~selenium.webdriver.remote.webdriver.WebDriver.get`. This
method help you to pass only relative ``path`` and/or ``query``. Scheme
and domains is append to URL from
:py:attr:`~selenium.webdriver.remote.webdriver.WebDriver.current_url`.
``query`` can be final string or dictionary. On dictionary it calls
:py:func:`urllib.urlencode`.
.. versionadded:: 2.0
"""
if urlparse(path).netloc:
return path
if isinstance(query, dict):
query = urlencode(query)
url_parts = urlparse(self.current_url)
new_url_parts = (
url_parts.scheme,
url_parts.netloc,
path or url_parts.path,
None, # params
query,
None, # fragment
)
url = urlunparse(new_url_parts)
return url
[docs] def switch_to_window(self, window_name=None, title=None, url=None):
"""
WebDriver implements switching to other window only by it's name. With
wrapper there is also option to switch by title of window or URL. URL
can be also relative path.
"""
if window_name:
self.switch_to.window(window_name)
return
if url:
url = self.get_url(path=url)
for window_handle in self.window_handles:
self.switch_to.window(window_handle)
if title and self.title == title:
return
if url and self.current_url == url:
return
raise selenium_exc.NoSuchWindowException('Window (title=%s, url=%s) not found.' % (title, url))
[docs] def close_window(self, window_name=None, title=None, url=None):
"""
WebDriver implements only closing current window. If you want to close
some window without having to switch to it, use this method.
"""
main_window_handle = self.current_window_handle
self.switch_to_window(window_name, title, url)
self.close()
self.switch_to_window(main_window_handle)
[docs] def close_other_windows(self):
"""
Closes all not current windows. Useful for tests - after each test you
can automatically close all windows.
"""
main_window_handle = self.current_window_handle
for window_handle in self.window_handles:
if window_handle == main_window_handle:
continue
self.switch_to_window(window_handle)
self.close()
self.switch_to_window(main_window_handle)
[docs] def close_alert(self, ignore_exception=False):
"""
JS alerts all blocking. This method closes it. If there is no alert,
method raises exception. In tests is good to call this method with
``ignore_exception`` setted to ``True`` which will ignore any exception.
"""
try:
alert = self.get_alert()
alert.accept()
except:
if not ignore_exception:
raise
[docs] def get_alert(self):
"""
Returns instance of :py:obj:`~selenium.webdriver.common.alert.Alert`.
"""
return Alert(self)
[docs] def wait_for_alert(self, timeout=None):
"""
Shortcut for waiting for alert. If it not ends with exception, it
returns that alert. Detault timeout is `~.default_wait_timeout`.
"""
if not timeout:
timeout = self.default_wait_timeout
alert = Alert(self)
# There is no better way how to check alert appearance
def alert_shown(driver):
try:
alert.text
return True
except selenium_exc.NoAlertPresentException:
return False
self.wait(timeout).until(alert_shown)
return alert
[docs] def download_url(self, url=None):
"""
With WebDriver you can't check status code or headers. For this you have
to make classic request. But web pages needs cookies and by this it gets
ugly. You can easily use this method.
When you not pass ``url``,
:py:attr:`~selenium.webdriver.remote.webdriver.WebDriver.current_url`
will be used.
Returns :py:obj:`~webdriverwrapper.download._Download` instance.
"""
return DownloadUrl(self, url)
[docs] def fill_out_and_submit(self, data, prefix='', turbo=False):
"""
Shortcut for filling out first ``<form>`` on page. See
:py:class:`~webdriverwrapper.forms.Form` for more information.
.. versionadded:: 2.0
"""
return self.get_elm(tag_name='form').fill_out_and_submit(data, prefix, turbo)
[docs] def fill_out(self, data, prefix='', turbo=False):
"""
Shortcut for filling out first ``<form>`` on page. See
:py:class:`~webdriverwrapper.forms.Form` for more information.
.. versionadded:: 2.0
"""
return self.get_elm(tag_name='form').fill_out(data, prefix, turbo)
[docs]class _WebElementWrapper(_WebdriverBaseWrapper, WebElement):
"""
Class wrapping :py:class:`selenium.WebElement <selenium.webdriver.remote.webelement.WebElement>`.
"""
def __new__(cls, webelement):
instance = super().__new__(cls)
instance.__dict__.update(webelement.__dict__)
return instance
def __init__(self, webelement):
# Nothing to do because whole __dict__ of original WebElement was
# copied during creation of instance.
pass
@property
def _driver(self):
"""
Returns always driver, not element. Use it when you need driver
and variable can be driver or element.
"""
return self._parent
@property
def current_url(self):
"""
Accessing :py:attr:`~selenium.webdriver.remote.webdriver.WebDriver.current_url`
also on elements.
"""
try:
current_url = self._driver.current_url
except Exception: # pylint: disable=broad-except
current_url = 'unknown'
finally:
return current_url
@property
def html(self):
"""
Returns ``innerHTML``.
.. versionadded:: 2.2
"""
self.get_attribute('innerHTML')
[docs] def clear(self):
# Just add some context when calling clear fails.
try:
return super().clear()
except (
selenium_exc.StaleElementReferenceException,
selenium_exc.InvalidElementStateException,
selenium_exc.ElementNotVisibleException,
selenium_exc.ElementNotSelectableException,
) as exc:
raise exc.__class__('Problem clearing element at {}.'.format(self.current_url))
[docs] def download_file(self):
"""
With WebDriver you can't check status code or headers. For this you have
to make classic request. But web pages needs cookies and data from forms
and by this it gets pretty ugly. You can easily use this method.
It can handle downloading of page/file by link or any type of form.
Returns :py:obj:`~webdriverwrapper.download._Download` instance.
"""
return DownloadFile(self)
class _SelectWrapper(_WebElementWrapper, Select):
def __init__(self, webelement):
# WebElementWrapper is created by coping __dict__ of WebElement instance
#+ in method __new__ of _WebElementWrapper. So there have to be called
#+ only init method of Select.
Select.__init__(self, webelement)
class Chrome(_WebdriverWrapper, Chrome):
pass
class Firefox(_WebdriverWrapper, Firefox):
pass
class Ie(_WebdriverWrapper, Ie):
pass
class Opera(_WebdriverWrapper, Opera):
pass
class PhantomJS(_WebdriverWrapper, PhantomJS):
pass
class Remote(_WebdriverWrapper, Remote):
pass