From 677408ea1820a75e8f46184bda4c5ee12061469b Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Sun, 5 Nov 2023 01:38:54 -0400 Subject: [PATCH] Condense repeated selenium logic into mixin class --- .../mixins/core_mixin.py | 34 +++-- .../mixins/live_server_mixin.py | 131 ++++++++++++++++++ .../channels_live_server_test_case.py | 127 ++--------------- .../test_cases/integration_test_case.py | 1 + .../test_cases/live_server_test_case.py | 128 ++--------------- 5 files changed, 177 insertions(+), 244 deletions(-) diff --git a/django_expanded_test_cases/mixins/core_mixin.py b/django_expanded_test_cases/mixins/core_mixin.py index f6815cf..9d106c7 100644 --- a/django_expanded_test_cases/mixins/core_mixin.py +++ b/django_expanded_test_cases/mixins/core_mixin.py @@ -77,7 +77,7 @@ class CoreTestCaseMixin: # region Class Functions @classmethod - def set_up_class(cls, debug_print=None): + def set_up_class(cls, *args, debug_print=None, **kwargs): """ Acts as the equivalent of the UnitTesting "setUpClass()" function. @@ -96,7 +96,7 @@ class CoreTestCaseMixin: cls._site_root_url = None @classmethod - def set_up_test_data(cls, extra_usergen_kwargs=None): + def set_up_test_data(cls, *args, extra_usergen_kwargs=None, **kwargs): """ Acts as the equivalent of the UnitTesting "setUpTestData()" function. @@ -118,6 +118,26 @@ class CoreTestCaseMixin: cls._auto_generate_test_users(extra_usergen_kwargs=extra_usergen_kwargs) + def set_up(self, *args, **kwargs): + pass + + def sub_test(self, *args, **kwargs): + """ + Acts as the equivalent of the UnitTesting "subtTest()" function. + + However, since this is not inheriting from a given TestCase, calling the literal function + here would override instead. + """ + # Reset display error, in case multiple subtests run and fail in a given test. + self._error_displayed = False + + @classmethod + def tear_down_class(cls, *args, **kwargs): + pass + + def tear_down(self, *args, **kwargs): + pass + @classmethod def _auto_generate_test_users(cls, extra_usergen_kwargs=None): """Logic to automatically generate test users. @@ -240,16 +260,6 @@ class CoreTestCaseMixin: 'Must be one of: ["anonymous", "relaxed", "strict"].' ) - def sub_test(self): - """ - Acts as the equivalent of the UnitTesting "subtTest()" function. - - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. - """ - # Reset display error, in case multiple subtests run and fail in a given test. - self._error_displayed = False - def _debug_print(self, *args, fore='', back='', style='', **kwargs): """Prints or suppresses output, based on DJANGO_EXPANDED_TESTCASES_DEBUG_PRINT settings variable. diff --git a/django_expanded_test_cases/mixins/live_server_mixin.py b/django_expanded_test_cases/mixins/live_server_mixin.py index c503ba3..47d5128 100644 --- a/django_expanded_test_cases/mixins/live_server_mixin.py +++ b/django_expanded_test_cases/mixins/live_server_mixin.py @@ -11,6 +11,8 @@ from textwrap import dedent # Third-Party Imports. from selenium import webdriver +from selenium.webdriver.chrome.service import Service as ChromeService +from selenium.webdriver.firefox.service import Service as FireFoxService # Internal Imports. from django_expanded_test_cases.constants import ( @@ -19,7 +21,12 @@ from django_expanded_test_cases.constants import ( ETC_RESPONSE_DEBUG_CONTENT_COLOR, ETC_OUTPUT_EMPHASIS_COLOR, + ETC_SELENIUM_BROWSER, + ETC_SELENIUM_HEADLESS, + ETC_SELENIUM_DISABLE_CACHE, ETC_SELENIUM_DEBUG_PORT_START_VALUE, + ETC_SELENIUM_WINDOW_POSITIONS, + ETC_SELENIUM_EXTRA_BROWSER_OPTIONS, ETC_SELENIUM_PAGE_TIMEOUT_DEFAULT, ETC_SELENIUM_IMPLICIT_WAIT_DEFAULT, ) @@ -37,6 +44,130 @@ SELENIUM_DEBUG_PORT = ETC_SELENIUM_DEBUG_PORT_START_VALUE class LiveServerMixin(ResponseTestCaseMixin): """Universal logic for all selenium LiveServer test cases.""" + @classmethod + def set_up_class(cls, debug_print=None): + """ + Acts as the equivalent of the UnitTesting "setUpClass()" function. + + However, since this is not inheriting from a given TestCase, calling the literal function + here would override instead. + + :param debug_print: Optional bool that indicates if debug output should print to console. + Param overrides setting value if both param and setting are set. + """ + # Call CoreMixin setup logic. + super().set_up_class(debug_print=debug_print) + + # Populate some initial values. + cls._driver_set = [] + cls._options = None + + # Import/Initialize some values based on chosen testing browser. Default to chrome. + cls._browser = str(ETC_SELENIUM_BROWSER).lower() + + if cls._browser in ['chrome', 'chromium']: + # Setup for Chrome/Chromium browser. + + # Setup browser driver to launch browser with. + try: + # Attempt driver auto-install, if webdriver_manager package is present. + cls._service = ChromeService() + + except ModuleNotFoundError: + # Fall back to manual installation handling. + + if cls._browser == 'chrome': + # For Chrome. + cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') + + if cls._browser == 'chromium': + # For Chromium. + cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') + + # Set required chrome options. + chromeOptions = webdriver.ChromeOptions() + # Disable any existing extensions on local chrome setup, for consistent test runs across machines. + chromeOptions.add_argument('--disable-extensions') + + # Add any user-provided options. + if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: + for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: + chromeOptions.add_argument(browser_option) + + # TODO: Document these? Seemed to come up a lot in googling errors and whatnot. + # # Avoid possible error in certain development environments about resource limits. + # # Error is along the lines of "DevToolsActivePort file doesn't exist". + # # See https://stackoverflow.com/a/69175552 + # chromeOptions.add_argument('--disable-dev-shm-using') + # # Avoid possible error when many drivers are opened. + # # See https://stackoverflow.com/a/56638103 + # chromeOptions.add_argument("--remote-debugging-port=9222") + + # Save options. + cls._options = chromeOptions + + # Everything else should handle the same for both. + cls._browser = 'chrome' + + elif cls._browser == 'firefox': + # Setup for Firefox browser. + + # Setup browser driver to launch browser with. + try: + # Attempt driver auto-install, if webdriver_manager package is present. + cls._service = FireFoxService() + + except ModuleNotFoundError: + # Fall back to manual installation handling. + cls._service = FireFoxService(executable_path='/usr/bin/geckodriver') + + # Set required chrome options. + firefoxOptions = webdriver.FirefoxOptions() + + # Add any user-provided options. + if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: + for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: + firefoxOptions.add_argument(browser_option) + + # Save options. + cls._options = firefoxOptions + + else: + raise ValueError('Unknown browser "{0}".'.format(cls._browser)) + + # Add universal options based on project settings. + if ETC_SELENIUM_HEADLESS: + cls._options.add_argument('headless') + if ETC_SELENIUM_DISABLE_CACHE: + cls._options.add_argument('disable-application-cache') + + # Handle window position values. + cls._window_positions = None + if ETC_SELENIUM_WINDOW_POSITIONS: + window_positions = list(ETC_SELENIUM_WINDOW_POSITIONS) + if len(window_positions) > 0: + cls._window_positions = window_positions + cls._window_position_index = 0 + + # Create initial testing driver. + cls.driver = cls.create_driver(cls) + + def set_up(self): + self._error_displayed = False + + def sub_test(self, *args, **kwargs): + # Call parent logic. + super().sub_test(*args, **kwargs) + + @classmethod + def tear_down_class(cls): + # Close all remaining driver instances for class. + while len(cls._driver_set) > 0: + cls.close_driver(cls, cls._driver_set[0]) + + # Call parent teardown logic. + super().tear_down_class() + # region Utility Functions def create_driver(self, switch_window=True): diff --git a/django_expanded_test_cases/test_cases/channels_live_server_test_case.py b/django_expanded_test_cases/test_cases/channels_live_server_test_case.py index 61d52d6..b82e2bd 100644 --- a/django_expanded_test_cases/test_cases/channels_live_server_test_case.py +++ b/django_expanded_test_cases/test_cases/channels_live_server_test_case.py @@ -8,19 +8,9 @@ Tends to take longer to test. So consider using IntegrationTestCase instead, whe # System Imports. # Third-Party Imports. -from selenium import webdriver -from selenium.webdriver.chrome.service import Service as ChromeService -from selenium.webdriver.firefox.service import Service as FireFoxService from channels.testing import ChannelsLiveServerTestCase as DjangoChannelsLiveServerTestCase # Internal Imports. -from django_expanded_test_cases.constants import ( - ETC_SELENIUM_BROWSER, - ETC_SELENIUM_HEADLESS, - ETC_SELENIUM_DISABLE_CACHE, - ETC_SELENIUM_WINDOW_POSITIONS, - ETC_SELENIUM_EXTRA_BROWSER_OPTIONS, -) from django_expanded_test_cases.mixins.live_server_mixin import LiveServerMixin @@ -32,107 +22,22 @@ class ChannelsLiveServerTestCase(DjangoChannelsLiveServerTestCase, LiveServerMix # Run parent setup logic. super().setUpClass() - # Also call CoreMixin setup logic. + # Also call Mixin setup logic. cls.set_up_class(debug_print=debug_print) - # Populate some initial values. - cls._driver_set = [] - cls._options = None - - # Import/Initialize some values based on chosen testing browser. Default to chrome. - cls._browser = str(ETC_SELENIUM_BROWSER).lower() - - if cls._browser in ['chrome', 'chromium']: - # Setup for Chrome/Chromium browser. - - # Setup browser driver to launch browser with. - try: - # Attempt driver auto-install, if webdriver_manager package is present. - cls._service = ChromeService() - - except ModuleNotFoundError: - # Fall back to manual installation handling. - - if cls._browser == 'chrome': - # For Chrome. - cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') - - if cls._browser == 'chromium': - # For Chromium. - cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') - - # Set required chrome options. - chromeOptions = webdriver.ChromeOptions() - # Disable any existing extensions on local chrome setup, for consistent test runs across machines. - chromeOptions.add_argument('--disable-extensions') - - # Add any user-provided options. - if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - chromeOptions.add_argument(browser_option) - - # TODO: Document these? Seemed to come up a lot in googling errors and whatnot. - # # Avoid possible error in certain development environments about resource limits. - # # Error is along the lines of "DevToolsActivePort file doesn't exist". - # # See https://stackoverflow.com/a/69175552 - # chromeOptions.add_argument('--disable-dev-shm-using') - # # Avoid possible error when many drivers are opened. - # # See https://stackoverflow.com/a/56638103 - # chromeOptions.add_argument("--remote-debugging-port=9222") - - # Save options. - cls._options = chromeOptions - - # Everything else should handle the same for both. - cls._browser = 'chrome' - - elif cls._browser == 'firefox': - # Setup for Firefox browser. - - # Setup browser driver to launch browser with. - try: - # Attempt driver auto-install, if webdriver_manager package is present. - cls._service = FireFoxService() - - except ModuleNotFoundError: - # Fall back to manual installation handling. - cls._service = FireFoxService(executable_path='/usr/bin/geckodriver') - - # Set required chrome options. - firefoxOptions = webdriver.FirefoxOptions() - - # Add any user-provided options. - if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - firefoxOptions.add_argument(browser_option) - - # Save options. - cls._options = firefoxOptions - - else: - raise ValueError('Unknown browser "{0}".'.format(cls._browser)) - - # Add universal options based on project settings. - if ETC_SELENIUM_HEADLESS: - cls._options.add_argument('headless') - if ETC_SELENIUM_DISABLE_CACHE: - cls._options.add_argument('disable-application-cache') - - # Handle window position values. - cls._window_positions = None - if ETC_SELENIUM_WINDOW_POSITIONS: - window_positions = list(ETC_SELENIUM_WINDOW_POSITIONS) - if len(window_positions) > 0: - cls._window_positions = window_positions - cls._window_position_index = 0 - - # Create initial testing driver. - cls.driver = cls.create_driver(cls) + @classmethod + def setUpTestData(cls): + """""" + # Initialize default data models. + cls.set_up_test_data() def setUp(self): # Run parent setup logic. super().setUp() + # Also call Mixin setup logic. + self.set_up() + self._error_displayed = False def subTest(self, *args, **kwargs): @@ -144,22 +49,12 @@ class ChannelsLiveServerTestCase(DjangoChannelsLiveServerTestCase, LiveServerMix @classmethod def tearDownClass(cls): - # Close all remaining driver instances for class. - while len(cls._driver_set) > 0: - cls.close_driver(cls, cls._driver_set[0]) + # Call Mixin setup logic. + cls.tear_down_class() # Call parent teardown logic. super().tearDownClass() - def tearDown(self): - # TODO: Below seems probably unnecessary? Research more. - # # Close all remaining window instances for test. - # # (Or at least attempt to for default driver for test). - # self.close_all_windows(self.driver) - - # Call parent teardown logic. - super().tearDown() - # Define acceptable imports on file. __all__ = [ diff --git a/django_expanded_test_cases/test_cases/integration_test_case.py b/django_expanded_test_cases/test_cases/integration_test_case.py index e9b7d42..b4e8dc4 100644 --- a/django_expanded_test_cases/test_cases/integration_test_case.py +++ b/django_expanded_test_cases/test_cases/integration_test_case.py @@ -40,6 +40,7 @@ from django_expanded_test_cases.mixins import ResponseTestCaseMixin class IntegrationTestCase(BaseTestCase, ResponseTestCaseMixin): """Testing functionality for views and other multi-part components.""" + @classmethod def setUpClass(cls, *args, debug_print=None, **kwargs): # Run parent setup logic. diff --git a/django_expanded_test_cases/test_cases/live_server_test_case.py b/django_expanded_test_cases/test_cases/live_server_test_case.py index 80e8cc2..3558530 100644 --- a/django_expanded_test_cases/test_cases/live_server_test_case.py +++ b/django_expanded_test_cases/test_cases/live_server_test_case.py @@ -9,18 +9,9 @@ Tends to take longer to test. So consider using IntegrationTestCase instead, whe # Third-Party Imports. from django.test import LiveServerTestCase as DjangoLiveServerTestCase -from selenium import webdriver -from selenium.webdriver.chrome.service import Service as ChromeService -from selenium.webdriver.firefox.service import Service as FireFoxService + # Internal Imports. -from django_expanded_test_cases.constants import ( - ETC_SELENIUM_BROWSER, - ETC_SELENIUM_HEADLESS, - ETC_SELENIUM_DISABLE_CACHE, - ETC_SELENIUM_WINDOW_POSITIONS, - ETC_SELENIUM_EXTRA_BROWSER_OPTIONS, -) from django_expanded_test_cases.mixins.live_server_mixin import LiveServerMixin @@ -32,107 +23,22 @@ class LiveServerTestCase(DjangoLiveServerTestCase, LiveServerMixin): # Run parent setup logic. super().setUpClass() - # Also call CoreMixin setup logic. + # Also call Mixin setup logic. cls.set_up_class(debug_print=debug_print) - # Populate some initial values. - cls._driver_set = [] - cls._options = None - - # Import/Initialize some values based on chosen testing browser. Default to chrome. - cls._browser = str(ETC_SELENIUM_BROWSER).lower() - - if cls._browser in ['chrome', 'chromium']: - # Setup for Chrome/Chromium browser. - - # Setup browser driver to launch browser with. - try: - # Attempt driver auto-install, if webdriver_manager package is present. - cls._service = ChromeService() - - except ModuleNotFoundError: - # Fall back to manual installation handling. - - if cls._browser == 'chrome': - # For Chrome. - cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') - - if cls._browser == 'chromium': - # For Chromium. - cls._service = ChromeService(executable_path='/usr/local/share/chromedriver') - - # Set required chrome options. - chromeOptions = webdriver.ChromeOptions() - # Disable any existing extensions on local chrome setup, for consistent test runs across machines. - chromeOptions.add_argument('--disable-extensions') - - # Add any user-provided options. - if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - chromeOptions.add_argument(browser_option) - - # TODO: Document these? Seemed to come up a lot in googling errors and whatnot. - # # Avoid possible error in certain development environments about resource limits. - # # Error is along the lines of "DevToolsActivePort file doesn't exist". - # # See https://stackoverflow.com/a/69175552 - # chromeOptions.add_argument('--disable-dev-shm-using') - # # Avoid possible error when many drivers are opened. - # # See https://stackoverflow.com/a/56638103 - # chromeOptions.add_argument("--remote-debugging-port=9222") - - # Save options. - cls._options = chromeOptions - - # Everything else should handle the same for both. - cls._browser = 'chrome' - - elif cls._browser == 'firefox': - # Setup for Firefox browser. - - # Setup browser driver to launch browser with. - try: - # Attempt driver auto-install, if webdriver_manager package is present. - cls._service = FireFoxService() - - except ModuleNotFoundError: - # Fall back to manual installation handling. - cls._service = FireFoxService(executable_path='/usr/bin/geckodriver') - - # Set required chrome options. - firefoxOptions = webdriver.FirefoxOptions() - - # Add any user-provided options. - if ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - for browser_option in ETC_SELENIUM_EXTRA_BROWSER_OPTIONS: - firefoxOptions.add_argument(browser_option) - - # Save options. - cls._options = firefoxOptions - - else: - raise ValueError('Unknown browser "{0}".'.format(cls._browser)) - - # Add universal options based on project settings. - if ETC_SELENIUM_HEADLESS: - cls._options.add_argument('headless') - if ETC_SELENIUM_DISABLE_CACHE: - cls._options.add_argument('disable-application-cache') - - # Handle window position values. - cls._window_positions = None - if ETC_SELENIUM_WINDOW_POSITIONS: - window_positions = list(ETC_SELENIUM_WINDOW_POSITIONS) - if len(window_positions) > 0: - cls._window_positions = window_positions - cls._window_position_index = 0 - - # Create initial testing driver. - cls.driver = cls.create_driver(cls) + @classmethod + def setUpTestData(cls): + """""" + # Initialize default data models. + cls.set_up_test_data() def setUp(self): # Run parent setup logic. super().setUp() + # Also call Mixin setup logic. + self.set_up() + self._error_displayed = False def subTest(self, *args, **kwargs): @@ -144,22 +50,12 @@ class LiveServerTestCase(DjangoLiveServerTestCase, LiveServerMixin): @classmethod def tearDownClass(cls): - # Close all remaining driver instances for class. - while len(cls._driver_set) > 0: - cls.close_driver(cls, cls._driver_set[0]) + # Call Mixin setup logic. + cls.tear_down_class() # Call parent teardown logic. super().tearDownClass() - def tearDown(self): - # TODO: Below seems probably unnecessary? Research more. - # # Close all remaining window instances for test. - # # (Or at least attempt to for default driver for test). - # self.close_all_windows(self.driver) - - # Call parent teardown logic. - super().tearDown() - # Define acceptable imports on file. __all__ = [ -- GitLab