1
+ import os
2
+ import datetime
3
+ from itertools import zip_longest
4
+ from robot .libraries .BuiltIn import BuiltIn
5
+
1
6
import allure_commons
2
7
from allure_commons .utils import now
3
8
from allure_commons .utils import uuid4
4
- from allure_commons .model2 import TestStepResult
9
+ from allure_commons .utils import md5
10
+ from allure_commons .utils import platform_label
11
+ from allure_commons .utils import host_tag
12
+ from allure_commons .utils import format_exception , format_traceback
13
+ from allure_commons .model2 import Label
5
14
from allure_commons .model2 import Status , StatusDetails
6
15
from allure_commons .model2 import Parameter
7
- from allure_commons .utils import format_exception , format_traceback
16
+ from allure_commons .types import LabelType , AttachmentType , Severity , LinkType
17
+ from allure_robotframework .utils import get_allure_status
18
+ from allure_robotframework .utils import get_allure_suites
19
+ from allure_robotframework .utils import get_allure_parameters
20
+ from allure_robotframework .utils import allure_labels , allure_links , allure_tags
21
+ from allure_robotframework .types import RobotStatus , RobotLogLevel
8
22
9
23
10
24
def get_status (exception ):
@@ -21,27 +35,185 @@ def get_status_details(exc_type, exception, exc_traceback):
21
35
trace = format_traceback (exc_traceback ))
22
36
23
37
38
+ def pool_id ():
39
+ return BuiltIn ().get_variable_value ('${PABOTEXECUTIONPOOLID}' ) or "default"
40
+
41
+
42
+ def get_message_time (timestamp ):
43
+ s_time = datetime .datetime .strptime (timestamp , "%Y%m%d %H:%M:%S.%f" )
44
+ return int (s_time .timestamp () * 1000 )
45
+
46
+
47
+ LOG_MESSAGE_FORMAT = '<p><b>[{level}]</b> {message}</p>'
48
+ FAIL_MESSAGE_FORMAT = '<p style="color: red"><b>[{level}]</b> {message}</p>'
49
+ MAX_STEP_MESSAGE_COUNT = int (os .getenv ('ALLURE_MAX_STEP_MESSAGE_COUNT' , 0 ))
50
+
51
+
24
52
class AllureListener (object ):
25
- def __init__ (self , logger ):
26
- self .logger = logger
53
+ def __init__ (self , lifecycle ):
54
+ self .lifecycle = lifecycle
55
+ self ._platform = platform_label ()
56
+ self ._host = host_tag ()
57
+ self ._current_msg = None
58
+ self ._current_tb = None
59
+
60
+ def start_suite_container (self , name , attributes ):
61
+ with self .lifecycle .start_container ():
62
+ pass
63
+
64
+ def stop_suite_container (self , name , attributes ):
65
+ suite_status = get_allure_status (attributes .get ('status' ))
66
+ suite_message = attributes .get ('message' )
67
+
68
+ with self .lifecycle .update_container () as container :
69
+ for uuid in container .children :
70
+ with self .lifecycle .update_test_case (uuid ) as test_result :
71
+ if test_result and test_result .status == Status .PASSED and suite_message :
72
+ test_result .status = suite_status
73
+ test_result .statusDetails = StatusDetails (message = self ._current_msg or suite_message ,
74
+ trace = self ._current_tb )
75
+ self .lifecycle .write_test_case (uuid )
76
+ self ._current_tb , self ._current_msg = None , None
77
+ self .lifecycle .write_container ()
78
+
79
+ def start_test_container (self , name , attributes ):
80
+ with self .lifecycle .start_container ():
81
+ pass
82
+
83
+ def stop_test_container (self , name , attributes ):
84
+ suite_status = get_allure_status (attributes .get ('status' ))
85
+ suite_message = attributes .get ('message' )
86
+
87
+ with self .lifecycle .schedule_test_case () as test_result :
88
+ if test_result .status == Status .PASSED and suite_message :
89
+ test_result .status = suite_status
90
+ test_result .statusDetails = StatusDetails (message = self ._current_msg or suite_message ,
91
+ trace = self ._current_tb )
92
+
93
+ self ._current_tb , self ._current_msg = None , None
94
+ self .lifecycle .write_container ()
95
+
96
+ def start_before_fixture (self , name ):
97
+ with self .lifecycle .start_before_fixture () as fixture :
98
+ fixture .name = name
99
+
100
+ def stop_before_fixture (self , attributes , messages ):
101
+ self ._report_messages (messages )
102
+ with self .lifecycle .update_before_fixture () as fixture :
103
+ fixture .status = get_allure_status (attributes .get ('status' ))
104
+ fixture .statusDetails = StatusDetails (message = self ._current_msg , trace = self ._current_tb )
105
+ self .lifecycle .stop_before_fixture ()
106
+
107
+ def start_after_fixture (self , name ):
108
+ with self .lifecycle .start_after_fixture () as fixture :
109
+ fixture .name = name
110
+
111
+ def stop_after_fixture (self , attributes , messages ):
112
+ self ._report_messages (messages )
113
+ with self .lifecycle .update_after_fixture () as fixture :
114
+ fixture .status = get_allure_status (attributes .get ('status' ))
115
+ fixture .statusDetails = StatusDetails (message = self ._current_msg , trace = self ._current_tb )
116
+ self .lifecycle .stop_after_fixture ()
117
+
118
+ def start_test (self , name , attributes ):
119
+ uuid = uuid4 ()
120
+ with self .lifecycle .schedule_test_case (uuid = uuid ) as test_result :
121
+ long_name = attributes .get ('longname' )
122
+ test_result .name = name
123
+ test_result .fullName = long_name
124
+ test_result .historyId = md5 (long_name )
125
+ test_result .start = now ()
126
+
127
+ for container in self .lifecycle .containers ():
128
+ container .children .append (uuid )
129
+
130
+ def stop_test (self , _ , attributes , messages ):
131
+ self ._report_messages (messages )
132
+
133
+ if 'skipped' in [tag .lower () for tag in attributes ['tags' ]]:
134
+ attributes ['status' ] = RobotStatus .SKIPPED
135
+
136
+ with self .lifecycle .update_test_case () as test_result :
137
+ test_result .stop = now ()
138
+ test_result .description = attributes .get ('doc' )
139
+ test_result .status = get_allure_status (attributes .get ('status' ))
140
+ test_result .labels .extend (get_allure_suites (attributes .get ('longname' )))
141
+ test_result .labels .append (Label (name = LabelType .FRAMEWORK , value = 'robotframework' ))
142
+ test_result .labels .append (Label (name = LabelType .LANGUAGE , value = self ._platform ))
143
+ test_result .labels .append (Label (name = LabelType .HOST , value = self ._host ))
144
+ test_result .labels .append (Label (name = LabelType .THREAD , value = pool_id ()))
145
+ test_result .labels .extend (allure_tags (attributes ))
146
+ test_result .statusDetails = StatusDetails (message = self ._current_msg or attributes .get ('message' ),
147
+ trace = self ._current_tb )
148
+
149
+ if attributes .get ('critical' ) == 'yes' :
150
+ test_result .labels .append (Label (name = LabelType .SEVERITY , value = Severity .CRITICAL ))
151
+
152
+ for label_type in (LabelType .EPIC , LabelType .FEATURE , LabelType .STORY ):
153
+ test_result .labels .extend (allure_labels (attributes , label_type ))
154
+
155
+ for link_type in (LinkType .ISSUE , LinkType .TEST_CASE , LinkType .LINK ):
156
+ test_result .links .extend (allure_links (attributes , link_type ))
157
+
158
+ self ._current_tb , self ._current_msg = None , None
159
+
160
+ def start_keyword (self , name ):
161
+ with self .lifecycle .start_step () as step :
162
+ step .name = name
163
+
164
+ def stop_keyword (self , attributes , messages ):
165
+ self ._report_messages (messages )
166
+ with self .lifecycle .update_step () as step :
167
+ step .status = get_allure_status (attributes .get ('status' ))
168
+ step .parameters = get_allure_parameters (attributes .get ('args' ))
169
+ step .statusDetails = StatusDetails (message = self ._current_msg , trace = self ._current_tb )
170
+ self .lifecycle .stop_step ()
171
+
172
+ def _report_messages (self , messages ):
173
+ has_trace = BuiltIn ().get_variable_value ("${LOG LEVEL}" ) in (RobotLogLevel .DEBUG , RobotLogLevel .TRACE )
174
+ attachment = ""
175
+
176
+ for message , next_message in zip_longest (messages , messages [1 :]):
177
+ name = message .get ('message' )
178
+ level = message .get ('level' )
179
+ message_format = FAIL_MESSAGE_FORMAT if level in RobotLogLevel .CRITICAL_LEVELS else LOG_MESSAGE_FORMAT
180
+
181
+ if level == RobotLogLevel .FAIL :
182
+ self ._current_msg = name or self ._current_msg
183
+ self ._current_tb = next_message .get ("message" ) if has_trace and next_message else self ._current_tb
184
+
185
+ if len (messages ) > MAX_STEP_MESSAGE_COUNT :
186
+ attachment += message_format .format (level = level , message = name .replace ('\n ' , '<br>' ))
187
+ else :
188
+ with self .lifecycle .start_step () as step :
189
+ step .name = name
190
+ step .start = step .stop = get_message_time (message .get ("timestamp" ))
191
+ step .status = Status .FAILED if level in RobotLogLevel .CRITICAL_LEVELS else Status .PASSED
192
+ self .lifecycle .stop_step ()
193
+
194
+ if attachment :
195
+ self .lifecycle .attach_data (uuid = uuid4 (), body = attachment , name = 'Keyword Log' ,
196
+ attachment_type = AttachmentType .HTML )
27
197
28
198
@allure_commons .hookimpl
29
199
def attach_data (self , body , name , attachment_type , extension ):
30
- self .logger .attach_data (uuid4 (), body , name = name , attachment_type = attachment_type , extension = extension )
200
+ self .lifecycle .attach_data (uuid4 (), body , name = name , attachment_type = attachment_type , extension = extension )
31
201
32
202
@allure_commons .hookimpl
33
203
def attach_file (self , source , name , attachment_type , extension ):
34
- self .logger .attach_file (uuid4 (), source , name = name , attachment_type = attachment_type , extension = extension )
204
+ self .lifecycle .attach_file (uuid4 (), source , name = name , attachment_type = attachment_type , extension = extension )
35
205
36
206
@allure_commons .hookimpl
37
207
def start_step (self , uuid , title , params ):
38
- parameters = [Parameter (name = name , value = value ) for name , value in params .items ()]
39
- step = TestStepResult (name = title , start = now (), parameters = parameters )
40
- self .logger .start_step (None , uuid , step )
208
+ with self .lifecycle .start_step () as step :
209
+ step .name = title
210
+ step .start = now ()
211
+ step .parameters = [Parameter (name = name , value = value ) for name , value in params .items ()]
41
212
42
213
@allure_commons .hookimpl
43
214
def stop_step (self , uuid , exc_type , exc_val , exc_tb ):
44
- self .logger .stop_step (uuid ,
45
- stop = now (),
46
- status = get_status (exc_val ),
47
- statusDetails = get_status_details (exc_type , exc_val , exc_tb ))
215
+ with self .lifecycle .update_step () as step :
216
+ step .stop = now ()
217
+ step .status = get_status (exc_val )
218
+ step .statusDetails = get_status_details (exc_type , exc_val , exc_tb )
219
+ self .lifecycle .stop_step ()
0 commit comments