testit-adapter-behave 4.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,49 @@
1
+ from behave.formatter.base import Formatter
2
+
3
+ from testit_python_commons.services import TmsPluginManager
4
+
5
+ from .listener import AdapterListener
6
+ from .utils import filter_out_scenarios, parse_userdata
7
+
8
+
9
+ class AdapterFormatter(Formatter):
10
+ __adapter_launch_is_started = False
11
+ __tests_for_launch = None
12
+
13
+ def __init__(self, stream_opener, config):
14
+ super(AdapterFormatter, self).__init__(stream_opener, config)
15
+
16
+ option = parse_userdata(config.userdata)
17
+
18
+ self.__listener = AdapterListener(
19
+ TmsPluginManager.get_adapter_manager(option),
20
+ TmsPluginManager.get_step_manager())
21
+
22
+ TmsPluginManager.get_plugin_manager().register(self.__listener)
23
+
24
+ def start_adapter_launch(self):
25
+ self.__listener.start_launch()
26
+
27
+ self.__tests_for_launch = self.__listener.get_tests_for_launch()
28
+ self.__adapter_launch_is_started = True
29
+
30
+ def uri(self, uri):
31
+ if not self.__adapter_launch_is_started:
32
+ self.start_adapter_launch()
33
+
34
+ def feature(self, feature):
35
+ feature.scenarios = filter_out_scenarios(
36
+ self.__tests_for_launch,
37
+ feature.scenarios)
38
+
39
+ def scenario(self, scenario):
40
+ self.__listener.get_scenario(scenario)
41
+
42
+ def match(self, match):
43
+ self.__listener.get_step_parameters(match)
44
+
45
+ def result(self, step):
46
+ self.__listener.get_step_result(step)
47
+
48
+ def close_stream(self):
49
+ self.__listener.stop_launch()
@@ -0,0 +1,141 @@
1
+ import testit_python_commons.services as adapter
2
+ from testit_python_commons.models.outcome_type import OutcomeType
3
+ from testit_python_commons.services import (
4
+ AdapterManager,
5
+ StepManager)
6
+
7
+ from .models.test_result_step import get_test_result_step_model
8
+ from .scenario_parser import (
9
+ parse_scenario,
10
+ parse_step_status,
11
+ parse_status_type)
12
+ from .utils import (
13
+ convert_step_to_step_result_model,
14
+ convert_executable_test_to_test_result_model)
15
+ import logging
16
+
17
+ # on_block_completed must be called AFTER other actions
18
+ # on_running_started must be called BEFORE other actions
19
+
20
+ class AdapterListener(object):
21
+ __executable_test = None
22
+ __background_steps_count = 0
23
+ __steps_count = 0
24
+
25
+ def __init__(self, adapter_manager: AdapterManager, step_manager: StepManager):
26
+ self.__adapter_manager = adapter_manager
27
+ self.__step_manager = step_manager
28
+
29
+ def start_launch(self):
30
+ self.__adapter_manager.on_running_started()
31
+ test_run_id = self.__adapter_manager.get_test_run_id()
32
+
33
+ self.__adapter_manager.set_test_run_id(test_run_id)
34
+ logging.debug("start_launch")
35
+
36
+ def stop_launch(self):
37
+ logging.debug("BEHAVE stop_launch")
38
+ self.__adapter_manager.write_tests()
39
+ self.__adapter_manager.on_block_completed()
40
+
41
+
42
+ def get_tests_for_launch(self):
43
+ logging.debug("get_tests_for_launch")
44
+ self.__adapter_manager.on_running_started()
45
+ return self.__adapter_manager.get_autotests_for_launch()
46
+
47
+ def get_scenario(self, scenario):
48
+ logging.debug("get_scenario")
49
+ self.__adapter_manager.on_running_started()
50
+ self.__executable_test = parse_scenario(scenario)
51
+ self.__background_steps_count = len(scenario.background_steps)
52
+ self.__steps_count = len(scenario.steps)
53
+
54
+
55
+
56
+ def set_scenario(self):
57
+ logging.debug("BEHAVE set_scenario")
58
+ self.__adapter_manager.write_test(
59
+ convert_executable_test_to_test_result_model(self.__executable_test))
60
+ # must depend on the current adapter mode
61
+ # if it's realtime -> call
62
+ if self.__adapter_manager.is_realtime():
63
+ self.__adapter_manager.on_block_completed()
64
+
65
+ def get_step_parameters(self, match):
66
+ scope = self.get_scope()
67
+
68
+ executable_step = get_test_result_step_model()
69
+
70
+ for argument in match.arguments:
71
+ name = argument.name if argument.name else 'param' + str(match.arguments.index(argument))
72
+ executable_step['description'] += f'{name} = {argument.original} '
73
+ executable_step['parameters'][name] = argument.original
74
+
75
+ self.__executable_test[scope].append(executable_step)
76
+
77
+ def get_step_result(self, result):
78
+ logging.debug("get_step_result")
79
+ self.__adapter_manager.on_running_started()
80
+ scope = self.get_scope()
81
+ outcome = parse_step_status(result.status)
82
+
83
+ self.__executable_test[scope][-1]['title'] = result.name
84
+ self.__executable_test[scope][-1]['outcome'] = outcome
85
+ self.__executable_test[scope][-1]['duration'] = round(result.duration * 1000)
86
+ self.__executable_test['duration'] += result.duration * 1000
87
+
88
+ # TODO: Add to python-commons
89
+ nested_step_results = self.__step_manager.get_steps_tree()
90
+ executable_step = self.__executable_test[scope][-1]
91
+ # TODO: Fix in python-commons
92
+ result_scope = f'{scope}Results' if scope == 'setUp' else 'stepResults'
93
+ self.__executable_test[result_scope].append(
94
+ convert_step_to_step_result_model(
95
+ executable_step,
96
+ nested_step_results))
97
+
98
+ if outcome != OutcomeType.PASSED:
99
+ self.__executable_test['traces'] = result.error_message
100
+ self.__executable_test['outcome'] = result.status.name
101
+ self.__executable_test['status_type'] = parse_status_type(result.status)
102
+ self.set_scenario()
103
+ return
104
+
105
+ if scope == 'setUp':
106
+ self.__background_steps_count -= 1
107
+ return
108
+
109
+ self.__steps_count -= 1
110
+
111
+ if self.__steps_count == 0:
112
+ self.__executable_test['outcome'] = result.status.name
113
+ self.__executable_test['status_type'] = parse_status_type(result.status)
114
+ self.set_scenario()
115
+
116
+ def get_scope(self):
117
+ self.__adapter_manager.on_running_started()
118
+ if self.__background_steps_count != 0:
119
+ return 'setUp'
120
+
121
+ return 'steps'
122
+
123
+ @adapter.hookimpl
124
+ def add_link(self, link):
125
+ if self.__executable_test:
126
+ self.__executable_test['resultLinks'].append(link)
127
+
128
+ @adapter.hookimpl
129
+ def add_message(self, test_message):
130
+ if self.__executable_test:
131
+ self.__executable_test['message'] = str(test_message)
132
+
133
+ @adapter.hookimpl
134
+ def add_attachments(self, attach_paths: list or tuple):
135
+ if self.__executable_test:
136
+ self.__executable_test['attachments'] += self.__adapter_manager.load_attachments(attach_paths)
137
+
138
+ @adapter.hookimpl
139
+ def create_attachment(self, body, name: str):
140
+ if self.__executable_test:
141
+ self.__executable_test['attachments'] += self.__adapter_manager.create_attachment(body, name)
File without changes
@@ -0,0 +1,5 @@
1
+ # TODO: Add model to python-commons; implement via attrs
2
+ def get_label_model(label):
3
+ return {
4
+ 'name': label
5
+ }
@@ -0,0 +1,19 @@
1
+ from attr import attrib, attrs
2
+
3
+
4
+ # TODO: Add model to python-commons
5
+ @attrs(kw_only=True)
6
+ class Option(object):
7
+ set_url = attrib(default=None)
8
+ set_private_token = attrib(default=None)
9
+ set_project_id = attrib(default=None)
10
+ set_configuration_id = attrib(default=None)
11
+ set_test_run_id = attrib(default=None)
12
+ set_test_run_name = attrib(default=None)
13
+ set_tms_proxy = attrib(default=None)
14
+ set_adapter_mode = attrib(default=None)
15
+ set_config_file = attrib(default=None)
16
+ set_cert_validation = attrib(default=None)
17
+ set_automatic_creation_test_cases = attrib(default=None)
18
+ set_automatic_updation_links_to_test_cases = attrib(default=None)
19
+ set_import_realtime = attrib(default=None)
@@ -0,0 +1,11 @@
1
+ class TagType:
2
+ EXTERNAL_ID = 'ExternalId='
3
+ DISPLAY_NAME = 'DisplayName='
4
+ LINKS = 'Links='
5
+ TITLE = 'Title='
6
+ WORK_ITEM_IDS = 'WorkItemIds='
7
+ DESCRIPTION = 'Description='
8
+ LABELS = 'Labels='
9
+ TAGS = 'Tags='
10
+ NAMESPACE = 'NameSpace='
11
+ CLASSNAME = 'ClassName='
@@ -0,0 +1,14 @@
1
+ # TODO: Add model to python-commons; implement via attrs
2
+ def get_test_result_step_model():
3
+ return {
4
+ 'title': None,
5
+ 'description': '',
6
+ 'outcome': None,
7
+ 'duration': 0,
8
+ 'steps': [],
9
+ 'parameters': {},
10
+ 'attachments': [],
11
+ # TODO: Add to python-commons
12
+ # 'started_on': '',
13
+ # 'completed_on': None
14
+ }
@@ -0,0 +1,17 @@
1
+ from testit_python_commons.models.link import Link
2
+ from testit_python_commons.models.link_type import LinkType
3
+
4
+
5
+
6
+ def get_url_to_link_model(url: str) -> Link:
7
+ return Link() \
8
+ .set_url(url) \
9
+ .set_link_type(LinkType.RELATED)
10
+
11
+
12
+ def get_dict_to_link_model(link: dict) -> Link:
13
+ return Link() \
14
+ .set_url(link['url']) \
15
+ .set_title(link.get('title', None)) \
16
+ .set_link_type(link.get('type', LinkType.RELATED)) \
17
+ .set_description(link.get('description', None))
@@ -0,0 +1,164 @@
1
+ import traceback
2
+ from enum import Enum
3
+
4
+ from testit_python_commons.models.outcome_type import OutcomeType
5
+ from testit_python_commons.models.status_type import StatusType
6
+
7
+ from .models.tags import TagType
8
+ from .tags_parser import parse_test_tags
9
+
10
+ STATUS_TYPE = {
11
+ 'passed': StatusType.SUCCEEDED,
12
+ 'failed': StatusType.FAILED,
13
+ 'error': StatusType.FAILED,
14
+ 'skipped': StatusType.INCOMPLETE,
15
+ 'untested': StatusType.INCOMPLETE,
16
+ 'undefined': StatusType.INCOMPLETE
17
+ }
18
+ STEP_STATUS = {
19
+ 'passed': OutcomeType.PASSED,
20
+ 'failed': OutcomeType.FAILED,
21
+ 'error': OutcomeType.FAILED,
22
+ 'skipped': OutcomeType.SKIPPED,
23
+ 'untested': OutcomeType.SKIPPED,
24
+ 'undefined': OutcomeType.BLOCKED
25
+ }
26
+
27
+
28
+ def parse_scenario(scenario):
29
+ tags = parse_test_tags(scenario.tags + scenario.feature.tags)
30
+
31
+ # TODO: Add model to python-commons; implement via attrs
32
+ executable_test = {
33
+ 'externalID': tags[TagType.EXTERNAL_ID] if
34
+ TagType.EXTERNAL_ID in tags and tags[TagType.EXTERNAL_ID] else get_scenario_external_id(scenario),
35
+ 'autoTestName': tags[TagType.DISPLAY_NAME] if
36
+ TagType.DISPLAY_NAME in tags and tags[TagType.DISPLAY_NAME] else get_scenario_name(scenario),
37
+ 'outcome': None,
38
+ 'status_type': None,
39
+ 'steps': [],
40
+ 'stepResults': [],
41
+ 'setUp': [],
42
+ 'setUpResults': [],
43
+ 'tearDown': [],
44
+ 'tearDownResults': [],
45
+ 'resultLinks': [],
46
+ 'duration': 0,
47
+ 'traces': None,
48
+ 'message': None,
49
+ 'namespace': get_scenario_namespace(scenario),
50
+ 'classname': None,
51
+ 'attachments': [],
52
+ 'parameters': get_scenario_parameters(scenario),
53
+ # TODO: Make optional in Converter python-commons
54
+ 'properties': {},
55
+ 'title': None,
56
+ 'description': None,
57
+ 'links': [],
58
+ 'labels': [],
59
+ 'tags': [],
60
+ 'workItemsID': [],
61
+ "externalKey": get_scenario_name(scenario)
62
+ # TODO: Add to python-commons
63
+ # 'started_on': '',
64
+ # 'completed_on': None
65
+ }
66
+
67
+ if TagType.TITLE in tags:
68
+ executable_test['title'] = tags[TagType.TITLE]
69
+
70
+ if TagType.DESCRIPTION in tags:
71
+ executable_test['description'] = tags[TagType.DESCRIPTION]
72
+
73
+ if TagType.LINKS in tags:
74
+ executable_test['links'] = tags[TagType.LINKS]
75
+
76
+ if TagType.LABELS in tags:
77
+ executable_test['labels'] = tags[TagType.LABELS]
78
+
79
+ if TagType.TAGS in tags:
80
+ executable_test['tags'] = tags[TagType.TAGS]
81
+
82
+ if TagType.NAMESPACE in tags:
83
+ executable_test['namespace'] = tags[TagType.NAMESPACE]
84
+
85
+ if TagType.CLASSNAME in tags:
86
+ executable_test['classname'] = tags[TagType.CLASSNAME]
87
+
88
+ if TagType.WORK_ITEM_IDS in tags:
89
+ # TODO: Fix in python-commons to "workItemIds"
90
+ executable_test['workItemsID'] = tags[TagType.WORK_ITEM_IDS]
91
+
92
+ return executable_test
93
+
94
+
95
+ def parse_step_status(status):
96
+ return STEP_STATUS[status.name]
97
+
98
+
99
+ def parse_status_type(status):
100
+ return STATUS_TYPE[status.name]
101
+
102
+
103
+ def get_scenario_name(scenario):
104
+ return scenario.name if scenario.name else scenario.keyword
105
+
106
+
107
+ def get_scenario_external_id(scenario):
108
+ from .utils import get_hash
109
+
110
+ return get_hash(scenario.feature.filename + scenario.name)
111
+
112
+
113
+ def get_scenario_namespace(scenario):
114
+ return scenario.feature.filename
115
+
116
+
117
+ def get_scenario_parameters(scenario):
118
+ row = scenario._row
119
+
120
+ return {name: value for name, value in zip(row.headings, row.cells)} if row else {}
121
+
122
+
123
+ def get_scenario_status(scenario):
124
+ for step in scenario.all_steps:
125
+ if get_step_status(step) != 'passed':
126
+ return get_step_status(step)
127
+ return OutcomeType.PASSED
128
+
129
+
130
+ def get_scenario_status_details(scenario):
131
+ for step in scenario.all_steps:
132
+ if get_step_status(step) != 'passed':
133
+ return get_step_status_details(step)
134
+
135
+
136
+ def get_step_status(result):
137
+ if result.exception:
138
+ return get_status(result.exception)
139
+ else:
140
+ if isinstance(result.status, Enum):
141
+ return STEP_STATUS.get(result.status.name, None)
142
+ else:
143
+ return STEP_STATUS.get(result.status, None)
144
+
145
+
146
+ def get_status(exception):
147
+ if exception and isinstance(exception, AssertionError):
148
+ return OutcomeType.FAILED
149
+ elif exception:
150
+ return OutcomeType.BLOCKED
151
+ return OutcomeType.PASSED
152
+
153
+
154
+ def get_step_status_details(result):
155
+ if result.exception:
156
+ trace = "\n".join(result.exc_traceback) if type(result.exc_traceback) == list else \
157
+ ''.join(traceback.format_tb(result.exc_traceback))
158
+ return trace
159
+
160
+
161
+ def get_step_table(step):
162
+ table = [','.join(step.table.headings)]
163
+ [table.append(','.join(list(row))) for row in step.table.rows]
164
+ return '\n'.join(table)
@@ -0,0 +1,106 @@
1
+ from .models.label import get_label_model
2
+ from .models.tags import TagType
3
+ from .models.url_link import get_url_to_link_model, get_dict_to_link_model
4
+
5
+
6
+ def parse_test_tags(tags):
7
+ parsed_tags = {
8
+ TagType.EXTERNAL_ID: None,
9
+ TagType.DISPLAY_NAME: None,
10
+ TagType.LINKS: [],
11
+ TagType.TITLE: None,
12
+ TagType.WORK_ITEM_IDS: [],
13
+ TagType.DESCRIPTION: None,
14
+ TagType.LABELS: [],
15
+ TagType.TAGS: [],
16
+ TagType.NAMESPACE: None,
17
+ TagType.CLASSNAME: None,
18
+ }
19
+
20
+ for tag in tags:
21
+ if TagType.EXTERNAL_ID in tag:
22
+ parsed_tags[TagType.EXTERNAL_ID] = __parse_space_in_tag(tag[len(TagType.EXTERNAL_ID):])
23
+
24
+ elif TagType.DISPLAY_NAME in tag:
25
+ parsed_tags[TagType.DISPLAY_NAME] = __parse_space_in_tag(tag[len(TagType.DISPLAY_NAME):])
26
+
27
+ elif TagType.LINKS in tag:
28
+ parsed_tags[TagType.LINKS].extend(
29
+ __parse_links(tag[len(TagType.LINKS):]))
30
+
31
+ elif TagType.TITLE in tag:
32
+ parsed_tags[TagType.TITLE] = __parse_space_in_tag(tag[len(TagType.TITLE):])
33
+
34
+ elif TagType.WORK_ITEM_IDS in tag:
35
+ parsed_tags[TagType.WORK_ITEM_IDS].extend(
36
+ __parse_massive(tag[len(TagType.WORK_ITEM_IDS):]))
37
+
38
+ elif TagType.DESCRIPTION in tag:
39
+ parsed_tags[TagType.DESCRIPTION] = __parse_space_in_tag(tag[len(TagType.DESCRIPTION):])
40
+
41
+ elif TagType.LABELS in tag:
42
+ parsed_tags[TagType.LABELS].extend(
43
+ __parse_labels(tag[len(TagType.LABELS):]))
44
+
45
+ elif TagType.TAGS in tag:
46
+ parsed_tags[TagType.TAGS].extend(
47
+ __parse_tags(tag[len(TagType.TAGS):]))
48
+
49
+ elif TagType.NAMESPACE in tag:
50
+ parsed_tags[TagType.NAMESPACE] = __parse_space_in_tag(tag[len(TagType.NAMESPACE):])
51
+
52
+ elif TagType.CLASSNAME in tag:
53
+ parsed_tags[TagType.CLASSNAME] = __parse_space_in_tag(tag[len(TagType.CLASSNAME):])
54
+
55
+ return parsed_tags
56
+
57
+
58
+ def __parse_massive(tag: str):
59
+ return tag.split(',')
60
+
61
+
62
+ def __parse_labels(tag):
63
+ parsed_labels = []
64
+
65
+ for label in __parse_massive(tag):
66
+ parsed_labels.append(get_label_model(label))
67
+
68
+ return parsed_labels
69
+
70
+
71
+ def __parse_tags(tag):
72
+ parsed_tags = []
73
+
74
+ for tms_tag in __parse_massive(tag):
75
+ parsed_tags.append(tms_tag)
76
+
77
+ return parsed_tags
78
+
79
+
80
+ def __parse_links(tag: str):
81
+ parsed_links = []
82
+ json_links = __parse_json(tag)
83
+
84
+ if not json_links:
85
+ for url in __parse_massive(tag):
86
+ parsed_links.append(get_url_to_link_model(url))
87
+
88
+ if isinstance(json_links, tuple):
89
+ for url in json_links:
90
+ parsed_links.append(get_url_to_link_model(url))
91
+
92
+ if isinstance(json_links, dict):
93
+ parsed_links.append(get_dict_to_link_model(json_links))
94
+
95
+ return parsed_links
96
+
97
+
98
+ def __parse_json(json_string: str):
99
+ try:
100
+ return eval(json_string)
101
+ except Exception:
102
+ return
103
+
104
+
105
+ def __parse_space_in_tag(tag: str) -> str:
106
+ return tag.replace('\\_', ' ')
@@ -0,0 +1,172 @@
1
+ import hashlib
2
+ import logging
3
+ import re
4
+ from typing import List
5
+
6
+ from testit_python_commons.models.step_result import StepResult
7
+ from testit_python_commons.models.test_result import TestResult
8
+
9
+ from .models.option import Option
10
+ from .models.tags import TagType
11
+ from .scenario_parser import get_scenario_external_id, get_scenario_parameters
12
+ from .tags_parser import parse_test_tags
13
+
14
+
15
+ def parse_userdata(userdata):
16
+ if not userdata:
17
+ return
18
+
19
+ option = Option()
20
+
21
+ if 'tmsUrl' in userdata:
22
+ option.set_url = userdata['tmsUrl']
23
+
24
+ if 'tmsPrivateToken' in userdata:
25
+ option.set_private_token = userdata['tmsPrivateToken']
26
+
27
+ if 'tmsProjectId' in userdata:
28
+ option.set_project_id = userdata['tmsProjectId']
29
+
30
+ if 'tmsConfigurationId' in userdata:
31
+ option.set_configuration_id = userdata['tmsConfigurationId']
32
+
33
+ if 'tmsTestRunId' in userdata:
34
+ option.set_test_run_id = userdata['tmsTestRunId']
35
+
36
+ if 'tmsTestRunName' in userdata:
37
+ option.set_test_run_name = userdata['tmsTestRunName']
38
+
39
+ if 'tmsProxy' in userdata:
40
+ option.set_tms_proxy = userdata['tmsProxy']
41
+
42
+ if 'tmsAdapterMode' in userdata:
43
+ option.set_adapter_mode = userdata['tmsAdapterMode']
44
+
45
+ if 'tmsConfigFile' in userdata:
46
+ option.set_config_file = userdata['tmsConfigFile']
47
+
48
+ if 'tmsCertValidation' in userdata:
49
+ option.set_cert_validation = userdata['tmsCertValidation']
50
+
51
+ if 'tmsAutomaticCreationTestCases' in userdata:
52
+ option.set_automatic_creation_test_cases = userdata['tmsAutomaticCreationTestCases']
53
+
54
+ if 'tmsAutomaticUpdationLinksToTestCases' in userdata:
55
+ option.set_automatic_updation_links_to_test_cases = userdata['tmsAutomaticUpdationLinksToTestCases']
56
+
57
+ if 'tmsImportRealtime' in userdata:
58
+ option.set_import_realtime = userdata['tmsImportRealtime']
59
+
60
+ return option
61
+
62
+
63
+ def filter_out_scenarios(tests_for_launch, scenarios):
64
+ if tests_for_launch:
65
+ included_scenarios = []
66
+ for i in range(len(scenarios)):
67
+ if scenarios[i].keyword == 'Scenario Outline' and hasattr(scenarios[i], 'scenarios'):
68
+ scenarios_outline = filter_out_scenarios(tests_for_launch, scenarios[i].scenarios)
69
+
70
+ if len(scenarios_outline) != 0:
71
+ for unmatched_scenario in set(scenarios[i].scenarios).symmetric_difference(set(scenarios_outline)):
72
+ scenarios[i].scenarios.remove(unmatched_scenario)
73
+
74
+ included_scenarios.append(scenarios[i])
75
+ else:
76
+ if validate_scenario(scenarios[i], tests_for_launch):
77
+ included_scenarios.append(scenarios[i])
78
+
79
+ scenarios = included_scenarios
80
+
81
+ return scenarios
82
+
83
+
84
+ def validate_scenario(scenario, tests_for_launch) -> bool:
85
+ tags = parse_test_tags(scenario.tags + scenario.feature.tags)
86
+ external_id = tags[TagType.EXTERNAL_ID] if \
87
+ TagType.EXTERNAL_ID in tags and tags[TagType.EXTERNAL_ID] else get_scenario_external_id(scenario)
88
+
89
+ if scenario.keyword == 'Scenario Outline':
90
+ external_id = param_attribute_collector(external_id, get_scenario_parameters(scenario))
91
+
92
+ return external_id in tests_for_launch
93
+
94
+
95
+ def param_attribute_collector(attribute, run_param):
96
+ result = attribute
97
+ param_keys = re.findall(r"\{'<(.*?)>'\}", attribute)
98
+ if len(param_keys) > 0:
99
+ for param_key in param_keys:
100
+ root_key = param_key
101
+ id_keys = re.findall(r'\[(.*?)\]', param_key)
102
+ if len(id_keys) == 0:
103
+ if root_key in run_param:
104
+ result = result.replace("{'<" + root_key + ">'}", str(run_param[root_key]))
105
+ else:
106
+ logging.error(f"Parameter {root_key} not found")
107
+ elif len(id_keys) == 1:
108
+ base_key = root_key.replace("[" + id_keys[0] + "]", "")
109
+ id_key = id_keys[0].strip("\'\"")
110
+ if id_key.isdigit() and int(id_key) in range(len(run_param[base_key])):
111
+ val_key = int(id_key)
112
+ elif id_key.isalnum() and not id_key.isdigit() and id_key in run_param[base_key].keys():
113
+ val_key = id_key
114
+ else:
115
+ raise SystemExit(f"Not key: {root_key} in run parameters or other keys problem")
116
+ result = result.replace("{'<" + root_key + ">'}", str(run_param[base_key][val_key]))
117
+ else:
118
+ raise SystemExit("For type tuple, list, dict) support only one level!")
119
+ elif len(param_keys) == 0:
120
+ result = attribute
121
+ else:
122
+ raise SystemExit("Collecting parameters error!")
123
+ return result
124
+
125
+
126
+ def convert_executable_test_to_test_result_model(executable_test: dict) -> TestResult:
127
+ return TestResult()\
128
+ .set_external_id(executable_test['externalID'])\
129
+ .set_autotest_name(executable_test['autoTestName'])\
130
+ .set_step_results(executable_test['stepResults'])\
131
+ .set_setup_results(executable_test['setUpResults'])\
132
+ .set_teardown_results(executable_test['tearDownResults'])\
133
+ .set_duration(executable_test['duration'])\
134
+ .set_outcome(executable_test['outcome'])\
135
+ .set_status_type(executable_test['status_type'])\
136
+ .set_traces(executable_test['traces'])\
137
+ .set_attachments(executable_test['attachments'])\
138
+ .set_parameters(executable_test['parameters'])\
139
+ .set_properties(executable_test['properties'])\
140
+ .set_namespace(executable_test['namespace'])\
141
+ .set_classname(executable_test['classname'])\
142
+ .set_title(executable_test['title'])\
143
+ .set_description(executable_test['description'])\
144
+ .set_links(executable_test['links'])\
145
+ .set_result_links(executable_test['resultLinks'])\
146
+ .set_labels(executable_test['labels']) \
147
+ .set_tags(executable_test['tags'])\
148
+ .set_work_item_ids(executable_test['workItemsID'])\
149
+ .set_message(executable_test['message'])\
150
+ .set_external_key(executable_test['externalKey'])
151
+
152
+
153
+ def convert_step_to_step_result_model(step: dict, nested_step_results: List[StepResult]) -> StepResult:
154
+ step_result_model = StepResult()\
155
+ .set_title(step['title'])\
156
+ .set_description(step['description'])\
157
+ .set_outcome(step['outcome'])\
158
+ .set_duration(step['duration'])\
159
+ .set_attachments(step['attachments'])
160
+
161
+ if 'parameters' in step:
162
+ step_result_model.set_parameters(step['parameters'])
163
+
164
+ if nested_step_results:
165
+ step_result_model.set_step_results(nested_step_results)
166
+
167
+ return step_result_model
168
+
169
+
170
+ def get_hash(value: str):
171
+ md = hashlib.sha256(bytes(value, encoding='utf-8'))
172
+ return md.hexdigest()
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: testit-adapter-behave
3
+ Version: 4.2.8
4
+ Summary: Behave adapter for Test IT
5
+ Home-page: https://github.com/testit-tms/adapters-python/
6
+ Author: Integration team
7
+ Author-email: integrations@testit.software
8
+ License: Apache-2.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.6
11
+ Classifier: Programming Language :: Python :: 3.7
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: behave
19
+ Requires-Dist: testit-python-commons==4.2.8
20
+ Requires-Dist: attrs
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license
28
+ Dynamic: requires-dist
29
+ Dynamic: summary
30
+
31
+ # Test IT TMS adapter for Behave
32
+
33
+ ![Test IT](https://raw.githubusercontent.com/testit-tms/adapters-python/master/images/banner.png)
34
+
35
+ [![Release
36
+ Status](https://img.shields.io/pypi/v/testit-adapter-behave?style=plastic)](https://pypi.python.org/pypi/testit-adapter-behave)
37
+ [![Downloads](https://img.shields.io/pypi/dm/testit-adapter-behave?style=plastic)](https://pypi.python.org/pypi/testit-adapter-behave)
38
+ [![GitHub contributors](https://img.shields.io/github/contributors/testit-tms/adapters-python?style=plastic)](https://github.com/testit-tms/adapters-python)
39
+
40
+ ## Getting Started
41
+
42
+ ### Installation
43
+
44
+ ```
45
+ pip install testit-adapter-behave
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Configuration
51
+
52
+ | Description | File property | Environment variable | CLI argument |
53
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|--------------------------------------------|--------------------------------------|
54
+ | Location of the TMS instance | url | TMS_URL | tmsUrl |
55
+ | API secret key [How to getting API secret key?](https://github.com/testit-tms/.github/tree/main/configuration#privatetoken) | privateToken | TMS_PRIVATE_TOKEN | tmsPrivateToken |
56
+ | ID of project in TMS instance [How to getting project ID?](https://github.com/testit-tms/.github/tree/main/configuration#projectid) | projectId | TMS_PROJECT_ID | tmsProjectId |
57
+ | ID of configuration in TMS instance [How to getting configuration ID?](https://github.com/testit-tms/.github/tree/main/configuration#configurationid) | configurationId | TMS_CONFIGURATION_ID | tmsConfigurationId |
58
+ | ID of the created test run in TMS instance.<br/>It's necessary for **adapterMode** 0 or 1 | testRunId | TMS_TEST_RUN_ID | tmsTestRunId |
59
+ | Parameter for specifying the name of test run in TMS instance (**It's optional**). If it is not provided, it is created automatically | testRunName | TMS_TEST_RUN_NAME | tmsTestRunName |
60
+ | Adapter mode. Default value - 0. The adapter supports following modes:<br/>0 - in this mode, the adapter filters tests by test run ID and configuration ID, and sends the results to the test run<br/>1 - in this mode, the adapter sends all results to the test run without filtering or [with filtering CLI](#run-with-filter)<br/>2 - in this mode, the adapter creates a new test run and sends results to the new test run | adapterMode | TMS_ADAPTER_MODE | tmsAdapterMode |
61
+ | It enables/disables certificate validation (**It's optional**). Default value - true | certValidation | TMS_CERT_VALIDATION | tmsCertValidation |
62
+ | Mode of automatic creation test cases (**It's optional**). Default value - false. The adapter supports following modes:<br/>true - in this mode, the adapter will create a test case linked to the created autotest (not to the updated autotest)<br/>false - in this mode, the adapter will not create a test case | automaticCreationTestCases | TMS_AUTOMATIC_CREATION_TEST_CASES | tmsAutomaticCreationTestCases |
63
+ | Mode of automatic updation links to test cases (**It's optional**). Default value - false. The adapter supports following modes:<br/>true - in this mode, the adapter will update links to test cases<br/>false - in this mode, the adapter will not update link to test cases | automaticUpdationLinksToTestCases | TMS_AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES | tmsAutomaticUpdationLinksToTestCases |
64
+ | Mode of import type selection when launching autotests (**It's optional**). Default value - true. The adapter supports following modes:<br/>true - in this mode, the adapter will create/update each autotest in real time<br/>false - in this mode, the adapter will create/update multiple autotests | importRealtime | TMS_IMPORT_REALTIME | tmsImportRealtime |
65
+ | Url of proxy server (**It's optional**) | tmsProxy | TMS_PROXY | tmsProxy |
66
+ | Name (**including extension**) of the configuration file If it is not provided, it is used default file name (**It's optional**) | - | TMS_CONFIG_FILE | tmsConfigFile |
67
+ | Sync storage port (**It's optional, 49152 by default**) | syncStoragePort | TMS_SYNC_STORAGE_PORT | syncStoragePort |
68
+
69
+ #### File
70
+
71
+ Add `[testit]` block to your **pyproject.toml** or create **connection_config.ini** file in the root directory of the project:
72
+ ```
73
+ [testit]
74
+ URL = URL
75
+ privateToken = USER_PRIVATE_TOKEN
76
+ projectId = PROJECT_ID
77
+ configurationId = CONFIGURATION_ID
78
+ testRunId = TEST_RUN_ID
79
+ testRunName = TEST_RUN_NAME
80
+ adapterMode = ADAPTER_MODE
81
+ certValidation = CERT_VALIDATION
82
+ automaticCreationTestCases = AUTOMATIC_CREATION_TEST_CASES
83
+ automaticUpdationLinksToTestCases = AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES
84
+ importRealtime = IMPORT_REALTIME
85
+ syncStoragePort = 49152
86
+
87
+ # This section are optional. It enables debug mode.
88
+ [debug]
89
+ tmsProxy = TMS_PROXY
90
+ ```
91
+
92
+ #### Examples
93
+
94
+ Launch with a `pyproject.toml` or `connection_config.ini` file in the root directory of the project:
95
+
96
+ ```
97
+ $ behave -f testit_adapter_behave.formatter:AdapterFormatter
98
+ ```
99
+
100
+ Launch with command-line parameters:
101
+
102
+ ```
103
+ $ behave -f testit_adapter_behave.formatter:AdapterFormatter -D tmsUrl=URL -D tmsPrivateToken=USER_PRIVATE_TOKEN -D
104
+ tmsProjectId=PROJECT_ID -D tmsConfigurationId=CONFIGURATION_ID -D tmsTestRunId=TEST_RUN_ID -D tmsAdapterMode=ADAPTER_MODE -D
105
+ tmsTestRunName=TEST_RUN_NAME -D tmsProxy='{"http":"http://localhost:8888","https":"http://localhost:8888"}' -D
106
+ tmsCertValidation=CERT_VALIDATION -D tmsAutomaticCreationTestCases=AUTOMATIC_CREATION_TEST_CASES -D
107
+ tmsAutomaticUpdationLinksToTestCases=AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES -D tmsImportRealtime=IMPORT_REALTIME
108
+ ```
109
+
110
+ Logging level:
111
+ ```
112
+ $ behave ... --logging-level=DEBUG
113
+ ```
114
+
115
+ If you want to enable debug mode then
116
+ see [How to enable debug logging?](https://github.com/testit-tms/adapters-python/tree/main/testit-python-commons)
117
+
118
+ #### Run with filter
119
+ To create filter by autotests you can use the Test IT CLI (use adapterMode 1 for run with filter):
120
+
121
+ ```
122
+ $ export TMS_TOKEN=<YOUR_TOKEN>
123
+ $ testit autotests_filter
124
+ --url https://tms.testit.software \
125
+ --configuration-id 5236eb3f-7c05-46f9-a609-dc0278896464 \
126
+ --testrun-id 6d4ac4b7-dd67-4805-b879-18da0b89d4a8 \
127
+ --framework behave \
128
+ --output tmp/filter.txt
129
+
130
+ $ behave "$(cat tmp/filter.txt)" -D tmsTestRunId=6d4ac4b7-dd67-4805-b879-18da0b89d4a8 -D tmsAdapterMode=1 -f testit_adapter_behave.formatter:AdapterFormatter
131
+ ```
132
+
133
+ ### Tags
134
+
135
+ Use tags to specify information about autotest.
136
+
137
+ Description of tags:
138
+
139
+ - `WorkItemIds` - a method that links autotests with manual tests. Receives the array of manual tests' IDs
140
+ - `DisplayName` - internal autotest name (used in Test IT)
141
+ - `ExternalId` - unique internal autotest ID (used in Test IT)
142
+ - `Title` - autotest name specified in the autotest card. If not specified, the name from the displayName method is used
143
+ - `Description` - autotest description specified in the autotest card
144
+ - `Labels` - labels listed in the autotest card
145
+ - `Tags` - tags listed in the autotest card
146
+ - `Links` - links listed in the autotest card
147
+ - `NameSpace` - directory in the TMS system (default - file's name of test)
148
+ - `ClassName` - subdirectory in the TMS system (default - class's name of test)
149
+
150
+ If you want to insert a space in the tags, use the "\\_" character combination.
151
+
152
+ Description of methods:
153
+
154
+ - `testit.addWorkItemIds` - a dynamic method that links autotests with manual tests. Receives the array of manual tests' IDs
155
+ - `testit.addDisplayName` - a dynamic method for adding internal autotest name (used in Test IT)
156
+ - `testit.addExternalId` - a dynamic method for adding unique internal autotest ID (used in Test IT)
157
+ - `testit.addTitle` - a dynamic method for adding autotest name specified in the autotest card. If not specified, the name from the displayName method is used
158
+ - `testit.addDescription` - a dynamic method for adding autotest description specified in the autotest card
159
+ - `testit.addLabels` - a dynamic method for adding labels listed in the autotest card
160
+ - `testit.addTags` - a dynamic method for adding tags listed in the autotest card
161
+ - `testit.addLinks` - links in the autotest result
162
+ - `testit.addAttachments` - uploading files in the autotest result
163
+ - `testit.addMessage` - information about autotest in the autotest result
164
+ - `testit.addNameSpace` - a dynamic method for adding directory in the TMS system (default - file's name of test)
165
+ - `testit.addClassName` - a dynamic method for adding subdirectory in the TMS system (default - class's name of test)
166
+ - `testit.addParameter` - a dynamic method for adding parameter in the autotest result
167
+ - `testit.step` - usage in the "with" construct to designation a step in the body of the test
168
+
169
+ ### Examples
170
+
171
+ #### Simple Test
172
+
173
+ ```py
174
+ import testit
175
+ from behave import given
176
+ from behave import then
177
+ from behave import when
178
+
179
+
180
+ @given("I authorize on the portal")
181
+ def authorization(context):
182
+ with testit.step("I set login"):
183
+ pass
184
+ with testit.step("I set password"):
185
+ pass
186
+
187
+
188
+ @when("I create a project")
189
+ def create_project(context):
190
+ pass
191
+
192
+
193
+ @when("I open the project")
194
+ def enter_project(context):
195
+ pass
196
+
197
+
198
+ @when("I create a section")
199
+ def create_section(context):
200
+ testit.addLinks(
201
+ title='component_dump.dmp',
202
+ type=testit.LinkType.RELATED,
203
+ url='https://dumps.example.com/module/some_module_dump',
204
+ description='Description'
205
+ )
206
+
207
+
208
+ @then("I create a test case")
209
+ def create_test_case(context):
210
+ testit.addAttachments('pictures/picture.jpg')
211
+ ```
212
+
213
+ ```buildoutcfg
214
+ Feature: Sample
215
+
216
+ Background:
217
+ Given I authorize on the portal
218
+
219
+ @ExternalId=failed_with_all_annotations
220
+ @DisplayName=Failed_test_with_all_annotations
221
+ @WorkItemIds=123
222
+ @Title=Title_in_the_autotest_card
223
+ @Description=Test_with_all_annotations
224
+ @Tags=Tag1,Tag2
225
+ @Links={"url":"https://dumps.example.com/module/repository","title":"Repository","description":"Example_of_repository","type":"Repository"}
226
+ Scenario: Create new project, section and test case
227
+ When I create a project
228
+ And I open the project
229
+ And I create a section
230
+ Then I create a test case
231
+ ```
232
+
233
+ #### Parameterized test
234
+
235
+ > [!WARNING]
236
+ > When linking a parameterized autotest to a parameterized test case, please consider the problematic points:
237
+ > - In TMS test cases have a table with parameters, but autotests do not. They are not equal entities, so there may be incompatibility in terms of parameters
238
+ > - Running a parameterized test case, TMS expects the results of all related autotests with all the parameters specified in the test case table
239
+ > - In TMS, the parameters are limited to the string type, so the adapter transmits absolutely all the autotest parameters as a string. This implies the following problematic point for the test case table
240
+ > - TMS expects a complete **textual** match of the name and value of the parameters of the test case table with the autotest parameters
241
+
242
+ ```py
243
+ from behave import when
244
+ from behave import then
245
+
246
+
247
+ @when("Summing {left:d}+{right:d}")
248
+ def step_impl(context, left, right):
249
+ context.sum = left + right
250
+
251
+
252
+ @then("Result is {result:d}")
253
+ def step_impl(context, result):
254
+ assert context.sum == result
255
+
256
+ ```
257
+
258
+ ```buildoutcfg
259
+ Feature: Rule
260
+ Tests that use Rule
261
+
262
+ Scenario Outline: Summing
263
+ When Summing <left>+<right>
264
+ Then Result is <result>
265
+
266
+ Examples:
267
+ | left | right | result |
268
+ | 1 | 1 | 3 |
269
+ | 9 | 9 | 18 |
270
+ ```
271
+
272
+ # Contributing
273
+
274
+ You can help to develop the project. Any contributions are **greatly appreciated**.
275
+
276
+ * If you have suggestions for adding or removing projects, feel free
277
+ to [open an issue](https://github.com/testit-tms/adapters-python/issues/new) to discuss it, or directly create a pull
278
+ request after you edit the *README.md* file with necessary changes.
279
+ * Please make sure you check your spelling and grammar.
280
+ * Create individual PR for each suggestion.
281
+ * Please also read through
282
+ the [Code Of Conduct](https://github.com/testit-tms/adapters-python/blob/master/CODE_OF_CONDUCT.md) before posting
283
+ your first idea as well.
284
+
285
+ # License
286
+
287
+ Distributed under the Apache-2.0 License.
288
+ See [LICENSE](https://github.com/testit-tms/adapters-python/blob/master/LICENSE.md) for more information.
289
+
@@ -0,0 +1,16 @@
1
+ testit_adapter_behave/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ testit_adapter_behave/formatter.py,sha256=3bvCs_3ISshs4uUQiIHBTnLWgO4d0AAp8ww56qOEUX8,1466
3
+ testit_adapter_behave/listener.py,sha256=ePojZW8oHaKWWIwCQhrTc1hPK3ufFHbC_uMcryVlqLI,5255
4
+ testit_adapter_behave/scenario_parser.py,sha256=XcI3feZrvrj0Gb5YWU0JhmltwrRqE2i_AR7hN_1rjgk,4767
5
+ testit_adapter_behave/tags_parser.py,sha256=Y_iyK7jUEc0RqOnT3j0Zo8C8mb6aBg71OXrvwh-wrDw,3019
6
+ testit_adapter_behave/utils.py,sha256=HRsAgk0UP8d0CoyAQWehn5WLJ5Y39WESSXQ1mz_-Y-E,6718
7
+ testit_adapter_behave/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ testit_adapter_behave/models/label.py,sha256=RejlJc1rN33pUAXw0wghOleFK2URGIdMEObXBsCRGkE,126
9
+ testit_adapter_behave/models/option.py,sha256=RqcCgV_Tbl-FxASXzeAVFs-2XeGBkRzxibaeme0Q__Y,723
10
+ testit_adapter_behave/models/tags.py,sha256=lTdcMs71xFaJfR6w6wo09TIwlPiSaFXPLST-WS-rpxw,291
11
+ testit_adapter_behave/models/test_result_step.py,sha256=OXFY0ZiBeb5OK7sk1Z0rHrfwnCPm5z4wmOqiCNph9Pc,379
12
+ testit_adapter_behave/models/url_link.py,sha256=qMKOi2yVkPI_hHO2MV2YZlSlzw7JJHkCe8X5xqNT8WA,509
13
+ testit_adapter_behave-4.2.8.dist-info/METADATA,sha256=3RMVvCAAZDZJ38EJLnUuG81KlcI1I47k0EmZEyq3d1c,18280
14
+ testit_adapter_behave-4.2.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ testit_adapter_behave-4.2.8.dist-info/top_level.txt,sha256=T3XswPnkdP_wek99JR9BBOq9FiVWv6-ucmFezMrmT0Q,22
16
+ testit_adapter_behave-4.2.8.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ testit_adapter_behave