From 8e9e95dea2c9f71132b16759d182f39cf3319825 Mon Sep 17 00:00:00 2001 From: Jason Antman Date: Sun, 25 Feb 2018 08:14:27 -0500 Subject: [PATCH] acceptance tests - retry getting selenium webdriver instance of ConnectionError raised --- CHANGES.rst | 1 + biweeklybudget/tests/conftest.py | 62 ++++++++++++++++++++++++++++---- tox.ini | 3 ++ 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 17b190d8..5be173f2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,6 +31,7 @@ Unreleased Changes * Add support for writing tests of data manipulation during database migrations, and write tests for the migration in for Issue 105, above. * Add support for ``BIWEEKLYBUDGET_LOG_FILE`` environment variable to cause Flask application logs to go to a file *in addition to* STDOUT. * Add support for ``SQL_POOL_PRE_PING`` environment variable to enable SQLAlchemy ``pool_pre_ping`` feature (see `Disconnect Handling - Pessimistic `_) for resource-constrained systems. +* Modify acceptance tests to retry up to 3 times, 3 seconds apart, if a ConnectionError (or subclass thereof) is raised when constructing the Selenium driver instance. This is a workaround for intermittent ConnectionResetErrors in TravisCI. 0.7.1 (2018-01-10) ------------------ diff --git a/biweeklybudget/tests/conftest.py b/biweeklybudget/tests/conftest.py index 57ef3ddc..899a5617 100644 --- a/biweeklybudget/tests/conftest.py +++ b/biweeklybudget/tests/conftest.py @@ -45,6 +45,8 @@ from time import time from tempfile import mkstemp +from retrying import retry + import biweeklybudget.settings from biweeklybudget.tests.fixtures.sampledata import SampleDataLoader from biweeklybudget.tests.sqlhelpers import restore_mysqldump, do_mysqldump @@ -59,6 +61,8 @@ try: import pytest_selenium.pytest_selenium + from selenium.webdriver.support.event_firing_webdriver import \ + EventFiringWebDriver HAVE_PYTEST_SELENIUM = True except ImportError: HAVE_PYTEST_SELENIUM = False @@ -230,19 +234,63 @@ def base_url(testflask): return testflask.url() -@pytest.fixture -def selenium(selenium): +def retry_if_conn_error(ex): + return isinstance(ex, ConnectionError) + + +@retry( + stop_max_attempt_number=3, wait_fixed=3000, + retry_on_exception=retry_if_conn_error +) +def get_driver_for_class(driver_class, driver_kwargs): + """ + Wrapper around the selenium ``driver()`` fixture's + ``driver_class(**driver_kwargs)`` call that retries up to 3 times, 3 + seconds apart, if a ConnectionError is raised. + """ + return driver_class(**driver_kwargs) + + +@pytest.yield_fixture +def driver(request, driver_class, driver_kwargs): + """ + Returns a WebDriver instance based on options and capabilities + + This is copied from pytest-selenium 1.11.4, but modified to retry getting + the driver up to 3 times to cope with intermittent connection resets in + TravisCI. We ripped the original ``driver = driver_class(**driver_kwargs)`` + out and replaced it with the ``get_driver_for_class()`` function, which + is wrapped in the retrying package's ``@retry`` decorator. + """ + driver = get_driver_for_class(driver_class, driver_kwargs) + + event_listener = request.config.getoption('event_listener') + if event_listener is not None: + # Import the specified event listener and wrap the driver instance + mod_name, class_name = event_listener.rsplit('.', 1) + mod = __import__(mod_name, fromlist=[class_name]) + event_listener = getattr(mod, class_name) + if not isinstance(driver, EventFiringWebDriver): + driver = EventFiringWebDriver(driver, event_listener()) + + request.node._driver = driver + yield driver + driver.quit() + + +@pytest.yield_fixture +def selenium(driver): """ Per pytest-selenium docs, use this to override the selenium fixture to provide global common setup. """ - selenium.set_window_size(1920, 1080) - selenium.implicitly_wait(2) + driver.set_window_size(1920, 1080) + driver.implicitly_wait(2) # from http://stackoverflow.com/a/13853684/211734 - selenium.set_script_timeout(30) + driver.set_script_timeout(30) # from http://stackoverflow.com/a/17536547/211734 - selenium.set_page_load_timeout(30) - return selenium + driver.set_page_load_timeout(30) + yield driver def _gather_screenshot(item, report, driver, summary, extra): diff --git a/tox.ini b/tox.ini index d825d44a..1431ca96 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,7 @@ deps = pytest-blockage pytest-timeout alembic-verify + retrying passenv=CI TRAVIS* CONTINUOUS_INTEGRATION AWS* NO_REFRESH_DB DB_CONNSTRING NO_CLASS_REFRESH_DB setenv = @@ -138,6 +139,7 @@ deps = pytest-selenium pytest-timeout alembic-verify + retrying passenv = {[testenv]passenv} setenv = {[testenv]setenv} basepython = python2.7 @@ -170,6 +172,7 @@ deps = freezegun alembic-verify pytest-html + retrying passenv=CI TRAVIS* CONTINUOUS_INTEGRATION NO_REFRESH_DB DB_CONNSTRING NO_CLASS_REFRESH_DB MYSQL_* setenv = {[testenv]setenv} basepython = python3.6