1+ import os
2+ import datetime
3+ from itertools import zip_longest
4+ from robot .libraries .BuiltIn import BuiltIn
5+
16import allure_commons
27from allure_commons .utils import now
38from 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
514from allure_commons .model2 import Status , StatusDetails
615from 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
822
923
1024def get_status (exception ):
@@ -21,27 +35,185 @@ def get_status_details(exc_type, exception, exc_traceback):
2135 trace = format_traceback (exc_traceback ))
2236
2337
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+
2452class 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 )
27197
28198 @allure_commons .hookimpl
29199 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 )
31201
32202 @allure_commons .hookimpl
33203 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 )
35205
36206 @allure_commons .hookimpl
37207 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 ()]
41212
42213 @allure_commons .hookimpl
43214 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