Skip to content

Commit

Permalink
Initial stab at faster interactions - unit tests pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Milan Falešník committed May 24, 2017
1 parent d0a8a8c commit 7743689
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 26 deletions.
79 changes: 55 additions & 24 deletions src/widgetastic/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
NoSuchElementException, UnexpectedAlertPresentException, MoveTargetOutOfBoundsException,
StaleElementReferenceException, NoAlertPresentException, LocatorNotImplemented)
from .log import create_widget_logger, null_logger
from .utils import repeat_once_on_exceptions
from .xpath import normalize_space


Expand Down Expand Up @@ -243,6 +244,7 @@ def elements(

return result

@repeat_once_on_exceptions(NoSuchElementException, check_safe=True)
def element(self, locator, *args, **kwargs):
"""Returns one :py:class:`selenium.webdriver.remote.webelement.WebElement`
Expand All @@ -254,6 +256,8 @@ def element(self, locator, *args, **kwargs):
Raises:
:py:class:`selenium.common.exceptions.NoSuchElementException`
"""
if 'check_safe' not in kwargs:
kwargs['check_safe'] = False
try:
vcheck = self._locator_force_visibility_check(locator)
if vcheck is not None:
Expand All @@ -268,7 +272,7 @@ def element(self, locator, *args, **kwargs):
else:
return elements[0]
except IndexError:
raise NoSuchElementException('Could not find an element {}'.format(repr(locator)))
raise NoSuchElementException('Could not find an element {!r}'.format(locator))

def perform_click(self):
"""Clicks the left mouse button at the current mouse position."""
Expand All @@ -279,8 +283,11 @@ def click(self, locator, *args, **kwargs):
Args: See :py:meth:`elements`
"""
self.logger.debug('click: %r', locator)
ignore_ajax = kwargs.pop('ignore_ajax', False)
if ignore_ajax:
self.logger.debug('click(%r, ignore_ajax=True)', locator)
else:
self.logger.debug('click(%r)', locator)
el = self.move_to_element(locator, *args, **kwargs)
self.plugin.before_click(el)
# and then click on current mouse position
Expand All @@ -300,8 +307,11 @@ def raw_click(self, locator, *args, **kwargs):
Args: See :py:meth:`elements`
"""
self.logger.debug('raw_click: %r', locator)
ignore_ajax = kwargs.pop('ignore_ajax', False)
if ignore_ajax:
self.logger.debug('raw_click(%r, ignore_ajax=True)', locator)
else:
self.logger.debug('raw_click(%r)', locator)
el = self.element(locator, *args, **kwargs)
self.plugin.before_click(el)
el.click()
Expand Down Expand Up @@ -343,6 +353,9 @@ def is_displayed(self, locator, *args, **kwargs):
# Just in case
return False

@repeat_once_on_exceptions(
StaleElementReferenceException, MoveTargetOutOfBoundsException,
check_safe=True)
def move_to_element(self, locator, *args, **kwargs):
"""Moves the mouse cursor to the middle of the element represented by the locator.
Expand All @@ -354,11 +367,13 @@ def move_to_element(self, locator, *args, **kwargs):
Returns:
:py:class:`selenium.webdriver.remote.webelement.WebElement`
"""
self.logger.debug('move_to_element: %r', locator)
if 'check_safe' not in kwargs:
kwargs['check_safe'] = False
self.logger.debug('move_to_element(%r)', locator)
el = self.element(locator, *args, **kwargs)
if el.tag_name == "option":
# Instead of option, let's move on its parent <select> if possible
parent = self.element("..", parent=el)
parent = self.element("..", parent=el, check_safe=False)
if parent.tag_name == "select":
self.move_to_element(parent)
return el
Expand All @@ -367,7 +382,7 @@ def move_to_element(self, locator, *args, **kwargs):
move_to.perform()
except MoveTargetOutOfBoundsException:
# ff workaround
self.execute_script("arguments[0].scrollIntoView();", el)
self.execute_script("arguments[0].scrollIntoView();", el, silent=True)
try:
move_to.perform()
except MoveTargetOutOfBoundsException: # This has become desperate now.
Expand All @@ -377,13 +392,15 @@ def move_to_element(self, locator, *args, **kwargs):
return el

def move_by_offset(self, x, y):
self.logger.debug('move_by_offset X:%r Y:%r', x, y)
ActionChains(self.selenium).move_by_offset(x, y)
"""Moves mouse pointer by given values."""
self.logger.debug('move_by_offset(%r, %r)', x, y)
ActionChains(self.selenium).move_by_offset(x, y).perform()
self.plugin.ensure_page_safe()

def execute_script(self, script, *args, **kwargs):
"""Executes a script."""
if not kwargs.pop('silent', False):
self.logger.debug('execute_script: %r', script)
self.logger.debug('execute_script(%r)', script)
return self.selenium.execute_script(dedent(script), *args, **kwargs)

def classes(self, locator, *args, **kwargs):
Expand All @@ -396,7 +413,7 @@ def classes(self, locator, *args, **kwargs):
"""
result = set(self.execute_script(
"return arguments[0].classList;", self.element(locator, *args, **kwargs), silent=True))
self.logger.debug('css classes for %r => %r', locator, result)
self.logger.debug('classes(%r) => %r', locator, result)
return result

def tag(self, *args, **kwargs):
Expand All @@ -409,7 +426,7 @@ def tag(self, *args, **kwargs):
"""
return self.element(*args, **kwargs).tag_name

def text(self, *args, **kwargs):
def text(self, locator, *args, **kwargs):
"""Returns the text inside the element represented by the locator passed.
The returned text is normalized with :py:func:`widgetastic.xpath.normalize_space` as defined
Expand All @@ -421,44 +438,55 @@ def text(self, *args, **kwargs):
:py:class:`str` with the text
"""
try:
text = self.element(*args, **kwargs).text
text = self.element(locator, *args, **kwargs).text
except MoveTargetOutOfBoundsException:
text = ''

if not text:
# It is probably invisible
text = self.execute_script(
'return arguments[0].textContent || arguments[0].innerText;',
self.element(*args, **kwargs))
self.element(locator, *args, **kwargs),
silent=True)
if text is None:
text = ''

