Skip to content
This repository has been archived by the owner on Jan 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #224 from edx/jmbowman/retry_page_load_errors
Browse files Browse the repository at this point in the history
Retry page load errors
jmbowman authored Feb 19, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents eed593a + eed686f commit 216effb
Showing 29 changed files with 222 additions and 216 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ sudo: false
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
before_install:
- wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz
- mkdir geckodriver
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v0.9.3 (02/14/19)
* PageLoadError and WrongPageError now inherit WebDriverException so
they can trigger retries instead of immediately failing tests

v0.9.2 (11/20/18) (0.9.1 skipped due to PyPI deployment issue)
* Support passing additional desired capabilities to remote WebDriver instances.

2 changes: 1 addition & 1 deletion bok_choy/a11y/a11y_audit.py
Original file line number Diff line number Diff line change
@@ -133,7 +133,7 @@ def _get_rules_js(self):
Raises: `RuntimeError` if the file isn't found.
"""
if not os.path.isfile(self.config.rules_file):
msg = 'Could not find the accessibility tools JS file: {}'.format(
msg = u'Could not find the accessibility tools JS file: {}'.format(
self.config.rules_file)
raise RuntimeError(msg)

20 changes: 10 additions & 10 deletions bok_choy/a11y/axe_core_ruleset.py
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ class AxeCoreAuditConfig(A11yAuditConfig):
def __init__(self, *args, **kwargs):
super(AxeCoreAuditConfig, self).__init__(*args, **kwargs)
self.rules, self.context = None, None
self.custom_rules = "customRules={}"
self.custom_rules = u"customRules={}"
self.rules_file = os.path.join(
os.path.split(CUR_DIR)[0],
'vendor/axe-core/axe.min.js'
@@ -243,7 +243,7 @@ def _check_rules(browser, rules_js, config):
__Caution__: You probably don't really want to call this method
directly! It will be used by `AxeCoreAudit.do_audit`.
"""
audit_run_script = dedent("""
audit_run_script = dedent(u"""
{rules_js}
{custom_rules}
axe.configure(customRules);
@@ -259,7 +259,7 @@ def _check_rules(browser, rules_js, config):
options=config.rules
)

