From b8bf183ba9315458f7f9f5c1817dbf5343e8d0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Leichtfu=C3=9F?= Date: Sun, 13 Sep 2020 16:27:42 +0200 Subject: [PATCH] [tests] refactored live server tests to prevent race conditions --- tests/testapp/tests/test_live_filters.py | 220 ++++++++++++++--------- 1 file changed, 134 insertions(+), 86 deletions(-) diff --git a/tests/testapp/tests/test_live_filters.py b/tests/testapp/tests/test_live_filters.py index f48c3f1..d2b36ed 100644 --- a/tests/testapp/tests/test_live_filters.py +++ b/tests/testapp/tests/test_live_filters.py @@ -1,4 +1,5 @@ +import time from django.contrib.auth.models import User from django.urls import reverse @@ -10,17 +11,87 @@ from selenium.webdriver.support.ui import Select from ..management.commands.createtestdata import create_test_data +class FilterPage: + WAIT_FOR_RELOAD = 1 + URL_PATH = reverse('admin:testapp_modela_changelist') + + # ul-indexes + MULTISELECT_UL = 3 + MULTISELECT_RELATED_UL = 7 + + def __init__(self, selenium, base_url): + self.base_url = base_url + self.url = base_url + self.URL_PATH + self.selenium = selenium + self.current_url = self.selenium.current_url + + def login(self, client): + # login to selenium - using a cookie from the django test client + admin = User.objects.get(username='admin') + client.force_login(admin) + cookie = client.cookies['sessionid'] + #selenium will set cookie domain based on current page domain + self.selenium.get(self.base_url + '/admin/') + self.selenium.add_cookie({'name': 'sessionid', 'value': cookie.value, 'secure': False, 'path': '/'}) + #need to update page for logged in user + self.selenium.refresh() + + def get(self, url_query=str()): + return self.selenium.get(self.url + '?' + url_query) + + def wait_for_reload(self): + now = time.time() + while self.current_url == self.selenium.current_url: + self.selenium.refresh() + if time.time() - now < self.WAIT_FOR_RELOAD: + msg = "Could not reload live server page. Waited {} sec." + raise RuntimeError(msg.format(self.WAIT_FOR_RELOAD)) + else: + self.current_url = self.selenium.current_url + return True + + @property + def item_count(self): + return len(self.selenium.find_elements_by_xpath('//*[@id="result_list"]/tbody/tr')) + + @property + def url_query(self): + return self.selenium.current_url.split('?')[-1].replace('%2C', ',') + + def get_selected_li_count(self, ul): + return len(ul.find_elements_by_css_selector('li.selected')) + + def use_dropdown_filter(self, select_id, option): + select = Select(self.selenium.find_element_by_id(select_id)) + select.select_by_visible_text(option) + self.wait_for_reload() + return Select(self.selenium.find_element_by_id(select_id)) + + def use_multiselect_filter(self, ul_num, title): + ul_xpath = '//*[@id="changelist-filter"]/ul[{}]'.format(ul_num) + a_css = 'li a[title="{}"]'.format(title) + ul = self.selenium.find_element_by_xpath(ul_xpath) + ul.find_element_by_css_selector(a_css).click() + self.wait_for_reload() + return self.selenium.find_element_by_xpath(ul_xpath) + + def use_multiselect_dropdown_filter(self, field, options): + select = Select(self.selenium.find_element_by_id(field + '_select')) + for value in options: + select.select_by_value(value) + self.selenium.find_element_by_id(field + '_submit').click() + self.wait_for_reload() + return Select(self.selenium.find_element_by_id(field + '_select')) + + class LiveFilterTest(StaticLiveServerTestCase): @classmethod def setUpClass(cls): super().setUpClass() - options = Options() options.headless = True cls.selenium = WebDriver(options=options) - cls.selenium.implicitly_wait(10) cls.url_path = reverse('admin:testapp_modela_changelist') - create_test_data() @classmethod def tearDownClass(cls): @@ -28,105 +99,82 @@ class LiveFilterTest(StaticLiveServerTestCase): super().tearDownClass() def setUp(self): - # login to selenium - using a cookie from the django test client - admin = User.objects.get(username='admin') - self.client.force_login(admin) - cookie = self.client.cookies['sessionid'] - self.selenium.get(self.live_server_url + '/admin/') #selenium will set cookie domain based on current page domain - self.selenium.add_cookie({'name': 'sessionid', 'value': cookie.value, 'secure': False, 'path': '/'}) - self.selenium.refresh() #need to update page for logged in user + create_test_data() + self.page = FilterPage(self.selenium, self.live_server_url) + self.page.login(self.client) - def get_item_count(self): - return len(self.selenium.find_elements_by_xpath('//*[@id="result_list"]/tbody/tr')) - - def get_url_query(self): - return self.selenium.current_url.split('?')[-1].replace('%2C', ',') - - def get_selected_list_items(self, ul_num): - xpath = '//*[@id="changelist-filter"]/ul[{}]/li[@class="selected"]'.format(ul_num) - return self.selenium.find_elements_by_xpath(xpath) - - def use_dropdown_filter(self, select_id, option, url_query, item_count): - select = Select(self.selenium.find_element_by_id(select_id)) - select.select_by_visible_text(option) - self.selenium.refresh() - select = Select(self.selenium.find_element_by_id(select_id)) - self.assertEqual(self.get_item_count(), item_count) - self.assertEqual(self.get_url_query(), url_query) + def check_dropdown_filter(self, select_id, query_key, query_value, option, count): + select = self.page.use_dropdown_filter(select_id, option) + self.assertEqual(self.page.item_count, count) self.assertEqual(select.first_selected_option.text, option) + if option == 'All': + self.assertNotIn(query_key, self.page.url_query) + else: + self.assertIn(query_key + query_value, self.page.url_query) - def use_multiselect_link(self, ul_num, title, item_count, selected_count, url_query): - link = '//*[@id="changelist-filter"]/ul[{}]/li/a[@title="{}"]'.format(ul_num, title) - self.selenium.find_element_by_xpath(link).click() - self.selenium.refresh() - self.assertIn(url_query, self.get_url_query()) - self.assertEqual(self.get_item_count(), item_count) - self.assertEqual(len(self.get_selected_list_items(ul_num)), selected_count) + def test_01_dropdown_filter(self): + self.page.get() - def use_multiselect_dropdown_filter(self, field, options, url_param, count): - select = Select(self.selenium.find_element_by_id(field + '_select')) - for value in options: - select.select_by_value(value) - self.selenium.find_element_by_id(field + '_submit').click() - self.selenium.refresh() - select = Select(self.selenium.find_element_by_id(field + '_select')) - self.assertIn(url_param + '=' + ','.join(options), self.get_url_query()) - self.assertEqual(len(select.all_selected_options), len(options)) - self.assertEqual(self.get_item_count(), count) - select.deselect_all() + # check simple dropdown filter + select_id, query_key = 'dropdown-gt3_filter_select', 'dropdown_gt3=' + self.check_dropdown_filter(select_id, query_key, '2', '2', 9) + self.check_dropdown_filter(select_id, query_key, '', 'All', 36) - def test_01_check_filter(self): - self.selenium.get(self.live_server_url + self.url_path) + # Check choices dropdown filter: + select_id, query_key = 'choices-dropdown_filter_select', 'choices_dropdown__exact=' + self.check_dropdown_filter(select_id, query_key, '3', 'three', 4) + self.check_dropdown_filter(select_id, query_key, '', 'All', 36) - # Check the simple dropdown filter: - select_id = 'dropdown-gt3_filter_select' - url_query = 'dropdown_gt3=2' - self.use_dropdown_filter(select_id, '2', url_query, 9) - self.use_dropdown_filter(select_id, 'All', '', 36) + # Check related dropdown filter: + select_id, query_key = 'related-dropdown_filter_select', 'related_dropdown__id__exact=' + self.check_dropdown_filter(select_id, query_key, '9', 'ModelB 9', 1) + self.check_dropdown_filter(select_id, query_key, '', 'All', 36) - # Check the choices dropdown filter: - select_id = 'choices-dropdown_filter_select' - url_query = 'choices_dropdown__exact=3' - self.use_dropdown_filter(select_id, 'three', url_query, 4) - self.use_dropdown_filter(select_id, 'All', '', 36) + def check_multiselect_filter(self, ul_num, query_key, query_value, option, count, selected): + ul = self.page.use_multiselect_filter(ul_num, option) + self.assertEqual(self.page.item_count, count) + self.assertEqual(self.page.get_selected_li_count(ul), selected) + if option == 'All': + self.assertNotIn(query_key, self.page.url_query) + else: + self.assertIn(query_key + query_value, self.page.url_query) - # # Check the related dropdown filter: - select_id = 'related-dropdown_filter_select' - url_query = 'related_dropdown__id__exact=9' - self.use_dropdown_filter(select_id, 'ModelB 9', url_query, 1) - self.use_dropdown_filter(select_id, 'All', '', 36) + def test_02_multiselect_filter(self): + # start with an already filtered changelist + self.page.get('dropdown_gt3=2') - # Start tests with activated dropdown_gt3 filter - self.selenium.get(self.live_server_url + self.url_path + '?dropdown_gt3=2') - - # Check the simple multiselect filter - self.use_multiselect_link(3, '4', 2, 1, 'multiselect__in=4') - self.use_multiselect_link(3, '3', 3, 2, 'multiselect__in=4,3') - self.use_multiselect_link(3, '2', 5, 3, 'multiselect__in=4,3,2') - self.use_multiselect_link(3, '3', 4, 2, 'multiselect__in=4,2') - self.use_multiselect_link(3, 'All', 9, 1, '') - self.assertNotIn('multiselect__in', self.get_url_query()) + # check simple multiselect filter + ul_num, query_key = self.page.MULTISELECT_UL, 'multiselect__in=' + self.check_multiselect_filter(ul_num, query_key, '4', '4', 2, 1) + self.check_multiselect_filter(ul_num, query_key, '4,3', '3', 3, 2) + self.check_multiselect_filter(ul_num, query_key, '4,3,2', '2', 5, 3) + self.check_multiselect_filter(ul_num, query_key, '', 'All', 9, 1) # check the multiselect related filter - self.use_multiselect_link(7, 'ModelB 34', 1, 1, 'multiselect_related__id__in=34') - self.use_multiselect_link(7, 'ModelB 30', 2, 2, 'multiselect_related__id__in=34,30') - self.use_multiselect_link(7, 'ModelB 26', 3, 3, 'multiselect_related__id__in=34,30,26') - self.use_multiselect_link(7, 'ModelB 34', 2, 2, 'multiselect_related__id__in=30,26') - self.use_multiselect_link(7, 'All', 9, 1, '') - self.assertNotIn('multiselect_related__id__in', self.get_url_query()) + ul_num, query_key = self.page.MULTISELECT_RELATED_UL, 'multiselect_related__id__in=' + self.check_multiselect_filter(ul_num, query_key, '34', 'ModelB 34', 1, 1) + self.check_multiselect_filter(ul_num, query_key, '34,30', 'ModelB 30', 2, 2) + self.check_multiselect_filter(ul_num, query_key, '34,30,26', 'ModelB 26', 3, 3) + self.check_multiselect_filter(ul_num, query_key, '30,26', 'ModelB 34', 2, 2) + self.check_multiselect_filter(ul_num, query_key, '', 'All', 9, 1) - # Start tests without any filter. - self.selenium.get(self.live_server_url + self.url_path) + def check_multiselect_dropdown_filter(self, field, options, query_key, count): + select = self.page.use_multiselect_dropdown_filter(field, options) + self.assertEqual(len(select.all_selected_options), len(options)) + self.assertEqual(self.page.item_count, count) + self.assertIn(query_key + ','.join(options), self.page.url_query) + select.deselect_all() + + def test_03_multiselect_dropdown_filter(self): + self.page.get() # check multiselect-dropdown - field = 'multiselect-dropdown' - url_param = 'multiselect_dropdown__in' + field, query_key = 'multiselect-dropdown', 'multiselect_dropdown__in=' options = [str(i) for i in range(2, 5)] - self.use_multiselect_dropdown_filter(field, options, url_param, 18) + self.check_multiselect_dropdown_filter(field, options, query_key, 18) # check multiselect-related-dropdown # (multiselect-dropdown filter is still effectual) - field = 'multiselect-related-dropdown' - url_param = 'multiselect_related_dropdown__id__in' + field, query_key = 'multiselect-related-dropdown', 'multiselect_related_dropdown__id__in=' options = [str(i) for i in range(1, 9)] - self.use_multiselect_dropdown_filter(field, options, url_param, 4) + self.check_multiselect_dropdown_filter(field, options, query_key, 4)