import argparse
import allure
import allure_commons
import os
from allure_commons.types import LabelType, Severity
from allure_commons.logger import AllureFileLogger
from allure_commons.utils import get_testplan
from allure_pytest.utils import allure_label, allure_labels, allure_full_name
from allure_pytest.helper import AllureTestHelper, AllureTitleHelper
from allure_pytest.listener import AllureListener
from allure_pytest.utils import ALLURE_DESCRIPTION_MARK, ALLURE_DESCRIPTION_HTML_MARK
from allure_pytest.utils import ALLURE_LABEL_MARK, ALLURE_LINK_MARK
def pytest_addoption(parser):
parser.getgroup("reporting").addoption('--alluredir',
action="store",
dest="allure_report_dir",
metavar="DIR",
default=None,
help="Generate Allure report in the specified directory (may not exist)")
parser.getgroup("reporting").addoption('--clean-alluredir',
action="store_true",
dest="clean_alluredir",
help="Clean alluredir folder if it exists")
parser.getgroup("reporting").addoption('--allure-no-capture',
action="store_false",
dest="attach_capture",
help="Do not attach pytest captured logging/stdout/stderr to report")
parser.getgroup("reporting").addoption('--inversion',
action="store",
dest="inversion",
default=False,
help="Run tests not in testplan")
def label_type(type_name, legal_values=set()):
def a_label_type(string):
atoms = set(string.split(','))
if type_name is LabelType.SEVERITY:
if not atoms <= legal_values:
raise argparse.ArgumentTypeError('Illegal {} values: {}, only [{}] are allowed'.format(
type_name,
', '.join(atoms - legal_values),
', '.join(legal_values)
))
return set((type_name, allure.severity_level(atom)) for atom in atoms)
return set((type_name, atom) for atom in atoms)
return a_label_type
severities = [x.value for x in list(allure.severity_level)]
formatted_severities = ', '.join(severities)
parser.getgroup("general").addoption('--allure-severities',
action="store",
dest="allure_severities",
metavar="SEVERITIES_SET",
default={},
type=label_type(LabelType.SEVERITY, legal_values=set(severities)),
help=f"""Comma-separated list of severity names.
Tests only with these severities will be run.
Possible values are: {formatted_severities}.""")
parser.getgroup("general").addoption('--allure-epics',
action="store",
dest="allure_epics",
metavar="EPICS_SET",
default={},
type=label_type(LabelType.EPIC),
help="""Comma-separated list of epic names.
Run tests that have at least one of the specified feature labels.""")
parser.getgroup("general").addoption('--allure-features',
action="store",
dest="allure_features",
metavar="FEATURES_SET",
default={},
type=label_type(LabelType.FEATURE),
help="""Comma-separated list of feature names.
Run tests that have at least one of the specified feature labels.""")
parser.getgroup("general").addoption('--allure-stories',
action="store",
dest="allure_stories",
metavar="STORIES_SET",
default={},
type=label_type(LabelType.STORY),
help="""Comma-separated list of story names.
Run tests that have at least one of the specified story labels.""")
parser.getgroup("general").addoption('--allure-ids',
action="store",
dest="allure_ids",
metavar="IDS_SET",
default={},
type=label_type(LabelType.ID),
help="""Comma-separated list of IDs.
Run tests that have at least one of the specified id labels.""")
def cf_type(string):
type_name, values = string.split("=", 1)
atoms = set(values.split(","))
return [(type_name, atom) for atom in atoms]
parser.getgroup("general").addoption('--allure-label',
action="append",
dest="allure_labels",
metavar="LABELS_SET",
default=[],
type=cf_type,
help="""List of labels to run in format label_name=value1,value2.
"Run tests that have at least one of the specified labels.""")
def link_pattern(string):
pattern = string.split(':', 1)
if not pattern[0]:
raise argparse.ArgumentTypeError('Link type is mandatory.')
if len(pattern) != 2:
raise argparse.ArgumentTypeError('Link pattern is mandatory')
return pattern
parser.getgroup("general").addoption('--allure-link-pattern',
action="append",
dest="allure_link_pattern",
metavar="LINK_TYPE:LINK_PATTERN",
default=[],
type=link_pattern,
help="""Url pattern for link type. Allows short links in test,
like 'issue-1'. Text will be formatted to full url with python
str.format().""")
def cleanup_factory(plugin):
def clean_up():
name = allure_commons.plugin_manager.get_name(plugin)
allure_commons.plugin_manager.unregister(name=name)
return clean_up
def pytest_addhooks(pluginmanager):
# Need register title hooks before conftest init
title_helper = AllureTitleHelper()
allure_commons.plugin_manager.register(title_helper)
def pytest_configure(config):
report_dir = config.option.allure_report_dir
clean = False if config.option.collectonly else config.option.clean_alluredir
test_helper = AllureTestHelper(config)
allure_commons.plugin_manager.register(test_helper)
config.add_cleanup(cleanup_factory(test_helper))
if report_dir:
report_dir = os.path.abspath(report_dir)
test_listener = AllureListener(config)
config.pluginmanager.register(test_listener, 'allure_listener')
allure_commons.plugin_manager.register(test_listener)
config.add_cleanup(cleanup_factory(test_listener))
file_logger = AllureFileLogger(report_dir, clean)
allure_commons.plugin_manager.register(file_logger)
config.add_cleanup(cleanup_factory(file_logger))
config.addinivalue_line("markers", f"{ALLURE_LABEL_MARK}: allure label marker")
config.addinivalue_line("markers", f"{ALLURE_LINK_MARK}: allure link marker")
config.addinivalue_line("markers", f"{ALLURE_DESCRIPTION_MARK}: allure description")
config.addinivalue_line("markers", f"{ALLURE_DESCRIPTION_HTML_MARK}: allure description html")
def select_by_labels(items, config):
arg_labels = set().union(
config.option.allure_epics,
config.option.allure_features,
config.option.allure_stories,
config.option.allure_ids,
config.option.allure_severities,
*config.option.allure_labels
)
if arg_labels:
selected, deselected = [], []
for item in items:
test_labels = set(allure_labels(item))
test_severity = allure_label(item, LabelType.SEVERITY)
if not test_severity:
test_labels.add((LabelType.SEVERITY, Severity.NORMAL))
if arg_labels & test_labels:
selected.append(item)
else:
deselected.append(item)
return selected, deselected
else:
return items, []
def select_by_testcase(items, config):
planned_tests = get_testplan()
is_inversion = config.option.inversion
if planned_tests:
def is_planed(item):
allure_ids = allure_label(item, LabelType.ID)
allure_string_ids = list(map(str, allure_ids))
for planed_item in planned_tests:
planed_item_string_id = str(planed_item.get("id"))
planed_item_selector = planed_item.get("selector")
if (
planed_item_string_id in allure_string_ids
or planed_item_selector == allure_full_name(item)
):
return True if not is_inversion else False
return False if not is_inversion else True
selected, deselected = [], []
for item in items:
selected.append(item) if is_planed(item) else deselected.append(item)
return selected, deselected
else:
return items, []
def pytest_collection_modifyitems(items, config):
selected, deselected_by_testcase = select_by_testcase(items, config)
selected, deselected_by_labels = select_by_labels(selected, config)
items[:] = selected
if deselected_by_testcase or deselected_by_labels:
config.hook.pytest_deselected(items=[*deselected_by_testcase, *deselected_by_labels])