305 lines
11 KiB
Python
305 lines
11 KiB
Python
import datetime
|
|
import os
|
|
from django.apps import apps
|
|
from django.contrib.auth.models import AbstractUser
|
|
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.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from six.moves.urllib.parse import quote
|
|
|
|
|
|
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 EmailTestMixin(object):
|
|
email_sender = 'Automatic Software Test <root@localhost>'
|
|
email_base_url = 'http://localhost'
|
|
email_subject_prefix = '[Test]'
|
|
|
|
def assertSender(self, mail):
|
|
self.assertEqual(mail.from_email, self.email_sender)
|
|
|
|
def assertRecipients(self, mail, recipients):
|
|
self.assertEqual(len(mail.recipients()), len(recipients))
|
|
for expected_recipient in recipients:
|
|
if isinstance(expected_recipient, AbstractUser):
|
|
expected_recipient = u'"%s" <%s>' % (expected_recipient.get_full_name(), expected_recipient.email)
|
|
recipients = mail.recipients()
|
|
self.assertIn(expected_recipient, recipients)
|
|
|
|
def assertSubject(self, mail, subject):
|
|
expected_subject = u'{} {}'.format(self.email_subject_prefix, subject)
|
|
self.assertEqual(mail.subject, expected_subject)
|
|
|
|
def assertBody(self, mail, body):
|
|
expected_lines = body.splitlines()
|
|
lines = mail.body.splitlines()
|
|
i = 0
|
|
for expected_line in expected_lines:
|
|
try:
|
|
line = lines[i]
|
|
except IndexError:
|
|
self.fail('line %d: no such line: %s' % (i, expected_line))
|
|
i += 1
|
|
try:
|
|
self.assertEqual(line, expected_line)
|
|
except AssertionError as e:
|
|
self.fail('line %d: %s' % (i, e))
|
|
self.assertEqual(mail.body, body)
|
|
|
|
def setUp(self):
|
|
app_config = apps.get_app_config('dav_base')
|
|
app_config.settings.email_sender = self.email_sender
|
|
app_config.settings.email_base_url = self.email_base_url
|
|
app_config.settings.email_subject_prefix = self.email_subject_prefix
|
|
|
|
|
|
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 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 SeleniumTestCase(StaticLiveServerTestCase):
|
|
headless = True
|
|
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:
|
|
if self.headless:
|
|
self._driver_options.add_argument('--headless')
|
|
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 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.%f'),
|
|
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, 0o700)
|
|
|
|
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'])
|
|
|
|
@tag('screenshots', 'browser')
|
|
def test_screenshots(self):
|
|
for location in self.locations:
|
|
self.get(location)
|
|
self.save_screenshot()
|