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.
- testit_adapter_behave/__init__.py +0 -0
- testit_adapter_behave/formatter.py +49 -0
- testit_adapter_behave/listener.py +141 -0
- testit_adapter_behave/models/__init__.py +0 -0
- testit_adapter_behave/models/label.py +5 -0
- testit_adapter_behave/models/option.py +19 -0
- testit_adapter_behave/models/tags.py +11 -0
- testit_adapter_behave/models/test_result_step.py +14 -0
- testit_adapter_behave/models/url_link.py +17 -0
- testit_adapter_behave/scenario_parser.py +164 -0
- testit_adapter_behave/tags_parser.py +106 -0
- testit_adapter_behave/utils.py +172 -0
- testit_adapter_behave-4.2.8.dist-info/METADATA +289 -0
- testit_adapter_behave-4.2.8.dist-info/RECORD +16 -0
- testit_adapter_behave-4.2.8.dist-info/WHEEL +5 -0
- testit_adapter_behave-4.2.8.dist-info/top_level.txt +1 -0
|
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,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
|
+

|
|
34
|
+
|
|
35
|
+
[](https://pypi.python.org/pypi/testit-adapter-behave)
|
|
37
|
+
[](https://pypi.python.org/pypi/testit-adapter-behave)
|
|
38
|
+
[](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 @@
|
|
|
1
|
+
testit_adapter_behave
|