audit_results_script = dedent("""
audit_results_script = dedent(u"""
window.console.log(window.a11yAuditResults);
return window.a11yAuditResults;
""")
@@ -350,14 +350,14 @@ def _get_message(node):

lines = []
for error_type in errors:
lines.append("Severity: {}".format(error_type.get("impact")))
lines.append("Rule ID: {}".format(error_type.get("id")))
lines.append("Help URL: {}\n".format(error_type.get('helpUrl')))
lines.append(u"Severity: {}".format(error_type.get("impact")))
lines.append(u"Rule ID: {}".format(error_type.get("id")))
lines.append(u"Help URL: {}\n".format(error_type.get('helpUrl')))

for node in error_type['nodes']:
msg = "Message: {}".format(_get_message(node))
html = "Html: {}".format(node.get('html').encode('utf-8'))
target = "Target: {}".format(node.get('target'))
msg = u"Message: {}".format(_get_message(node))
html = u"Html: {}".format(node.get('html').encode('utf-8'))
target = u"Target: {}".format(node.get('target'))
fill_opts = {
'width': 100,
'initial_indent': '\t',
@@ -382,7 +382,7 @@ def report_errors(audit, url):
"""
errors = AxeCoreAudit.get_errors(audit)
if errors["total"] > 0:
msg = "URL '{}' has {} errors:\n\n{}".format(
msg = u"URL '{}' has {} errors:\n\n{}".format(
url,
errors["total"],
AxeCoreAudit.format_errors(errors["errors"])
14 changes: 7 additions & 7 deletions bok_choy/a11y/axs_ruleset.py
Original file line number Diff line number Diff line change
@@ -90,8 +90,8 @@ def set_scope(self, include=None, exclude=None):
page.a11y_audit.config.set_scope()
"""
if include:
self.scope = "document.querySelector(\"{}\")".format(
', '.join(include)
self.scope = u"document.querySelector(\"{}\")".format(
u', '.join(include)
)
else:
self.scope = "null"
@@ -160,20 +160,20 @@ def _check_rules(browser, rules_js, config):
# run all rules.
rules = config.rules_to_run
if rules:
rules_config = "auditConfig.auditRulesToRun = {rules};".format(
rules_config = u"auditConfig.auditRulesToRun = {rules};".format(
rules=rules)
else:
rules_config = ""

ignored_rules = config.rules_to_ignore
if ignored_rules:
rules_config += (
"\nauditConfig.auditRulesToIgnore = {rules};".format(
u"\nauditConfig.auditRulesToIgnore = {rules};".format(
rules=ignored_rules
)
)

script = dedent("""
script = dedent(u"""
{rules_js}
var auditConfig = new axs.AuditConfiguration();
{rules_config}
@@ -219,9 +219,9 @@ def report_errors(audit, url):
"""
errors = AxsAudit.get_errors(audit)
if errors:
msg = "URL '{}' has {} errors:\n{}".format(
msg = u"URL '{}' has {} errors:\n{}".format(
url,
len(errors),
(', ').join(errors)
', '.join(errors)
)
raise AccessibilityError(msg)
38 changes: 19 additions & 19 deletions bok_choy/browser.py
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@ def save_source(driver, name):
with open(file_name, 'wb') as output_file:
output_file.write(source.encode('utf-8'))
except Exception: # pylint: disable=broad-except
msg = "Could not save the browser page source to {}.".format(file_name)
msg = u"Could not save the browser page source to {}.".format(file_name)
LOGGER.warning(msg)


@@ -131,8 +131,8 @@ def save_screenshot(driver, name):

else:
msg = (
"Browser does not support screenshots. "
"Could not save screenshot '{name}'"
u"Browser does not support screenshots. "
u"Could not save screenshot '{name}'"
).format(name=name)

LOGGER.warning(msg)
@@ -182,8 +182,8 @@ def save_driver_logs(driver, prefix):
output_file.write("{}{}".format(dumps(line), '\n'))
except: # pylint: disable=bare-except
msg = (
"Could not save browser log of type '{log_type}'. "
"It may be that the browser does not support it."
u"Could not save browser log of type '{log_type}'. "
u"It may be that the browser does not support it."
).format(log_type=log_type)

LOGGER.warning(msg, exc_info=True)
@@ -311,22 +311,22 @@ def _firefox_profile():
profile_dir = os.environ.get(FIREFOX_PROFILE_ENV_VAR)

if profile_dir:
LOGGER.info("Using firefox profile: %s", profile_dir)
LOGGER.info(u"Using firefox profile: %s", profile_dir)
try:
firefox_profile = webdriver.FirefoxProfile(profile_dir)
except OSError as err:
if err.errno == errno.ENOENT:
raise BrowserConfigError(
"Firefox profile directory {env_var}={profile_dir} does not exist".format(
u"Firefox profile directory {env_var}={profile_dir} does not exist".format(
env_var=FIREFOX_PROFILE_ENV_VAR, profile_dir=profile_dir))
elif err.errno == errno.EACCES:
raise BrowserConfigError(
"Firefox profile directory {env_var}={profile_dir} has incorrect permissions. It must be \
u"Firefox profile directory {env_var}={profile_dir} has incorrect permissions. It must be \
readable and executable.".format(env_var=FIREFOX_PROFILE_ENV_VAR, profile_dir=profile_dir))
else:
# Some other OSError:
raise BrowserConfigError(
"Problem with firefox profile directory {env_var}={profile_dir}: {msg}"
u"Problem with firefox profile directory {env_var}={profile_dir}: {msg}"
.format(env_var=FIREFOX_PROFILE_ENV_VAR, profile_dir=profile_dir, msg=str(err)))
else:
LOGGER.info("Using default firefox profile")
@@ -373,14 +373,14 @@ def _local_browser_class(browser_name):
"""

# Log name of local browser
LOGGER.info("Using local browser: %s [Default is firefox]", browser_name)
LOGGER.info(u"Using local browser: %s [Default is firefox]", browser_name)

# Get class of local browser based on name
browser_class = BROWSERS.get(browser_name)
headless = os.environ.get('BOKCHOY_HEADLESS', 'false').lower() == 'true'
if browser_class is None:
raise BrowserConfigError(
"Invalid browser name {name}. Options are: {options}".format(
u"Invalid browser name {name}. Options are: {options}".format(
name=browser_name, options=", ".join(list(BROWSERS.keys()))))
else:
if browser_name == 'firefox':
@@ -392,7 +392,7 @@ def _local_browser_class(browser_name):
firefox_options = FirefoxOptions()
firefox_options.log.level = 'trace'
if headless:
firefox_options.set_headless(True)
firefox_options.headless = True
browser_args = []
browser_kwargs = {
'firefox_profile': _firefox_profile(),
@@ -418,7 +418,7 @@ def _local_browser_class(browser_name):
elif browser_name == 'chrome':
chrome_options = ChromeOptions()
if headless:
chrome_options.set_headless(True)
chrome_options.headless = True

# Emulate webcam and microphone for testing purposes
chrome_options.add_argument('--use-fake-device-for-media-stream')
@@ -429,7 +429,7 @@ def _local_browser_class(browser_name):

browser_args = []
browser_kwargs = {
'chrome_options': chrome_options,
'options': chrome_options,
}
else:
browser_args, browser_kwargs = [], {}
@@ -453,14 +453,14 @@ def _remote_browser_class(env_vars, tags=None):
caps = _capabilities_dict(envs, tags)

if 'accessKey' in caps:
LOGGER.info("Using SauceLabs: %s %s %s", caps['platform'], caps['browserName'], caps['version'])
LOGGER.info(u"Using SauceLabs: %s %s %s", caps['platform'], caps['browserName'], caps['version'])
else:
LOGGER.info("Using Remote Browser: %s", caps['browserName'])
LOGGER.info(u"Using Remote Browser: %s", caps['browserName'])

# Create and return a new Browser
# We assume that the WebDriver end-point is running locally (e.g. using
# SauceConnect)
url = "http://{0}:{1}/wd/hub".format(
url = u"http://{0}:{1}/wd/hub".format(
envs['SELENIUM_HOST'], envs['SELENIUM_PORT'])

browser_args = []
@@ -532,13 +532,13 @@ def _required_envs(env_vars):
missing = [key for key, val in list(envs.items()) if val is None]
if missing:
msg = (
"These environment variables must be set: " + ", ".join(missing)
u"These environment variables must be set: " + u", ".join(missing)
)
raise BrowserConfigError(msg)

# Check that we support this browser
if envs['SELENIUM_BROWSER'] not in BROWSERS:
msg = "Unsuppported browser: {0}".format(envs['SELENIUM_BROWSER'])
msg = u"Unsuppported browser: {0}".format(envs['SELENIUM_BROWSER'])
raise BrowserConfigError(msg)

return envs
12 changes: 6 additions & 6 deletions bok_choy/javascript.py
Original file line number Diff line number Diff line change
@@ -123,14 +123,14 @@ def _wait_for_js(self):
if hasattr(self, '_js_vars') and self._js_vars:
EmptyPromise(
lambda: _are_js_vars_defined(self.browser, self._js_vars),
"JavaScript variables defined: {0}".format(", ".join(self._js_vars))
u"JavaScript variables defined: {0}".format(", ".join(self._js_vars))
).fulfill()

# Wait for RequireJS dependencies to load
if hasattr(self, '_requirejs_deps') and self._requirejs_deps:
EmptyPromise(
lambda: _are_requirejs_deps_loaded(self.browser, self._requirejs_deps),
"RequireJS dependencies loaded: {0}".format(", ".join(self._requirejs_deps)),
u"RequireJS dependencies loaded: {0}".format(", ".join(self._requirejs_deps)),
try_limit=5
).fulfill()

@@ -144,13 +144,13 @@ def _are_js_vars_defined(browser, js_vars):
"""
# This script will evaluate to True iff all of
# the required vars are defined.
script = " && ".join([
"!(typeof {0} === 'undefined')".format(var)
script = u" && ".join([
u"!(typeof {0} === 'undefined')".format(var)
for var in js_vars
])

try:
return browser.execute_script("return {}".format(script))
return browser.execute_script(u"return {}".format(script))
except WebDriverException as exc:
if "is not defined" in exc.msg or "is undefined" in exc.msg:
return False
@@ -176,7 +176,7 @@ def _are_requirejs_deps_loaded(browser, deps):
# We install a RequireJS module with the dependencies we want
# to ensure are loaded. When our module loads, we return
# control to the test suite.
script = dedent("""
script = dedent(u"""
// Retrieve the callback function used to return control to the test suite
var callback = arguments[arguments.length - 1];
24 changes: 12 additions & 12 deletions bok_choy/page_object.py
Original file line number Diff line number Diff line change
@@ -40,14 +40,14 @@
XSS_HTML = "<xss"


class WrongPageError(Exception):
class WrongPageError(WebDriverException):
"""
The page object reports that we're on the wrong page!
"""
pass


class PageLoadError(Exception):
class PageLoadError(WebDriverException):
"""
An error occurred while loading the page.
"""
@@ -318,18 +318,18 @@ def visit(self):
PageObject
"""
if self.url is None:
raise NotImplementedError("Page {} does not provide a URL to visit.".format(self))
raise NotImplementedError(u"Page {} does not provide a URL to visit.".format(self))

# Validate the URL
if not self.validate_url(self.url):
raise PageLoadError("Invalid URL: '{}'".format(self.url))
raise PageLoadError(u"Invalid URL: '{}'".format(self.url))

# Visit the URL
try:
self.browser.get(self.url)
except (WebDriverException, socket.gaierror):
LOGGER.warning(u"Unexpected page load exception:", exc_info=True)
raise PageLoadError("Could not load page '{!r}' at URL '{}'".format(
raise PageLoadError(u"Could not load page '{!r}' at URL '{}'".format(
self, self.url
))

@@ -342,7 +342,7 @@ def visit(self):
try:
return self.wait_for_page()
except BrokenPromise:
raise PageLoadError("Timed out waiting to load page '{!r}' at URL '{}'".format(
raise PageLoadError(u"Timed out waiting to load page '{!r}' at URL '{}'".format(
self, self.url
))

@@ -389,7 +389,7 @@ def _verify_page(self):
if not, raise a `WrongPageError`.
"""
if not self.is_browser_on_page():
msg = "Not on the correct page to use '{!r}' at URL '{}'".format(
msg = u"Not on the correct page to use '{!r}' at URL '{}'".format(
self, self.url
)
raise WrongPageError(msg)
@@ -413,7 +413,7 @@ def _verify_xss_exposure(self):
if all_hits_count > safe_hits_count:
potential_hits = re.findall('<[^<]+<xss', html_source)
raise XSSExposureError(
"{} XSS issue(s) found on page. Potential places are {}".format(
u"{} XSS issue(s) found on page. Potential places are {}".format(
all_hits_count - safe_hits_count, potential_hits
)
)
@@ -467,7 +467,7 @@ def _is_document_ready():
).fulfill()

result = Promise(
lambda: (self.is_browser_on_page(), self), "loaded page {!r}".format(self),
lambda: (self.is_browser_on_page(), self), u"loaded page {!r}".format(self),
timeout=timeout
).fulfill()

@@ -519,7 +519,7 @@ def handle_alert(self, confirm=True):
"""

# Before executing the `with` block, stub the confirm/alert functions
script = dedent("""
script = dedent(u"""
window.confirm = function() {{ return {0}; }};
window.alert = function() {{ return; }};
""".format("true" if confirm else "false")).strip()
@@ -698,9 +698,9 @@ def scroll_to_element(self, element_selector, timeout=60):
"""
# Ensure element exists
msg = "Element '{element}' is present".format(element=element_selector)
msg = u"Element '{element}' is present".format(element=element_selector)
self.wait_for(lambda: self.q(css=element_selector).present, msg, timeout=timeout)

# Obtain coordinates and use those for JavaScript call
loc = self.q(css=element_selector).first.results[0].location
self.browser.execute_script("window.scrollTo({x},{y})".format(x=loc['x'], y=loc['y']))
self.browser.execute_script(u"window.scrollTo({x},{y})".format(x=loc['x'], y=loc['y']))
8 changes: 4 additions & 4 deletions bok_choy/query.py
Original file line number Diff line number Diff line change
@@ -97,7 +97,7 @@ def replace(self, **kwargs):
clone.transforms = list(clone.transforms)
for key, value in kwargs.items():
if not hasattr(clone, key):
raise TypeError('replace() got an unexpected keyword argument {!r}'.format(key))
raise TypeError(u'replace() got an unexpected keyword argument {!r}'.format(key))

setattr(clone, key, value)
return clone
@@ -342,7 +342,7 @@ def __init__(self, browser, **kwargs):
query_name, query_value = list(kwargs.items())[0]

if query_name not in QUERY_TYPES:
raise TypeError('{} is not a supported query type for BrowserQuery()'.format(query_name))
raise TypeError(u'{} is not a supported query type for BrowserQuery()'.format(query_name))

def query_fn(): # pylint: disable=missing-docstring
return getattr(browser, QUERY_TYPES[query_name])(query_value)
@@ -372,7 +372,7 @@ def attrs(self, attribute_name):
Returns:
A list of attribute values for `attribute_name`.
"""
desc = 'attrs({!r})'.format(attribute_name)
desc = u'attrs({!r})'.format(attribute_name)
return self.map(lambda el: el.get_attribute(attribute_name), desc).results

@property
@@ -504,4 +504,4 @@ def _fill(elem): # pylint: disable=missing-docstring
elem.clear()
elem.send_keys(text)

self.map(_fill, 'fill({!r})'.format(text)).execute()
self.map(_fill, u'fill({!r})'.format(text)).execute()
2 changes: 1 addition & 1 deletion bok_choy/web_app_test.py
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ def set_viewport_size(self, width, height):

# Measure the difference between the actual document width and the
# desired viewport width so we can account for scrollbars:
script = "return {width: document.body.clientWidth, height: document.body.clientHeight};"
script = u"return {width: document.body.clientWidth, height: document.body.clientHeight};"
measured = self.driver.execute_script(script)
delta = width - measured['width']

2 changes: 2 additions & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Core requirements for using this application

-c constraints.txt

lazy # For lazy evaluation of the page object's a11y_audit attribute
selenium>=2,<4 # Browser automation driver
six # For Python 2 & 3 compatibility
8 changes: 4 additions & 4 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
#
# make upgrade
#
lazy==1.3
selenium==3.14.0
six==1.11.0
urllib3==1.23 # via selenium
lazy==1.4
selenium==3.141.0
six==1.12.0
urllib3==1.24.1 # via selenium
15 changes: 15 additions & 0 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Version constraints for pip-installation.
#
# This file doesn't install any packages. It specifies version constraints
# that will be applied if a package is needed.
#
# When pinning something here, please provide an explanation of why. Ideally,
# link to other information that will help people in the future to remove the
# pin when possible. Writing an issue against the offending project and
# linking to it here is good.

# more-itertools 6.0.0 dropped Python 2.7 support
more-itertools<6.0.0

# Newer versions of pylint-plugin-utils drop Python 2.7 support
pylint-plugin-utils<0.4
2 changes: 2 additions & 0 deletions requirements/dev.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Dependencies that are used in development environments.
# Please do not use this file for packages that are needed for test runs.

-c constraints.txt

-r pip-tools.txt # pip-tools and its dependencies, for managing requirements files
-r needle.txt # Dependencies for running the various test suites
-r travis.txt # Dependencies for tox and related utilities
73 changes: 33 additions & 40 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -5,57 +5,50 @@
# make upgrade
#
apipkg==1.5
astroid==1.5.2
atomicwrites==1.1.5
attrs==18.1.0
backports.functools-lru-cache==1.5
certifi==2018.4.16
astroid==1.5.3
atomicwrites==1.3.0
attrs==18.2.0
certifi==2018.11.29
chardet==3.0.4
click-log==0.1.8
click==6.7
click==7.0
codecov==2.0.15
configparser==3.5.0
coverage==4.5.1
edx-lint==0.5.5
enum34==1.1.6
coverage==4.5.2
edx-lint==1.1.0
execnet==1.5.0
first==2.0.1
funcsigs==1.0.2
futures==3.2.0 ; python_version == "2.7"
idna==2.7
filelock==3.0.10
idna==2.8
isort==4.3.4
lazy-object-proxy==1.3.1
lazy==1.3
lazy==1.4
mccabe==0.6.1
mock==2.0.0
more-itertools==4.3.0
more-itertools==5.0.0
needle==0.5.0
nose==1.3.7
packaging==17.1
pathlib2==2.3.2
pbr==4.2.0
pillow==5.2.0
pip-tools==2.0.2
pluggy==0.7.1
py==1.5.4
pycodestyle==2.4.0
packaging==19.0
pbr==5.1.2
pillow==5.4.1
pip-tools==3.3.2
pluggy==0.8.1
py==1.7.0
pycodestyle==2.5.0
pylint-celery==0.3
pylint-django==0.7.2
pylint-plugin-utils==0.3
pylint==1.7.1
pyparsing==2.2.0
pytest-cov==2.5.1
pytest-forked==0.2
pytest-xdist==1.22.5
pytest==3.7.1
requests==2.19.1
scandir==1.9.0
selenium==3.14.0
singledispatch==3.4.0.3
six==1.11.0
pylint==1.7.6
pyparsing==2.3.1
pytest-cov==2.6.1
pytest-forked==1.0.2
pytest-xdist==1.26.1
pytest==4.2.1
requests==2.21.0
selenium==3.141.0
six==1.12.0
toml==0.10.0
tox-battery==0.5.1
tox-travis==0.10
tox==3.2.1
urllib3==1.23
virtualenv==16.0.0
wrapt==1.10.11
tox-travis==0.11
tox==3.7.0
urllib3==1.24.1
virtualenv==16.4.0
wrapt==1.11.1
2 changes: 2 additions & 0 deletions requirements/doc.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Requirements for documentation generation

-c constraints.txt

-r base.txt # Core bok-choy dependencies

edx-sphinx-theme # edX theme for Sphinx output
44 changes: 19 additions & 25 deletions requirements/doc.txt
Original file line number Diff line number Diff line change
@@ -4,34 +4,28 @@
#
# make upgrade
#
alabaster==0.7.11 # via sphinx
alabaster==0.7.12 # via sphinx
babel==2.6.0 # via sphinx
bleach==2.1.3 # via readme-renderer
certifi==2018.4.16 # via requests
cffi==1.11.5 # via cmarkgfm
bleach==3.1.0 # via readme-renderer
certifi==2018.11.29 # via requests
chardet==3.0.4 # via requests
cmarkgfm==0.4.2 # via readme-renderer
docutils==0.14 # via readme-renderer, sphinx
edx-sphinx-theme==1.3.0
future==0.16.0 # via readme-renderer
html5lib==1.0.1 # via bleach
idna==2.7 # via requests
imagesize==1.0.0 # via sphinx
edx-sphinx-theme==1.4.0
idna==2.8 # via requests
imagesize==1.1.0 # via sphinx
jinja2==2.10 # via sphinx
lazy==1.3
markupsafe==1.0 # via jinja2
packaging==17.1 # via sphinx
pycparser==2.18 # via cffi
pygments==2.2.0 # via readme-renderer, sphinx
pyparsing==2.2.0 # via packaging
pytz==2018.5 # via babel
readme-renderer==21.0
requests==2.19.1 # via sphinx
selenium==3.14.0
six==1.11.0
lazy==1.4
markupsafe==1.1.0 # via jinja2
packaging==19.0 # via sphinx
pygments==2.3.1 # via readme-renderer, sphinx
pyparsing==2.3.1 # via packaging
pytz==2018.9 # via babel
readme-renderer==24.0
requests==2.21.0 # via sphinx
selenium==3.141.0
six==1.12.0
snowballstemmer==1.2.1 # via sphinx
sphinx==1.7.6
sphinx==1.8.4
sphinxcontrib-websupport==1.1.0 # via sphinx
typing==3.6.4 # via sphinx
urllib3==1.23
webencodings==0.5.1 # via html5lib
urllib3==1.24.1
webencodings==0.5.1 # via bleach
2 changes: 2 additions & 0 deletions requirements/needle.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Requirements for test runs with needle installed for visual diff support

-c constraints.txt

-r test.txt # Base testing dependencies

needle # For test assertions involving screenshots and visual diffs
56 changes: 24 additions & 32 deletions requirements/needle.txt
Original file line number Diff line number Diff line change
@@ -5,46 +5,38 @@
# make upgrade
#
apipkg==1.5
astroid==1.5.2
atomicwrites==1.1.5
attrs==18.1.0
backports.functools-lru-cache==1.5
astroid==1.5.3
atomicwrites==1.3.0
attrs==18.2.0
click-log==0.1.8
click==6.7
configparser==3.5.0
coverage==4.5.1
edx-lint==0.5.5
enum34==1.1.6
click==7.0
coverage==4.5.2
edx-lint==1.1.0
execnet==1.5.0
funcsigs==1.0.2
futures==3.2.0 ; python_version == "2.7"
isort==4.3.4
lazy-object-proxy==1.3.1
lazy==1.3
lazy==1.4
mccabe==0.6.1
mock==2.0.0
more-itertools==4.3.0
more-itertools==5.0.0
needle==0.5.0
nose==1.3.7 # via needle
packaging==17.1
pathlib2==2.3.2
pbr==4.2.0
pillow==5.2.0 # via needle
pluggy==0.7.1
py==1.5.4
pycodestyle==2.4.0
packaging==19.0
pbr==5.1.2
pillow==5.4.1 # via needle
pluggy==0.8.1
py==1.7.0
pycodestyle==2.5.0
pylint-celery==0.3
pylint-django==0.7.2
pylint-plugin-utils==0.3
pylint==1.7.1
pyparsing==2.2.0
pytest-cov==2.5.1
pytest-forked==0.2
pytest-xdist==1.22.5
pytest==3.7.1
scandir==1.9.0
selenium==3.14.0
singledispatch==3.4.0.3
six==1.11.0
urllib3==1.23
wrapt==1.10.11
pylint==1.7.6
pyparsing==2.3.1
pytest-cov==2.6.1
pytest-forked==1.0.2
pytest-xdist==1.26.1
pytest==4.2.1
selenium==3.141.0
six==1.12.0
urllib3==1.24.1
wrapt==1.11.1
2 changes: 2 additions & 0 deletions requirements/pip-tools.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Just the dependencies to run pip-tools, mainly for the "upgrade" make target

-c constraints.txt

pip-tools # Contains pip-compile, used to generate pip requirements files
7 changes: 3 additions & 4 deletions requirements/pip-tools.txt
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
#
# make upgrade
#
click==6.7 # via pip-tools
first==2.0.1 # via pip-tools
pip-tools==2.0.2
six==1.11.0 # via pip-tools
click==7.0 # via pip-tools
pip-tools==3.3.2
six==1.12.0 # via pip-tools
3 changes: 2 additions & 1 deletion requirements/test.in
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Additional requirements for test runs.

-c constraints.txt

-r base.txt # Core bok-choy dependencies

edx_lint # pylint plug-ins for additional code quality checks
futures ; python_version == "2.7" # via isort
mock # For mocking functionality in assorted tests
packaging # For version number parsing and comparisons
pycodestyle # For checking compliance with PEP 8 coding style guidelines
pylint-plugin-utils<0.4 # via pylint-celery, pylint-django; newer versions drop Python 2 support
pytest-cov # For code coverage statistics generation
pytest-xdist # For parallel test execution
56 changes: 24 additions & 32 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -5,43 +5,35 @@
# make upgrade
#
apipkg==1.5 # via execnet
astroid==1.5.2 # via edx-lint, pylint, pylint-celery
atomicwrites==1.1.5 # via pytest
attrs==18.1.0 # via pytest
backports.functools-lru-cache==1.5 # via astroid, pylint
astroid==1.5.3 # via edx-lint, pylint, pylint-celery
atomicwrites==1.3.0 # via pytest
attrs==18.2.0 # via pytest
click-log==0.1.8 # via edx-lint
click==6.7 # via click-log, edx-lint
configparser==3.5.0 # via pylint
coverage==4.5.1 # via pytest-cov
edx-lint==0.5.5
enum34==1.1.6 # via astroid
click==7.0 # via click-log, edx-lint
coverage==4.5.2 # via pytest-cov
edx-lint==1.1.0
execnet==1.5.0 # via pytest-xdist
funcsigs==1.0.2 # via mock, pytest
futures==3.2.0 ; python_version == "2.7"
isort==4.3.4 # via pylint
lazy-object-proxy==1.3.1 # via astroid
lazy==1.3
lazy==1.4
mccabe==0.6.1 # via pylint
mock==2.0.0
more-itertools==4.3.0 # via pytest
packaging==17.1
pathlib2==2.3.2 # via pytest
pbr==4.2.0 # via mock
pluggy==0.7.1 # via pytest
py==1.5.4 # via pytest
pycodestyle==2.4.0
more-itertools==5.0.0 # via pytest
packaging==19.0
pbr==5.1.2 # via mock
pluggy==0.8.1 # via pytest
py==1.7.0 # via pytest
pycodestyle==2.5.0
pylint-celery==0.3 # via edx-lint
pylint-django==0.7.2 # via edx-lint
pylint-plugin-utils==0.3
pylint==1.7.1 # via edx-lint, pylint-celery, pylint-django, pylint-plugin-utils
pyparsing==2.2.0 # via packaging
pytest-cov==2.5.1
pytest-forked==0.2 # via pytest-xdist
pytest-xdist==1.22.5
pytest==3.7.1 # via pytest-cov, pytest-forked, pytest-xdist
scandir==1.9.0 # via pathlib2
selenium==3.14.0
singledispatch==3.4.0.3 # via astroid, pylint
six==1.11.0
urllib3==1.23
wrapt==1.10.11 # via astroid
pylint-plugin-utils==0.3 # via pylint-celery, pylint-django
pylint==1.7.6 # via edx-lint, pylint-celery, pylint-django, pylint-plugin-utils
pyparsing==2.3.1 # via packaging
pytest-cov==2.6.1
pytest-forked==1.0.2 # via pytest-xdist
pytest-xdist==1.26.1
pytest==4.2.1 # via pytest-cov, pytest-forked, pytest-xdist
selenium==3.141.0
six==1.12.0
urllib3==1.24.1
wrapt==1.11.1 # via astroid
2 changes: 2 additions & 0 deletions requirements/travis.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Packages needed in order to run tox.
# Installed by Travis and dev environments.

-c constraints.txt

codecov # Code coverage reporting
tox # Virtualenv management for tests
tox-battery # Makes tox aware of requirements file changes
24 changes: 13 additions & 11 deletions requirements/travis.txt
Original file line number Diff line number Diff line change
@@ -4,17 +4,19 @@
#
# make upgrade
#
certifi==2018.4.16 # via requests
certifi==2018.11.29 # via requests
chardet==3.0.4 # via requests
codecov==2.0.15
coverage==4.5.1 # via codecov
idna==2.7 # via requests
pluggy==0.7.1 # via tox
py==1.5.4 # via tox
requests==2.19.1 # via codecov
six==1.11.0 # via tox
coverage==4.5.2 # via codecov
filelock==3.0.10 # via tox
idna==2.8 # via requests
pluggy==0.8.1 # via tox
py==1.7.0 # via tox
requests==2.21.0 # via codecov
six==1.12.0 # via tox
toml==0.10.0 # via tox
tox-battery==0.5.1
tox-travis==0.10
tox==3.2.1
urllib3==1.23 # via requests
virtualenv==16.0.0 # via tox
tox-travis==0.11
tox==3.7.0
urllib3==1.24.1 # via requests
virtualenv==16.4.0 # via tox
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
import sys
from setuptools import setup

VERSION = '0.9.2'
VERSION = '0.9.3'
DESCRIPTION = 'UI-level acceptance test framework'


@@ -31,6 +31,7 @@ def is_requirement(line):
"""
return not (
line == '' or
line.startswith('-c') or
line.startswith('-r') or
line.startswith('#') or
line.startswith('-e') or
6 changes: 3 additions & 3 deletions tests/pages.py
Original file line number Diff line number Diff line change
@@ -78,13 +78,13 @@ def select_car(self, car_value):
"""
Select the car with ``car_value`` in the drop-down list.
"""
self.q(css='select[name="cars"] option[value="{}"]'.format(car_value)).first.click()
self.q(css=u'select[name="cars"] option[value="{}"]'.format(car_value)).first.click()

def is_car_selected(self, car):
"""
Return ``True`` if the given ``car`` is selected, ``False`` otherwise.
"""
return self.q(css='select[name="cars"] option[value="{}"]'.format(car)).selected
return self.q(css=u'select[name="cars"] option[value="{}"]'.format(car)).selected


class CheckboxPage(SitePage):
@@ -97,7 +97,7 @@ def toggle_pill(self, pill_name):
"""
Toggle the box for the pill with `pill_name` (red or blue).
"""
self.q(css="#fixture input#{}".format(pill_name)).first.click()
self.q(css=u"#fixture input#{}".format(pill_name)).first.click()


class AlertPage(SitePage):
4 changes: 2 additions & 2 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
@@ -299,9 +299,9 @@ def test_save_driver_logs_exception(self, caplog):
# Check that no files were created.
log_types = browser.log_types
for log_type in log_types:
expected_file = os.path.join(tempdir_path, 'js_page_{}.log'.format(log_type))
expected_file = os.path.join(tempdir_path, u'js_page_{}.log'.format(log_type))
assert not os.path.exists(expected_file)
assert "Could not save browser log of type '{}'.".format(log_type) in caplog.text
assert u"Could not save browser log of type '{}'.".format(log_type) in caplog.text

def test_save_source(self):
browser = self.browser
2 changes: 1 addition & 1 deletion tests/test_page_object.py
Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ def test_validate_url(self):
self.assertEqual(
returned_val,
is_valid,
msg="Url: {0}, Expected {1} but got {2}".format(url, is_valid, returned_val)
msg=u"Url: {0}, Expected {1} but got {2}".format(url, is_valid, returned_val)
)

def test_guarded_methods(self):

0 comments on commit 216effb

Please sign in to comment.