return normalize_space(text)
result = normalize_space(text)
self.logger.debug('text(%r) => %r', locator, result)
return result

def get_attribute(self, attr, *args, **kwargs):
return self.element(*args, **kwargs).get_attribute(attr)
def get_attribute(self, attr, locator, *args, **kwargs):
"""Get attribute value from an element."""
result = self.element(locator, *args, **kwargs).get_attribute(attr)
self.logger.debug('get_attribute(%r, %r) => %r', attr, locator, result)
return result

def set_attribute(self, attr, value, *args, **kwargs):
def set_attribute(self, attr, value, locator, *args, **kwargs):
self.logger.debug('set_attribute(%r, %r, %r)', attr, value, locator)
return self.execute_script(
"arguments[0].setAttribute(arguments[1], arguments[2]);",
self.element(*args, **kwargs), attr, value)
self.element(locator, *args, **kwargs), attr, value)

def size_of(self, *args, **kwargs):
def size_of(self, locator, *args, **kwargs):
"""Returns element's size as a tuple of width/height."""
size = self.element(*args, **kwargs).size
size = self.element(locator, *args, **kwargs).size
self.logger.debug('size_of(%r) => w:%r h:%r', locator, size['width'], size['height'])
return Size(size['width'], size['height'])

def clear(self, locator, *args, **kwargs):
"""Clears a text input with given locator."""
self.logger.debug('clear: %r', locator)
self.logger.debug('clear(%r)', locator)
el = self.element(locator, *args, **kwargs)
self.plugin.before_keyboard_input(el, None)
result = el.clear()
self.plugin.after_keyboard_input(el, None)
self.plugin.ensure_page_safe()
return result

def is_selected(self, *args, **kwargs):
return self.element(*args, **kwargs).is_selected()
def is_selected(self, locator, *args, **kwargs):
result = self.element(locator, *args, **kwargs).is_selected()
self.logger.debug('is_selected(%r) => %r', locator, result)
return result

def send_keys(self, text, locator, *args, **kwargs):
"""Sends keys to the element. Detects the file inputs automatically.
Expand All @@ -481,7 +509,7 @@ def send_keys(self, text, locator, *args, **kwargs):
self.selenium.file_detector = LocalFileDetector()
el = self.move_to_element(locator, *args, **kwargs)
self.plugin.before_keyboard_input(el, text)
self.logger.debug('send_keys %r to %r', text, locator)
self.logger.debug('send_keys(%r, %r)', text, locator)
result = el.send_keys(text)
if Keys.ENTER not in text:
try:
Expand All @@ -499,6 +527,9 @@ def send_keys(self, text, locator, *args, **kwargs):
if file_intercept:
self.selenium.file_detector = UselessFileDetector()

# And ensure the page input was safe
self.plugin.ensure_page_safe()

def get_alert(self):
"""Returns the current alert object.
Expand Down
19 changes: 19 additions & 0 deletions src/widgetastic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re
import string
from cached_property import cached_property
from functools import wraps
from smartloc import Locator
from threading import Lock

Expand Down Expand Up @@ -441,3 +442,21 @@ def normalize_space(text):
string.*
"""
return _replace_spaces_with(text.strip(), ' ')


def repeat_once_on_exceptions(*exception_classes, **set_key_to_what):
if not set_key_to_what:
raise TypeError('set_key_to_what is not set!')

def g(f):
@wraps(f)
def wrapped(*args, **kwargs):
try:
return f(*args, **kwargs)
except exception_classes:
kwargs.update(set_key_to_what)
return f(*args, **kwargs)

return wrapped

return g
2 changes: 0 additions & 2 deletions testing/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,6 @@ def all(cls, browser):

assert len(view.table_row) == 3

assert False


def test_parametrized_view_read_without_all(browser):
class MyView(View):
Expand Down

0 comments on commit 7743689

Please sign in to comment.