Simplify retry logic for ui helper functions

This commit is contained in:
Jay Zoldak
2013-09-04 09:28:30 -04:00
parent 50f190c55d
commit 2ff056df8c
17 changed files with 245 additions and 163 deletions

View File

@@ -75,7 +75,8 @@ def make_desired_capabilities():
desired_capabilities['build'] = settings.SAUCE.get('BUILD')
desired_capabilities['video-upload-on-pass'] = False
desired_capabilities['sauce-advisor'] = False
desired_capabilities['record-screenshots'] = False
desired_capabilities['capture-html'] = True
desired_capabilities['record-screenshots'] = True
desired_capabilities['selenium-version'] = "2.34.0"
desired_capabilities['max-duration'] = 3600
desired_capabilities['public'] = 'public restricted'
@@ -87,6 +88,7 @@ def initial_setup(server):
"""
Launch the browser once before executing the tests.
"""
# from nose.tools import set_trace; set_trace()
world.absorb(settings.SAUCE.get('SAUCE_ENABLED'), 'SAUCE_ENABLED')
if not world.SAUCE_ENABLED:

View File

@@ -5,7 +5,7 @@ from lettuce import world
import time
import platform
from urllib import quote_plus
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
@@ -18,11 +18,6 @@ def wait(seconds):
time.sleep(float(seconds))
@world.absorb
def wait_for(func):
WebDriverWait(world.browser.driver, 5).until(func)
@world.absorb
def visit(url):
world.browser.visit(django_url(url))
@@ -34,7 +29,7 @@ def url_equals(url):
@world.absorb
def is_css_present(css_selector, wait_time=5):
def is_css_present(css_selector, wait_time=10):
return world.browser.is_element_present_by_css(css_selector, wait_time=wait_time)
@@ -44,92 +39,116 @@ def is_css_not_present(css_selector, wait_time=5):
@world.absorb
def css_has_text(css_selector, text, index=0, max_attempts=5):
return world.css_text(css_selector, index=index, max_attempts=max_attempts) == text
def css_has_text(css_selector, text, index=0):
return world.css_text(css_selector, index=index) == text
@world.absorb
def css_find(css, wait_time=5):
def is_visible(_driver):
return EC.visibility_of_element_located((By.CSS_SELECTOR, css,))
def wait_for(func, timeout=5):
WebDriverWait(world.browser.driver, timeout).until(func)
world.browser.is_element_present_by_css(css, wait_time=wait_time)
wait_for(is_visible)
def wait_for_present(css_selector, timeout=30):
"""
Waiting for the element to be present in the DOM.
Throws an error if the wait_for time expires.
Otherwise this method will return None
"""
WebDriverWait(driver=world.browser.driver,
timeout=60).until(EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,)))
@world.absorb
def wait_for_visible(css_selector, timeout=30):
"""
Waiting for the element to be visible in the DOM.
Throws an error if the wait_for time expires.
Otherwise this method will return None
"""
WebDriverWait(driver=world.browser.driver,
timeout=timeout).until(EC.visibility_of_element_located((By.CSS_SELECTOR, css_selector,)))
@world.absorb
def wait_for_invisible(css_selector, timeout=30):
"""
Waiting for the element to be either invisible or not present on the DOM.
Throws an error if the wait_for time expires.
Otherwise this method will return None
"""
WebDriverWait(driver=world.browser.driver,
timeout=timeout).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,)))
@world.absorb
def wait_for_clickable(css_selector, timeout=30):
"""
Waiting for the element to be present and clickable.
Throws an error if the wait_for time expires.
Otherwise this method will return None.
"""
# Sometimes the element is clickable then gets obscured.
# In this case, pause so that it is not reported clickable too early
WebDriverWait(world.browser.driver,
timeout=timeout).until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector,)))
@world.absorb
def css_find(css, wait_time=30):
"""
Wait for the element(s) as defined by css locator
to be present.
This method will return a WebDriverElement.
"""
wait_for_present(css_selector=css, timeout=wait_time)
return world.browser.find_by_css(css)
@world.absorb
def css_click(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
def css_click(css_selector, index=0, wait_time=30):
"""
Perform a click on a CSS selector, retrying if it initially fails.
Perform a click on a CSS selector, first waiting for the element
to be present and clickable.
This function handles errors that may be thrown if the component cannot be clicked on.
However, there are cases where an error may not be thrown, and yet the operation did not
actually succeed. For those cases, a success_condition lambda can be supplied to verify that the click worked.
This function will return True if the click worked (taking into account both errors and the optional
success_condition).
This method will return True if the click worked.
"""
assert is_css_present(css_selector), "{} is not present".format(css_selector)
for _ in range(max_attempts):
try:
world.css_find(css_selector)[index].click()
if success_condition():
return
except WebDriverException:
# Occasionally, MathJax or other JavaScript can cover up
# an element temporarily.
# If this happens, wait a second, then try again
world.wait(1)
except:
pass
else:
# try once more, letting exceptions raise
world.css_find(css_selector)[index].click()
if not success_condition():
raise Exception("unsuccessful click")
wait_for_clickable(css_selector, timeout=wait_time)
assert world.css_find(css_selector)[index].visible
# Sometimes you can't click in the center of the element, as
# another element might be on top of it. In this case, try
# clicking in the upper left corner.
try:
return world.css_find(css_selector)[index].click()
except WebDriverException:
return css_click_at(css_selector, index=index)
@world.absorb
def css_check(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
def css_check(css_selector, index=0, wait_time=30):
"""
Checks a check box based on a CSS selector, retrying if it initially fails.
Checks a check box based on a CSS selector, first waiting for the element
to be present and clickable. This is just a wrapper for calling "click"
because that's how selenium interacts with check boxes and radio buttons.
This function handles errors that may be thrown if the component cannot be clicked on.
However, there are cases where an error may not be thrown, and yet the operation did not
actually succeed. For those cases, a success_condition lambda can be supplied to verify that the check worked.
This function will return True if the check worked (taking into account both errors and the optional
success_condition).
This method will return True if the check worked.
"""
assert is_css_present(css_selector), "{} is not present".format(css_selector)
for _ in range(max_attempts):
try:
world.css_find(css_selector)[index].check()
if success_condition():
return
except WebDriverException:
# Occasionally, MathJax or other JavaScript can cover up
# an element temporarily.
# If this happens, wait a second, then try again
world.wait(1)
except:
pass
else:
# try once more, letting exceptions raise
world.css_find(css_selector)[index].check()
if not success_condition():
raise Exception("unsuccessful check")
return css_click(css_selector=css_selector, index=index, wait_time=wait_time)
@world.absorb
def css_click_at(css, x_cord=10, y_cord=10):
def css_click_at(css_selector, index=0, x_coord=10, y_coord=10, timeout=5):
'''
A method to click at x,y coordinates of the element
rather than in the center of the element
'''
element = css_find(css).first
element.action_chains.move_to_element_with_offset(element._element, x_cord, y_cord)
wait_for_clickable(css_selector, timeout=timeout)
element = css_find(css_selector)[index]
assert element.visible
element.action_chains.move_to_element_with_offset(element._element, x_coord, y_coord)
element.action_chains.click()
element.action_chains.perform()
@@ -139,58 +158,55 @@ def id_click(elem_id):
"""
Perform a click on an element as specified by its id
"""
world.css_click('#%s' % elem_id)
css_click('#%s' % elem_id)
@world.absorb
def css_fill(css_selector, text, index=0, max_attempts=5):
assert is_css_present(css_selector)
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].fill(text), max_attempts=max_attempts)
def css_fill(css_selector, text, index=0):
css_find(css_selector)[index].fill(text)
@world.absorb
def click_link(partial_text, index=0, max_attempts=5):
return world.retry_on_exception(lambda: world.browser.find_link_by_partial_text(partial_text)[index].click(), max_attempts=max_attempts)
def click_link(partial_text, index=0):
world.browser.find_link_by_partial_text(partial_text)[index].click()
@world.absorb
def css_text(css_selector, index=0, max_attempts=5):
def css_text(css_selector, index=0, timeout=30):
# Wait for the css selector to appear
if world.is_css_present(css_selector):
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].text, max_attempts=max_attempts)
if is_css_present(css_selector):
return css_find(css_selector, wait_time=timeout)[index].text
else:
return ""
@world.absorb
def css_value(css_selector, index=0, max_attempts=5):
def css_value(css_selector, index=0):
# Wait for the css selector to appear
if world.is_css_present(css_selector):
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].value, max_attempts=max_attempts)
if is_css_present(css_selector):
return css_find(css_selector)[index].value
else:
return ""
@world.absorb
def css_html(css_selector, index=0, max_attempts=5):
def css_html(css_selector, index=0):
"""
Returns the HTML of a css_selector and will retry if there is a StaleElementReferenceException
Returns the HTML of a css_selector
"""
assert is_css_present(css_selector)
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].html, max_attempts=max_attempts)
return css_find(css_selector)[index].html
@world.absorb
def css_has_class(css_selector, class_name, index=0, max_attempts=5):
return world.retry_on_exception(lambda: world.css_find(css_selector)[index].has_class(class_name), max_attempts=max_attempts)
def css_has_class(css_selector, class_name, index=0):
return css_find(css_selector)[index].has_class(class_name)
@world.absorb
def css_visible(css_selector, index=0, max_attempts=5):
def css_visible(css_selector, index=0):
assert is_css_present(css_selector)
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].visible, max_attempts=max_attempts)
return css_find(css_selector)[index].visible
@world.absorb
@@ -235,14 +251,17 @@ def click_tools():
def is_mac():
return platform.mac_ver()[0] is not ''
@world.absorb
def is_firefox():
return world.browser.driver_name is 'Firefox'
@world.absorb
def trigger_event(css_selector, event='change', index=0):
world.browser.execute_script("$('{}:eq({})').trigger('{}')".format(css_selector, index, event))
@world.absorb
def retry_on_exception(func, max_attempts=5):
attempt = 0