diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py index f36f3fa15094287c019fcd5023bfb7c0a42bfed8..4e2513d776f673a7319cd46e971862ea6559e56a 100644 --- a/common/test/acceptance/tests/helpers.py +++ b/common/test/acceptance/tests/helpers.py @@ -4,15 +4,16 @@ Test helper functions and base classes. import functools import inspect +import io import json import operator import os import pprint -import unittest +import sys import urlparse from contextlib import contextmanager from datetime import datetime -from unittest import TestCase +from unittest import SkipTest, TestCase import requests from bok_choy.javascript import js_defined @@ -23,7 +24,6 @@ from opaque_keys.edx.locator import CourseLocator from path import Path as path from pymongo import ASCENDING, MongoClient from selenium.common.exceptions import StaleElementReferenceException -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.select import Select @@ -52,10 +52,13 @@ def skip_if_browser(browser): """ def decorator(test_function): + """ + The decorator to be applied to the test function. + """ @functools.wraps(test_function) def wrapper(self, *args, **kwargs): if self.browser.name == browser: - raise unittest.SkipTest('Skipping as this test will not work with {}'.format(browser)) + raise SkipTest('Skipping as this test will not work with {}'.format(browser)) test_function(self, *args, **kwargs) return wrapper return decorator @@ -107,7 +110,7 @@ def load_data_str(rel_path): Load a file from the "data" directory as a string. `rel_path` is the path relative to the data directory. """ - full_path = path(__file__).abspath().dirname() / "data" / rel_path + full_path = path(__file__).abspath().dirname() / "data" / rel_path # pylint: disable=no-value-for-parameter with open(full_path) as data_file: return data_file.read() @@ -318,7 +321,7 @@ def element_has_text(page, css_selector, text): text_present = False text_list = page.q(css=css_selector).text - if len(text_list) > 0 and (text in text_list): + if text_list and (text in text_list): text_present = True return text_present @@ -445,13 +448,13 @@ OPEN_BOOKS = { } -def url_for_help(book_slug, path): +def url_for_help(book_slug, path_component): """ Create a full help URL given a book slug and a path component. """ # Emulate the switch between books that happens in envs/bokchoy.py books = EDX_BOOKS if RELEASE_LINE == "master" else OPEN_BOOKS - url = 'http://edx.readthedocs.io/projects/{}/en/{}{}'.format(books[book_slug], doc_version(), path) + url = 'http://edx.readthedocs.io/projects/{}/en/{}{}'.format(books[book_slug], doc_version(), path_component) return url @@ -741,37 +744,49 @@ class AcceptanceTest(WebAppTest): self.longMessage = True # pylint: disable=invalid-name def tearDown(self): - try: - self.browser.get('http://{}:{}'.format( - os.environ.get('BOK_CHOY_HOSTNAME', '127.0.0.1'), - os.environ.get('BOK_CHOY_LMS_PORT', 8003), - )) - except: # pylint: disable=bare-except - self.browser.get('http://{}:{}'.format( - os.environ.get('BOK_CHOY_HOSTNAME', '127.0.0.1'), - os.environ.get('BOK_CHOY_CMS_PORT', 8031), - )) - logs = self.browser.execute_script("return window.localStorage.getItem('console_log_capture');") - if not logs: + self._save_console_log() + super(AcceptanceTest, self).tearDown() + + def _save_console_log(self): + """ + Retrieve any JS errors caught by our error handler in the browser + and save them to a log file. This is a workaround for Firefox not + supporting the Selenium log capture API yet; for details, see + https://github.com/mozilla/geckodriver/issues/284 + """ + browser_name = os.environ.get('SELENIUM_BROWSER', 'firefox') + if browser_name != 'firefox': return - logs = json.loads(logs) - - log_dir = path('test_root') / 'log' - if 'shard' in os.environ: - log_dir /= "shard_{}".format(os.environ["SHARD"]) - log_dir.mkdir_p() - - with (log_dir / '{}.browser.log'.format(self.id()[:60])).open('w') as browser_log: - for (message, url, line_no, col_no, stack) in logs: - browser_log.write(u"{}:{}:{}: {}\n {}\n".format( - url, - line_no, - col_no, - message, - (stack or "").replace('\n', '\n ') - )) + result = sys.exc_info() + exception_type = result[0] - super(AcceptanceTest, self).tearDown() + # Do not save for skipped tests. + if exception_type is SkipTest: + return + + # If the test failed, save the browser console log. + # The exception info will either be an assertion error (on failure) + # or an actual exception (on error) + if result != (None, None, None): + logs = self.browser.execute_script("return window.localStorage.getItem('console_log_capture');") + if not logs: + return + logs = json.loads(logs) + + log_dir = os.environ.get('SELENIUM_DRIVER_LOG_DIR') + if log_dir and not os.path.exists(log_dir): + os.makedirs(log_dir) + + log_path = os.path.join(log_dir, '{}_browser.log'.format(self.id())) + with io.open(log_path, 'w') as browser_log: + for (message, url, line_no, col_no, stack) in logs: + browser_log.write(u"{}:{}:{}: {}\n {}\n".format( + url, + line_no, + col_no, + message, + (stack or "").replace('\n', '\n ') + )) class UniqueCourseTest(AcceptanceTest): diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index bc878d0d0897a7c33210af6cc074a039c466839f..3da81fa90de9bbe45dcfdecd729070ff2fffea43 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -11,7 +11,7 @@ common/lib/symmath asn1crypto==0.24.0 backports-abc==0.5 # via tornado cffi==1.11.5 -cryptography==2.2.2 +cryptography==2.3 enum34==1.1.6 futures==3.2.0 # via tornado idna==2.7 diff --git a/requirements/edx-sandbox/shared.txt b/requirements/edx-sandbox/shared.txt index 9c112a860a4fec3a6e4194125cdbb4f92af9afeb..7640564156e591b7840ff2d59646ce2ed9b1f8e4 100644 --- a/requirements/edx-sandbox/shared.txt +++ b/requirements/edx-sandbox/shared.txt @@ -10,7 +10,7 @@ -e common/lib/symmath asn1crypto==0.24.0 # via cryptography cffi==1.11.5 # via cryptography -cryptography==2.2.2 +cryptography==2.3 enum34==1.1.6 # via cryptography idna==2.7 # via cryptography ipaddress==1.0.22 # via cryptography diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 0b4656aaaf73d9984dfbaf8214dcdc62cf3bd0c3..278c4205a9542e123d33d60d6d51185ea13da380 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -42,6 +42,7 @@ git+https://github.com/edx/xblock-utils.git@v1.1.1#egg=xblock-utils==1.1.1 -e common/lib/xmodule amqp==1.4.9 # via kombu analytics-python==1.1.0 +aniso8601==3.0.2 # via tincan anyjson==0.3.3 # via kombu appdirs==1.4.3 # via fs argh==0.26.2 @@ -61,7 +62,7 @@ charade==1.0.3 # via pysrt click==6.7 # via user-util coreapi==2.3.3 # via django-rest-swagger, openapi-codec coreschema==0.0.4 # via coreapi -cryptography==2.2.2 +cryptography==2.3 cssutils==1.0.2 # via pynliner ddt==0.8.0 decorator==4.3.0 # via dogapi, pycontracts @@ -83,7 +84,7 @@ django-method-override==0.1.0 django-model-utils==3.0.0 django-mptt==0.8.7 django-multi-email-field==0.5.1 # via edx-enterprise -django-mysql==2.3.0 +django-mysql==2.3.1 django-oauth-toolkit==0.12.0 django-object-actions==0.10.0 # via edx-enterprise django-pyfs==2.0 @@ -93,7 +94,7 @@ django-require==1.0.11 django-rest-swagger==2.2.0 django-sekizai==0.10.0 django-ses==0.8.4 -django-simple-history==2.2.0 +django-simple-history==2.3.0 django-splash==0.2.2 django-statici18n==1.4.0 django-storages==1.4.1 @@ -177,7 +178,7 @@ openapi-codec==1.3.2 # via django-rest-swagger path.py==8.2.1 pathtools==0.1.2 paver==1.3.4 -pbr==4.1.0 +pbr==4.1.1 pdfminer==20140328 piexif==1.0.2 pillow==3.4.0 @@ -211,7 +212,7 @@ requests-oauthlib==0.6.1 requests==2.9.1 rest-condition==1.0.3 rfc6266-parser==0.0.5.post2 -rules==1.3 +rules==2.0 s3transfer==0.1.13 # via boto3 sailthru-client==2.2.3 scipy==0.14.0 @@ -227,6 +228,7 @@ sorl-thumbnail==12.3 sortedcontainers==0.9.2 stevedore==1.10.0 sympy==0.7.1 +tincan==0.0.5 # via edx-enterprise unicodecsv==0.14.1 uritemplate==3.0.0 # via coreapi urllib3==1.23 # via elasticsearch diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index dddeb3090ed56c85311e6f9683993b6c22605fde..6a9ab722c9b5808014a49618c028fccafc455017 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -46,6 +46,7 @@ git+https://github.com/edx/xblock-utils.git@v1.1.1#egg=xblock-utils==1.1.1 alabaster==0.7.11 # via sphinx amqp==1.4.9 analytics-python==1.1.0 +aniso8601==3.0.2 anyjson==0.3.3 apipkg==1.5 appdirs==1.4.3 @@ -62,7 +63,7 @@ beautifulsoup==3.2.1 before-after==1.0.1 billiard==3.3.0.23 bleach==1.4 -bok-choy==0.7.3 +bok-choy==0.8.0 boto3==1.4.8 boto==2.39.0 botocore==1.8.17 @@ -77,7 +78,7 @@ constantly==15.1.0 coreapi==2.3.3 coreschema==0.0.4 coverage==4.2 -cryptography==2.2.2 +cryptography==2.3 cssselect==1.0.3 cssutils==1.0.2 ddt==0.8.0 @@ -103,7 +104,7 @@ django-method-override==0.1.0 django-model-utils==3.0.0 django-mptt==0.8.7 django-multi-email-field==0.5.1 -django-mysql==2.3.0 +django-mysql==2.3.1 django-oauth-toolkit==0.12.0 django-object-actions==0.10.0 django-pyfs==2.0 @@ -113,7 +114,7 @@ django-require==1.0.11 django-rest-swagger==2.2.0 django-sekizai==0.10.0 django-ses==0.8.4 -django-simple-history==2.2.0 +django-simple-history==2.3.0 django-splash==0.2.2 django-statici18n==1.4.0 django-storages==1.4.1 @@ -231,7 +232,7 @@ parsel==1.5.0 path.py==8.2.1 pathtools==0.1.2 paver==1.3.4 -pbr==4.1.0 +pbr==4.1.1 pdfminer==20140328 piexif==1.0.2 pillow==3.4.0 @@ -293,7 +294,7 @@ requests-oauthlib==0.6.1 requests==2.9.1 rest-condition==1.0.3 rfc6266-parser==0.0.5.post2 -rules==1.3 +rules==2.0 s3transfer==0.1.13 sailthru-client==2.2.3 scipy==0.14.0 @@ -313,7 +314,7 @@ social-auth-app-django==2.1.0 social-auth-core==1.7.0 sorl-thumbnail==12.3 sortedcontainers==0.9.2 -sphinx==1.7.5 +sphinx==1.7.6 sphinxcontrib-websupport==1.1.0 # via sphinx splinter==0.8.0 sqlparse==0.2.4 # via django-debug-toolbar @@ -323,6 +324,7 @@ sympy==0.7.1 testfixtures==6.2.0 testtools==2.3.0 text-unidecode==1.2 +tincan==0.0.5 tox-battery==0.5.1 tox==3.1.2 traceback2==1.4.0 diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt index ebaa7f91c5e1a9fe65573bcb9ef53874551a59cb..0aed1139d1cfd6aca5c783c975e56694ef464a5d 100644 --- a/requirements/edx/paver.txt +++ b/requirements/edx/paver.txt @@ -14,7 +14,7 @@ mock==1.0.1 path.py==8.2.1 pathtools==0.1.2 # via watchdog paver==1.3.4 -pbr==4.1.0 # via stevedore +pbr==4.1.1 # via stevedore psutil==1.2.1 pymongo==2.9.1 python-memcached==1.48 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 8f6f5b82a2f6c56353513fb7a0b241b16b0c370c..bca1f12750d58afa8bc3b4dd273f002ec6eedb0e 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -43,6 +43,7 @@ git+https://github.com/edx/xblock-utils.git@v1.1.1#egg=xblock-utils==1.1.1 -e common/lib/xmodule amqp==1.4.9 analytics-python==1.1.0 +aniso8601==3.0.2 anyjson==0.3.3 apipkg==1.5 # via execnet appdirs==1.4.3 @@ -59,7 +60,7 @@ beautifulsoup==3.2.1 before-after==1.0.1 billiard==3.3.0.23 bleach==1.4 -bok-choy==0.7.3 +bok-choy==0.8.0 boto3==1.4.8 boto==2.39.0 botocore==1.8.17 @@ -74,7 +75,7 @@ constantly==15.1.0 # via twisted coreapi==2.3.3 coreschema==0.0.4 coverage==4.2 -cryptography==2.2.2 +cryptography==2.3 cssselect==1.0.3 cssutils==1.0.2 ddt==0.8.0 @@ -99,7 +100,7 @@ django-method-override==0.1.0 django-model-utils==3.0.0 django-mptt==0.8.7 django-multi-email-field==0.5.1 -django-mysql==2.3.0 +django-mysql==2.3.1 django-oauth-toolkit==0.12.0 django-object-actions==0.10.0 django-pyfs==2.0 @@ -109,7 +110,7 @@ django-require==1.0.11 django-rest-swagger==2.2.0 django-sekizai==0.10.0 django-ses==0.8.4 -django-simple-history==2.2.0 +django-simple-history==2.3.0 django-splash==0.2.2 django-statici18n==1.4.0 django-storages==1.4.1 @@ -222,7 +223,7 @@ parsel==1.5.0 # via scrapy path.py==8.2.1 pathtools==0.1.2 paver==1.3.4 -pbr==4.1.0 +pbr==4.1.1 pdfminer==20140328 piexif==1.0.2 pillow==3.4.0 @@ -282,7 +283,7 @@ requests-oauthlib==0.6.1 requests==2.9.1 rest-condition==1.0.3 rfc6266-parser==0.0.5.post2 -rules==1.3 +rules==2.0 s3transfer==0.1.13 sailthru-client==2.2.3 scipy==0.14.0 @@ -307,6 +308,7 @@ sympy==0.7.1 testfixtures==6.2.0 testtools==2.3.0 # via fixtures, python-subunit text-unidecode==1.2 # via faker +tincan==0.0.5 tox-battery==0.5.1 tox==3.1.2 traceback2==1.4.0 # via testtools, unittest2