# -*- coding: utf-8 -*- # # Copyright (C) 2017 Branko Majic # # This file is part of Django Conntrackt. # # Django Conntrackt is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # Django Conntrackt is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # Django Conntrackt. If not, see . # # Standard library imports. import os import time # Django imports. from django.contrib.staticfiles.testing import StaticLiveServerTestCase # Third-party library imports. from selenium.common.exceptions import WebDriverException from selenium import webdriver # Maximum amount of time to wait before assuming a test has # failed. Used as default for functions that wait. MAX_WAIT = 10 # Delay between subsequent runs of function when using the wait # decorator. EXECUTION_DELAY = 0.5 # Base directory where the tests are located at. BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Base directory where the test binaries are located at. TEST_BINARIES_DIR = os.path.join(BASE_DIR, "..", "..", "test_binaries") # Location of Firefox binary. FIREFOX_BINARY = os.path.join(TEST_BINARIES_DIR, "firefox", "firefox") # Location of geckodriver binary. GECKODRIVER_BINARY = os.path.join(TEST_BINARIES_DIR, "geckodriver") def wait(fn, execution_delay=EXECUTION_DELAY, max_wait=MAX_WAIT): """ Decorator that will produce a wrapper function that will run the specified function repeatedly as long as all of the following conditions have been met: - Function throws either AssertionError or selenium.common.exceptions.WebDriverException. - Combined runtime of all function calls does not exceed the maximum wait time. As soon as the original function returns without any thrown exceptions, the wrapper function will return its result. If the function throws any other exception beyond the ones listed above, this exception will get propagated. Arguments: fn Function that should be run. max_wait Maximum time in seconds to wait for function to execute successfully. execution_delay Delay in seconds between subsequent function runs. Used to prevent high CPU usage. Returns: Wrapper function around passed-in function. The wrapper function, provided it executes without failure, returns the result of original function. """ def modified_fn(*args, **kwargs): start_time = time.time() while True: try: return fn(*args, **kwargs) except (AssertionError, WebDriverException) as e: if time.time() - start_time > max_wait: raise e time.sleep(execution_delay) return modified_fn class FunctionalTest(StaticLiveServerTestCase): """ Helper test class that provides some convenience when writing functional tests. During test set-up, the class will: - Set-up a browser instance, storing it in property self.browser. During the test tear-down, the class will: - Destroy the browser instance. In addition to this, the class provides a convenience method wait_for which can be used to wait for a function to execute within a time slot and assert something, as described for the wait decorator. """ def setUp(self): self.browser = webdriver.Firefox(firefox_binary=FIREFOX_BINARY, executable_path=GECKODRIVER_BINARY) def tearDown(self): self.browser.quit() super(FunctionalTest, self).tearDown() @wait def wait_for(self, fn): return fn()