import datetime import os import sys import time import urllib from unittest import skip, SkipTest from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test import SimpleTestCase, TestCase, tag from django.urls import reverse from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def skip_unless_tag_option(): if '--tag' in sys.argv: return lambda func: func else: return skip('Skipped unless --tag option is used') class AppSetting(object): def __init__(self, name, of=None): self.name = name self.of = of class AppsTestCase(SimpleTestCase): app_config = None settings = () def setUp(self): super(AppsTestCase, self).setUp() if self.app_config: self.configured_settings = self.app_config.settings else: self.configured_settings = None def test_settings(self): config = self.configured_settings for setting in self.settings: name = setting.name self.assertTrue(hasattr(config, name), 'Settings do not contain {}'.format(name)) value = getattr(config, name) of = setting.of if of is not None: self.assertIsInstance(value, of) class Url(object): def __init__(self, location, name=None, func=None, **kwargs): self.location = location self.name = name self.func = func self.redirect = kwargs.get('redirect', False) self.status_code = kwargs.get('status_code', 200) self.follow = kwargs.get('follow', False) class UrlsTestCase(SimpleTestCase): urls = () def test_locations(self): for url in self.urls: if url.location: response = self.client.get(url.location, follow=url.follow) if url.redirect: self.assertRedirects(response, url.redirect) else: self.assertEqual(response.status_code, url.status_code, 'Getting \'{}\' is not OK'.format(url.location)) if url.func: self.assertEqual(response.resolver_match.func.__name__, url.func.__name__, 'Getting \'{}\' resolve to wrong function'.format(url.location)) def test_names(self): for url in self.urls: if url.name: response = self.client.get(reverse(url.name), follow=url.follow) if url.redirect: self.assertRedirects(response, url.redirect) else: self.assertEqual(response.status_code, url.status_code, 'Getting url named \'{}\' is not OK'.format(url.name)) if url.func: self.assertEqual(response.resolver_match.func.__name__, url.func.__name__, 'Getting url named \'{}\' resolve to wrong function'.format(url.name)) class FormDataSet(object): def __init__(self, data, expected_errors=None, form_kwargs=None): self.data = data self.expected_errors = expected_errors self.form_kwargs = form_kwargs class FormsTestCase(TestCase): form_class = None valid_data_sets = () invalid_data_sets = () def test_valid_data(self, form_class=None, data_sets=None, form_kwargs=None): if form_class is None: form_class = self.form_class if form_class is None: return True if data_sets is None: data_sets = self.valid_data_sets for data_set in data_sets: fk = {} if form_kwargs is not None: fk.update(form_kwargs) if data_set.form_kwargs is not None: fk.update(data_set.form_kwargs) fk['data'] = data_set.data form = form_class(**fk) if not form.is_valid(): errors = [] for key in form.errors.as_data(): for ve in form.errors[key].as_data(): errors.append(u'%s (%s)' % (ve.code, ve.message)) self.fail(u'Invalid form data \'%s\': %s' % (data_set.data, errors)) def test_invalid_data(self, form_class=None, data_sets=None, form_kwargs=None): if form_class is None: form_class = self.form_class if form_class is None: return True if data_sets is None: data_sets = self.invalid_data_sets for data_set in data_sets: fk = {} if form_kwargs is not None: fk.update(form_kwargs) if data_set.form_kwargs is not None: fk.update(data_set.form_kwargs) fk['data'] = data_set.data form = form_class(**fk) if form.is_valid(): self.fail('Valid form data: \'%s\'' % data_set.data) if data_set.expected_errors: error_dicts = form.errors.as_data() for key, code in data_set.expected_errors: error_codes = [ve.code for ve in error_dicts[key]] self.assertIn(code, error_codes) class SeleniumTestCase(StaticLiveServerTestCase): window_width = 1024 window_height = 768 def __init__(self, *args, **kwargs): super(SeleniumTestCase, self).__init__(*args, **kwargs) self._driver = None self._driver_options = webdriver.FirefoxOptions() self.quit_selenium = None @property def selenium(self): if self._driver is None: self._driver = webdriver.Firefox(options=self._driver_options) if self.quit_selenium is None: self.quit_selenium = True if self.window_width and self.window_height: self._driver.set_window_size(self.window_width, self.window_height) return self._driver def tearDown(self): if self.quit_selenium: self.selenium.quit() super(SeleniumTestCase, self).tearDown() def complete_url(self, location): base_url = self.live_server_url return '{}/{}'.format(base_url, location.lstrip('/')) def get(self, location): return self.selenium.get(self.complete_url(location)) def wait_on(self, driver, ec_name, ec_argument, timeout=30): ec = getattr(EC, ec_name) return WebDriverWait(driver, timeout).until(ec(ec_argument)) def wait_on_presence(self, driver, locator, timeout=30): ec_name = 'presence_of_element_located' return self.wait_on(driver, ec_name, locator, timeout) def wait_until_stale(self, driver, element, timeout=30): ec_name = 'staleness_of' return self.wait_on(driver, ec_name, element, timeout) class ScreenshotTestCase(SeleniumTestCase): screenshot_prefix = '' locations = () def __init__(self, *args, **kwargs): super(ScreenshotTestCase, self).__init__(*args, **kwargs) # screenshot_base_dir = os.path.join('/', 'tmp', 'test-screenshots') screenshot_base_dir = 'test-screenshots' self.screenshot_path = screenshot_base_dir self.screenshot_sequences = {} def sanitize_filename(self, location): return urllib.quote(location).replace('/', '--') def save_screenshot(self, title=None, sequence=None, resize=True): if sequence is None: sequence = '' else: if sequence in self.screenshot_sequences: self.screenshot_sequences[sequence] += 1 else: self.screenshot_sequences[sequence] = 1 n = self.screenshot_sequences[sequence] sequence = u'%s-%04d-' % (sequence, n) if title is None: location = self.selenium.current_url if location.startswith(self.live_server_url): location = location[len(self.live_server_url):] location = location.lstrip('/') if location == '': location = 'root' title = location base_name = u'{timestamp}-{prefix}{sequence}{title}.png'.format( timestamp=datetime.datetime.now().strftime('%Y%m%d-%H%M%S'), prefix=self.screenshot_prefix, sequence=sequence, title=title, ) path = os.path.join(self.screenshot_path, self.sanitize_filename(base_name)) if not os.path.isdir(self.screenshot_path): os.makedirs(self.screenshot_path, 0700) restore_size = False if resize: window_size = self.selenium.get_window_size() deco_height = self.selenium.execute_script('return window.outerHeight - window.innerHeight') doc_height = self.selenium.execute_script('return document.body.scrollHeight') if (window_size['height'] - deco_height) < doc_height: self.selenium.set_window_size(window_size['width'], doc_height + deco_height) restore_size = True self.selenium.save_screenshot(path) if restore_size: self.selenium.set_window_size(window_size['width'], window_size['height']) @skip_unless_tag_option() @tag('screenshots') def test_screenshots(self): for location in self.locations: self.get(location) self.save_screenshot()