Source code for webdriver_test_tools.project.cmd.run

import textwrap
import unittest
from argparse import RawTextHelpFormatter

from webdriver_test_tools import config
from webdriver_test_tools.common import cmd
from webdriver_test_tools.project import test_factory
from webdriver_test_tools.project.test_loader import load_project_tests
from webdriver_test_tools.project.cmd.common import parse_test_args
from webdriver_test_tools.testcase import Browsers


[docs]def add_run_subparser(subparsers, config_module=None, parents=[], formatter_class=RawTextHelpFormatter): """Add subparser for the ``<test_package> run`` command :param subparsers: ``argparse._SubParsersAction`` object for the test package ArgumentParser (i.e. the object returned by the ``add_subparsers()`` method) :param config_module: (Optional) The module object for ``<test_project>.config``. Will use :mod:`webdriver_test_tools.config` if not specified if unspecified :param parents: (Default: ``[]``) Parent parsers for the run subparser :param formatter_class: (Default: ``argparse.RawTextHelpFormatter``) Class to use for the ``formatter_class`` parameter :return: ``argparse.ArgumentParser`` object for the newly added ``run`` subparser """ # Get browser config classes browser_config, browserstack_config = get_browser_config_classes(config_module) run_description = 'Run the test suite' run_help = run_description run_parser = subparsers.add_parser( 'run', description=run_description, help=run_help, parents=parents, # TODO: always use test_parent_parser? formatter_class=formatter_class, add_help=False, epilog=cmd.argparse.ARGPARSE_EPILOG ) # Browser Arguments group = run_parser.add_argument_group('Browser Arguments') if browserstack_config.ENABLE: browser_choices = list(set(browser_config.get_browser_names()) | set(browserstack_config.get_browser_names())) else: browser_choices = list(browser_config.get_browser_names()) browser_options_help = _format_browser_choices(browser_config, browserstack_config) browser_help = 'Run tests only in the specified browsers.' + browser_options_help group.add_argument('-b', '--browser', nargs='+', choices=browser_choices, metavar='<browser>', help=browser_help) headless_options_help = _format_headless_browsers(browser_config) headless_help = 'Run tests using headless browsers.' + headless_options_help group.add_argument('-H', '--headless', action='store_true', help=headless_help) # BrowserStack arguments if browserstack_config.ENABLE: group = run_parser.add_argument_group('BrowserStack') browserstack_help = 'Run tests on BrowserStack instead of locally' group.add_argument('-B', '--browserstack', action='store_true', help=browserstack_help) # Build name build_help = 'Set the build name for the group of tests' group.add_argument('--build', metavar='<name>', help=build_help) # Enabling/disabling video recording video_help = 'Record video of tests' group.add_argument('--video', dest='video', action='store_true', help=video_help) no_video_help = 'Disable video recording' group.add_argument('--no-video', dest='video', action='store_false', help=no_video_help) # Set default to the value configured in browserstack_config (or True if not configured) # TODO: move to BrowserStackConfig class method? video_default = True if 'browserstack.video' not in browserstack_config.BS_CAPABILITIES else \ browserstack_config.BS_CAPABILITIES['browserstack.video'] run_parser.set_defaults(video=video_default) # Output Arguments group = run_parser.add_argument_group('Output Options') verbosity_help = textwrap.dedent('''\ 0 - Final results only 1 - Final results and progress indicator 2 - Full output ''') group.add_argument('-v', '--verbosity', type=int, choices=[0, 1, 2], metavar='<level>', help=verbosity_help) return run_parser
# Help text formatting functions def _format_browser_choices(browser_config, browserstack_config): """Format the help string for browser choices If BrowserStack is disabled or doesn't have any browsers enabled that aren't also in the local browser config, output string will have the following format: .. code:: python '\\nOptions: {browser0,browser1}' If BrowserStack is enabled and there are different browsers enabled for local and BrowserStack, output string will have the following format: .. code:: python ''' Local & BrowserStack: {browser0,browser1} Local Only: {browser2,browser3} BrowserStack Only: {browser4,browser5} ''' :param browser_config: BrowserConfig class :param browserstack_config: BrowserStackConfig class :return: Formatted help string for browser options """ options = '' local_set = set(browser_config.get_browser_names()) browserstack_set = set(browserstack_config.get_browser_names()) # If browserstack is disabled or there's no difference in browsers if not browserstack_config.ENABLE or local_set == browserstack_set: options = '\nOptions: ' + _browser_list_string(browser_config.get_browser_names()) # Else if there is some difference between enabled sets else: both_set = local_set.intersection(browserstack_set) local_only = local_set - browserstack_set browserstack_only = browserstack_set - local_set if both_set: options += '\nLocal & BrowserStack:\n' + cmd.INDENT + _browser_list_string(list(both_set)) if local_only: options += '\nLocal Only:\n' + cmd.INDENT + _browser_list_string(list(local_only)) if browserstack_only: options += '\nBrowserStack Only:\n' + cmd.INDENT + _browser_list_string(list(browserstack_only)) return options def _format_headless_browsers(browser_config): """Format the help string for compatible browsers in ``--headless`` argument help string :param browser_config: :class:`BrowserConfig <webdriver_test_tools.config.browser.BrowserConfig>` class :return: Formatted help string for browser options """ enabled_browsers = browser_config.get_browser_names() browser_names = [ browser_class.SHORT_NAME for browser_class in Browsers.HEADLESS_COMPATIBLE if browser_class.SHORT_NAME in enabled_browsers ] return '\nCompatible Browsers:\n' + cmd.INDENT + _browser_list_string(browser_names) def _browser_list_string(browser_names): """Takes a list of browser names and returns a string representation in the format '{browser0,browser1,browser2}' :param browser_names: List of browser names :return: String representation of the browser name list """ return '{{{}}}'.format(','.join(browser_names)) # Config module helper functions
[docs]def get_browser_config_classes(config_module): """Get the ``BrowserConfig`` and ``BrowserStackConfig`` classes from a project. :param config_module: The module object for ``<test_project>.config`` :return: Tuple containing: - ``config_module.BrowserConfig`` (or :class:`webdriver_test_tools.config.BrowserConfig <webdriver_test_tools.config.browser.BrowserConfig>` if not present in ``config_module``) - ``config_module.BrowserStackConfig`` (or :class:`webdriver_test_tools.config.BrowserStackConfig <webdriver_test_tools.config.browser.BrowserStackConfig>` if not present in ``config_module``) """ if config_module is None: config_module = config # TODO: set config_module.<Class> instead so this only needs to be called once? # All projects should have a BrowserConfig class, but just in case browser_config = config_module.BrowserConfig if 'BrowserConfig' in dir(config_module) else config.BrowserConfig # Older projects may not have the BrowserStackConfig class browserstack_config = config_module.BrowserStackConfig if 'BrowserStackConfig' in dir(config_module) else config.BrowserStackConfig return browser_config, browserstack_config
# Argument parsing functions
[docs]def parse_run_args(tests_module, config_module, args): """Parse arguments and run the 'run' command :param tests_module: The module object for ``<test_project>.tests`` :param config_module: The module object for ``<test_project>.config`` :param args: The namespace returned by parser.parse_args() :return: Exit code, 0 if tests were successful, 1 otherwise """ kwargs = parse_test_args(args) # Get browser config classes browser_config, browserstack_config = get_browser_config_classes(config_module) # Parse browserstack args if 'browserstack' in dir(args): kwargs['browserstack'] = args.browserstack # Update browserstack_config attributes based on CLI overrides if args.browserstack: browserstack_config.update_configurations(build=args.build, video=args.video) # Parse --headless and --verbosity args kwargs.update({ 'headless': args.headless, 'verbosity': args.verbosity, }) # Handle --browser args browser_config_class = browserstack_config if 'browserstack' in kwargs and kwargs['browserstack'] else browser_config kwargs['browser_classes'] = browser_config_class.get_browser_classes(args.browser) # Run tests using parsed args exit_code = run_tests(tests_module, config_module, **kwargs) return exit_code
[docs]def run_tests(tests_module, config_module, browser_classes=None, test_class_map=None, skip_class_map=None, test_module_names=None, skip_module_names=None, browserstack=False, headless=False, verbosity=None): """Run tests using parsed args and project modules :param tests_module: The module object for ``<test_project>.tests`` :param config_module: The module object for ``<test_project>.config`` or :mod:`webdriver_test_tools.config` if not specified :param browser_classes: (Optional) List of browser test classes from parsed arg for ``--browser`` command line argument :param test_class_map: (Optional) Result of passing parsed arg for ``--test`` command line argument to :func:`parse_test_names()` :param skip_class_map: (Optional) Result of passing parsed arg for ``--skip`` command line argument to :func:`parse_test_names()` :param test_module_names: (Optional) Parsed arg for ``--module`` command line argument :param skip_module_names: (Optional) Parsed arg for ``--skip-module`` command line argument :param browserstack: (Default = False) If True, generated test cases should run on BrowserStack :param headless: (Default = False) If True, configure driver to run tests in a headless browser. Tests will only be generated for drivers that support running headless browsers :param verbosity: (Optional) Output verbosity level for the test runner. :return: Exit code, 0 if tests were successful, 1 otherwise """ # Enable graceful Ctrl+C handling unittest.installHandler() # Load WebDriverTestCase subclasses from project tests tests = load_project_tests(tests_module, test_module_names, skip_module_names, test_class_map, skip_class_map) # Generate browser test cases from the loaded WebDriverTestCase classes browser_test_suite = test_factory.generate_browser_test_suite( tests, browser_classes, test_class_map, skip_class_map, config_module, browserstack, headless ) # Get configured test runner and run suite test_runner = config_module.TestSuiteConfig.get_runner(verbosity=verbosity) # Capture result for exit code # TODO see if there's anything else useful you can do with the result? result = test_runner.run(browser_test_suite) # Link to BrowserStack automation dashboard if applicable if browserstack: print('', 'See BrowserStack Automation Dashboard for Detailed Results:', 'https://automate.browserstack.com', sep='\n') # Propagate exit code return 0 if result.wasSuccessful() else 1