From 1bcd47930472fb2a471b57d65fae8e2c1ed4e13a Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 21 Mar 2019 17:59:35 +0100 Subject: [PATCH] UPD: more tests. --- dav_base/tests/generic.py | 77 ++-- dav_events/tests/test_screenshots.py | 541 +++++++++++++++++++++++++++ 2 files changed, 592 insertions(+), 26 deletions(-) create mode 100644 dav_events/tests/test_screenshots.py diff --git a/dav_base/tests/generic.py b/dav_base/tests/generic.py index 6b767d4..b786ba4 100644 --- a/dav_base/tests/generic.py +++ b/dav_base/tests/generic.py @@ -1,12 +1,14 @@ import datetime import os import sys +import time import urllib -from unittest import skip +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 @@ -157,14 +159,27 @@ class FormsTestCase(TestCase): class SeleniumTestCase(StaticLiveServerTestCase): - def setUp(self): - super(SeleniumTestCase, self).setUp() - self.selenium = webdriver.Firefox() - if not hasattr(self, 'quit_selenium'): - self.quit_selenium = 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: + 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 hasattr(self, 'quit_selenium') and self.quit_selenium: + if self.quit_selenium: self.selenium.quit() super(SeleniumTestCase, self).tearDown() @@ -172,37 +187,37 @@ class SeleniumTestCase(StaticLiveServerTestCase): base_url = self.live_server_url return '{}/{}'.format(base_url, location.lstrip('/')) - def wait_until_stale(self, driver, element, timeout=30): - return WebDriverWait(driver, timeout).until(EC.staleness_of(element)) + 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): - return WebDriverWait(driver, timeout).until(EC.presence_of_element_located(locator)) + 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 = '' - window_width = 1024 - window_height = 768 locations = () - @classmethod - def setUpClass(cls): - super(ScreenshotTestCase, cls).setUpClass() - + 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' - cls.screenshot_path = screenshot_base_dir - cls.screenshot_sequences = {} - - def setUp(self): - super(ScreenshotTestCase, self).setUp() - if self.window_width and self.window_height: - self.selenium.set_window_size(self.window_width, self.window_height) + 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): + def save_screenshot(self, title=None, sequence=None, resize=True): if sequence is None: sequence = '' else: @@ -230,10 +245,20 @@ class ScreenshotTestCase(SeleniumTestCase): 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) - def get(self, location): - return self.selenium.get(self.complete_url(location)) + if restore_size: + self.selenium.set_window_size(window_size['width'], window_size['height']) @skip_unless_tag_option() @tag('screenshots') diff --git a/dav_events/tests/test_screenshots.py b/dav_events/tests/test_screenshots.py new file mode 100644 index 0000000..2a2cd87 --- /dev/null +++ b/dav_events/tests/test_screenshots.py @@ -0,0 +1,541 @@ +# -*- coding: utf-8 -*- +import datetime +import time +from django.apps import apps +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.test import tag +from django.urls import reverse +from django.utils.translation import ugettext +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import Select + + +from dav_base.tests.generic import skip_unless_tag_option, ScreenshotTestCase + +TEST_PASSWORD = u'me||ön 2' +TEST_EVENT_DATA_S = { + 'mode': 'training', + 'sport': 'S', + 'ski_lift': True, + 'first_day': '24.12.2019', + 'last_day': '26.12.2019', + 'country': 'CH', + 'location': u'St. Antönien', + 'transport_other': 'Helikopter', + 'meeting_point': 'dav', + 'meeting_time': '10:29', + 'return_arrival_time': '20:14', + 'arrival_previous_day': True, + 'basecamp': u'Grand Hôtel', + 'accommodation': 'NONE', + 'meals_other': u'Fünf-Sterne-Küche per Zimmerservice und Minibar', + 'requirements_add': u'Fußnoten: https://de.wikipedia.org/wiki/Heliskiing', + 'pre_meeting_1': '10.12.2019 18:35', + 'trainer_firstname': u'Trainer1 Ö.', + 'trainer_familyname': u'Ä. Tourenleiter', + 'trainer_email': 'trainer@localhost', + 'trainer_2_fullname': u'Trainer2 Überflieger', + 'trainer_3_fullname': u'Trainer3 Weißalles', + 'deadline': 'month', + 'charge': '1800', + 'additional_costs': None, + 'course_topic_1': u'Grundkentnisse in der neusten Willy-Bögner-Kollektion', + 'course_topic_2': u'Ein- und Aussteigen am Heliköpter', + 'course_topic_3': u'Cocktailkunde für dekadentes Après-Ski', + 'course_topic_4': u'Und vieles möhr', + 'course_goal_1': [ + u'Güt aussehen', + u'Lässig sein', + u'Geld scheißen', + ], + 'title': u'Heli-Sking-Kurs für Snobs', + 'description': [ + u'Hier steht jetzt so allerhand Scheiß.', + u'z.B. eine ÜRL: https://de.wikipedia.org (leider öhne Ümläüte)', + u'oder eine E-Mail-Ädresse: heinzel@alpenverein-karlsruhe.de' + ], + 'internal_note': [ + u'Automatisierter Software Test', + u'Viele Grüße', + u' heinzel =u}', + ], +} +TEST_EVENT_DATA_W = { + 'level': 'advanced', + 'first_day': '21.6.2019', + 'alt_first_day': '22.06.2019', + 'terrain': 'flats', + 'location': u'Karlsruhe (Schloßpark)', + 'transport': 'public', + 'meeting_point_other': u'Bahnhof der Schloßgartenbahn', + 'departure_time': '07:00', + 'departure_ride': u'Schloßgartenbahn in Richtung Schloßgartenbahnbahnhof', + 'return_departure_time': '19', + 'return_arrival_time': '19:10', + 'trainer_phone': u'+555 666 AB (Büro)', + 'title': u'Wanderung durch den Karlsruher Schloßpark', + 'description': [ + u'Hier steht jetzt so allerhand Scheiß.', + u'z.B. eine ÜRL: https://de.wikipedia.org (leider öhne Ümläüte)', + u'oder eine E-Mail-Ädresse: heinzel@alpenverein-karlsruhe.de' + ], + 'internal_note': [ + u'Automatisierter Software Test', + u'Viele Grüße', + u' heinzel =u}', + ], +} + + +@skip_unless_tag_option() +@tag('screenshots') +class TestCase(ScreenshotTestCase): + screenshot_prefix = 'dav_events-' + + def create_user_for_role(self, role_name, first_name, last_name): + user = None + setting_name = 'groups_{}'.format(role_name) + app_config = apps.get_app_config('dav_events') + group_names = getattr(app_config.settings, setting_name) + if group_names: + group_name = group_names[0] + group = Group(name=group_name) + group.save() + user_model = get_user_model() + email = '{}@localhost'.format(role_name) + user_name = email + password = TEST_PASSWORD + user = user_model.objects.create_user(username=user_name, password=password, email=user_name, + first_name=first_name, last_name=last_name) + user.groups.add(group) + + return user + + def setUp(self): + super(TestCase, self).setUp() + + self.manager_super = self.create_user_for_role('manager_super', 'Manager Super', 'Tourenreferent') + self.manager_w = self.create_user_for_role('manager_w', 'Manager W', 'BereichsleiterWandern') + self.manager_s = self.create_user_for_role('manager_s', 'Manager S', 'BereichsleiterSki') + self.publisher_print = self.create_user_for_role('publisher_print', 'Publisher Print', 'RedaktionPrint') + self.publisher_web = self.create_user_for_role('publisher_web', 'Publisher Web', 'RedaktionWeb') + self.publisher_facebook = self.create_user_for_role('publisher_facebook', 'Publisher Facebook', 'RedaktionFacebook') + + def login(self, username, password): + self.get(reverse('dav_auth:login')) + c = self.selenium + username_field = self.wait_on_presence(c, (By.ID, 'id_username')) + username_field.clear() + username_field.send_keys(username) + password_field = c.find_element_by_id('id_password') + password_field.clear() + password_field.send_keys(password) + password_field.send_keys(Keys.RETURN) + self.wait_until_stale(c, password_field) + + def logout(self): + self.get(reverse('dav_auth:logout')) + + def create_event(self, event_data, auth=False, screenshots=True): + data = event_data + c = self.selenium + + c.get(self.complete_url(reverse('dav_events:root'))) + + if auth: + sequence_name = 'create_event_auth' + button = self.wait_on_presence(c, (By.ID, 'btn-events-list')) + button.click() + button = self.wait_on_presence(c, (By.ID, 'btn-event-create')) + if screenshots: + self.save_screenshot('event_list_before', sequence=sequence_name) + else: + sequence_name = 'create_event_unauth' + button = self.wait_on_presence(c, (By.ID, 'btn-event-create')) + + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('mode_form', sequence=sequence_name) + + if 'mode' in data: + field = c.find_element_by_id('id_mode') + radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['mode'])) + radio.click() + + if 'sport' in data: + field = c.find_element_by_id('id_sport') + radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['sport'])) + radio.click() + + if 'level' in data: + field = c.find_element_by_id('id_level') + radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['level'])) + radio.click() + + if 'ski_lift' in data and data['ski_lift']: + field = c.find_element_by_id('id_ski_lift') + field.click() + + field = c.find_element_by_css_selector('input#id_first_day_widget') + field.send_keys(data['first_day']) + td = c.find_element_by_css_selector('div.datetimepicker-days td.active') + td.click() + + field = c.find_element_by_css_selector('input#id_last_day_widget') + field.click() + if screenshots: + self.save_screenshot('last_date_widget', sequence=sequence_name) + if 'last_day' in data: + field.send_keys(data['last_day']) + + if 'alt_first_day' in data: + field = c.find_element_by_css_selector('input#id_alt_first_day_widget') + field.send_keys(data['alt_first_day']) + + if 'alt_last_day' in data: + field = c.find_element_by_css_selector('input#id_alt_last_day_widget') + field.send_keys(data['alt_last_day']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('location_form', sequence=sequence_name) + + if 'country' in data: + field = c.find_element_by_id('id_country') + Select(field).select_by_value(data['country']) + + if 'terrain' in data: + field = c.find_element_by_id('id_terrain') + Select(field).select_by_value(data['terrain']) + + if 'location' in data: + field = c.find_element_by_id('id_location') + field.send_keys(data['location']) + + if 'transport_other' in data: + field = c.find_element_by_id('id_transport_other') + field.send_keys(data['transport_other']) + if 'transport' in data: + field = c.find_element_by_id('id_transport') + Select(field).select_by_value(data['transport']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('journey_form', sequence=sequence_name) + + if 'meeting_point_other' in data: + field = c.find_element_by_id('id_meeting_point_other') + field.send_keys(data['meeting_point_other']) + if 'meeting_point' in data: + field = c.find_element_by_id('id_meeting_point') + Select(field).select_by_value(data['meeting_point']) + + if 'meeting_time' in data: + field = c.find_element_by_css_selector('input#id_meeting_time_widget') + field.send_keys(data['meeting_time']) + if 'departure_time' in data: + field = c.find_element_by_css_selector('input#id_departure_time_widget') + field.send_keys(data['departure_time']) + + if 'departure_ride' in data: + field = c.find_element_by_id('id_departure_ride') + field.send_keys(data['departure_ride']) + + if 'return_departure_time' in data: + field = c.find_element_by_css_selector('input#id_return_departure_time_widget') + field.send_keys(data['return_departure_time']) + if 'return_arrival_time' in data: + field = c.find_element_by_css_selector('input#id_return_arrival_time_widget') + field.send_keys(data['return_arrival_time']) + + if 'arrival_previous_day' in data and data['arrival_previous_day']: + field = c.find_element_by_id('id_arrival_previous_day') + field.click() + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + + if 'last_day' in data: + if screenshots: + self.save_screenshot('accommodation_form', sequence=sequence_name) + + if 'basecamp' in data: + field = c.find_element_by_id('id_basecamp') + field.send_keys(data['basecamp']) + + if 'accommodation' in data: + field = c.find_element_by_id('id_accommodation') + Select(field).select_by_value(data['accommodation']) + + if 'meals_other' in data: + field = c.find_element_by_id('id_meals_other') + field.send_keys(data['meals_other']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + + if screenshots: + self.save_screenshot('requirements_form', sequence=sequence_name) + + if 'requirements_add' in data: + field = c.find_element_by_id('id_requirements') + field.send_keys(Keys.RETURN) + if isinstance(data['requirements_add'], list): + field.send_keys(Keys.RETURN.join(data['requirements_add'])) + else: + field.send_keys(data['requirements_add']) + + if 'pre_meeting_1' in data: + field = c.find_element_by_css_selector('input#id_pre_meeting_1_widget') + field.send_keys(data['pre_meeting_1']) + + if 'pre_meeting_2' in data: + field = c.find_element_by_css_selector('input#id_pre_meeting_2_widget') + field.send_keys(data['pre_meeting_2']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('trainer_form', sequence=sequence_name) + + if 'trainer_firstname' in data: + field = self.wait_on_presence(c, (By.ID, 'id_trainer_firstname')) + field.send_keys(data['trainer_firstname']) + + if 'trainer_familyname' in data: + field = c.find_element_by_id('id_trainer_familyname') + field.send_keys(data['trainer_familyname']) + + if 'trainer_email' in data: + field = c.find_element_by_id('id_trainer_email') + field.send_keys(data['trainer_email']) + + if 'trainer_phone' in data: + field = c.find_element_by_id('id_trainer_phone') + field.send_keys(data['trainer_phone']) + + if 'trainer_2_fullname' in data: + field = c.find_element_by_id('id_trainer_2_fullname') + field.send_keys(data['trainer_2_fullname']) + + if 'trainer_3_fullname' in data: + field = c.find_element_by_id('id_trainer_3_fullname') + field.send_keys(data['trainer_3_fullname']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('registration_form', sequence=sequence_name) + + if 'deadline' in data: + field = c.find_element_by_id('id_deadline') + radio = field.find_element_by_css_selector('input[value=\'{}\']'.format(data['deadline'])) + radio.click() + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('charges_form', sequence=sequence_name) + + if 'charge' in data: + field = c.find_element_by_id('id_charge') + field.clear() + field.send_keys(data['charge']) + + if 'additional_costs' in data: + field = c.find_element_by_id('id_additional_costs') + field.clear() + if data['additional_costs'] is not None: + field.send_keys(data['additional_costs']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + + if 'mode' in data and data['mode'] == 'training': + if screenshots: + self.save_screenshot('training_form', sequence=sequence_name) + + field = c.find_element_by_id('id_course_topic_1') + if isinstance(data['course_topic_1'], list): + field.send_keys(Keys.RETURN.join(data['course_topic_1'])) + else: + field.send_keys(data['course_topic_1']) + + if 'course_topic_2' in data: + field = c.find_element_by_id('id_course_topic_2') + field.send_keys(data['course_topic_2']) + + if 'course_topic_3' in data: + button = c.find_element_by_id('btn-topic-add') + button.click() + field = c.find_element_by_id('id_course_topic_3') + field.send_keys(data['course_topic_3']) + + if 'course_topic_4' in data: + button.click() + field = c.find_element_by_id('id_course_topic_4') + field.send_keys(data['course_topic_4']) + + field = c.find_element_by_id('id_course_goal_1') + if isinstance(data['course_goal_1'], list): + field.send_keys(Keys.RETURN.join(data['course_goal_1'])) + else: + field.send_keys(data['course_goal_1']) + + if 'course_goal_2' in data: + field = c.find_element_by_id('id_course_goal_2') + field.send_keys(data['course_goal_2']) + + if 'course_goal_3' in data: + button = c.find_element_by_id('btn-goal-add') + button.click() + field = c.find_element_by_id('id_course_goal_3') + field.send_keys(data['course_goal_3']) + + if 'course_goal_4' in data: + button.click() + field = c.find_element_by_id('id_course_goal_4') + field.send_keys(data['course_goal_4']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + + if screenshots: + self.save_screenshot('description_form', sequence=sequence_name) + + if 'title' in data: + field = c.find_element_by_id('id_title') + field.clear() + field.send_keys(data['title']) + + field = c.find_element_by_id('id_description') + if isinstance(data['description'], list): + field.send_keys(Keys.RETURN.join(data['description'])) + else: + field.send_keys(data['description']) + + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('summary_form', sequence=sequence_name) + + button = c.find_element_by_id('btn-form-back') + button.click() + self.wait_until_stale(c, button) + + for i in range(0, 11): + try: + button = c.find_element_by_id('btn-form-next') + button.click() + self.wait_until_stale(c, button) + except NoSuchElementException: + break + else: + self.fail('Too many sub forms') + + if 'internal_note' in data: + field = c.find_element_by_id('id_internal_note') + if isinstance(data['internal_note'], list): + field.send_keys(Keys.RETURN.join(data['internal_note'])) + else: + field.send_keys(data['internal_note']) + + button = c.find_element_by_id('btn-form-submit') + button.click() + self.wait_until_stale(c, button) + + if not auth: + if screenshots: + self.save_screenshot('user_and_event_created_set_password', sequence=sequence_name) + + field = c.find_element_by_id('id_new_password') + field.send_keys(TEST_PASSWORD) + field = c.find_element_by_id('id_new_password_repeat') + field.send_keys(TEST_PASSWORD) + field.send_keys(Keys.RETURN) + + self.wait_until_stale(c, field) + if screenshots: + self.save_screenshot('finished', sequence=sequence_name) + else: + if screenshots: + self.save_screenshot('event_list_after', sequence=sequence_name) + + def accept_event(self, title, screenshots=True): + c = self.selenium + + sequence_name = 'accept_event' + + c.get(self.complete_url(reverse('dav_events:root'))) + button = self.wait_on_presence(c, (By.ID, 'btn-events-list')) + button.click() + self.wait_until_stale(c, button) + + if screenshots: + self.save_screenshot('event_list_before', sequence=sequence_name) + + link = c.find_element_by_link_text(title) + link.click() + self.wait_until_stale(c, link) + + if screenshots: + self.save_screenshot('event_details', sequence=sequence_name) + + button = c.find_element_by_id('btn-accept') + button.click() + time.sleep(.5) + if screenshots: + self.save_screenshot('accept_modal', sequence=sequence_name) + + button = c.find_element_by_css_selector('#modal-accept-dialog a.btn-success') + button.click() + self.wait_until_stale(c, button) + if screenshots: + self.save_screenshot('accepted', sequence=sequence_name) + + link = c.find_element_by_link_text(ugettext('Veranstaltungsliste')) + link.click() + self.wait_until_stale(c, link) + if screenshots: + self.save_screenshot('event_list_after', sequence=sequence_name) + + def test_screenshots(self): + #self.quit_selenium = False + c = self.selenium + + self.get(reverse('dav_events:root')) + self.save_screenshot('home') + + self.create_event(TEST_EVENT_DATA_S, auth=False) + self.create_event(TEST_EVENT_DATA_W, auth=True) + + link = c.find_element_by_link_text(TEST_EVENT_DATA_W['title']) + link.click() + self.wait_until_stale(c, link) + self.save_screenshot('owner_event_details') + + self.logout() + + self.login(self.manager_s.username, TEST_PASSWORD) + self.accept_event(TEST_EVENT_DATA_S['title']) + self.logout() + + self.login(self.manager_super.username, TEST_PASSWORD) + self.accept_event(TEST_EVENT_DATA_W['title']) + self.logout()