class AllureListener(object): ROBOT_LISTENER_API_VERSION = 2 def __init__(self, allurePropPath=None, source='Listener'): self.stack = [] self.testsuite = None self.callstack = [] self.AllurePropPath = allurePropPath self.AllureIssueIdRegEx = '' self.suite_setup = None self.suite_teardown = None self.test_setup = None self.test_teardown = None self.testsuite is None self.isFirstSuite = True # Setting this variable prevents the loading of a Library added Listener. # I case the Listener is added via Command Line, the Robot Context is not # yet there and will cause an exceptions. Similar section in start_suite. try: AllureListenerActive = BuiltIn().get_variable_value( '${ALLURE}', False) BuiltIn().set_global_variable('${ALLURE}', True) except: pass def start_suitesetup(self, name, attributes): severity = 'blocker' test = TestCase(name=name, description=name, start=now(), attachments=[], labels=[], severity=severity, steps=[]) self.stack.append(test) return def end_suitesetup(self, name, attributes): step = self.stack.pop() if attributes.get('status') == Robot.PASS: step.status = Status.PASSED else: step.status = Status.FAILED step.description = attributes.get('doc') step.stop = now() step = TestStep(name=name, title=attributes.get('kwname'), attachments=[], status=step.status, steps=step.steps, start=step.start, stop=step.stop) if attributes.get('type') == 'Setup': if not self.stack: self.suite_setup = step else: self.stack[-1].steps.append(step) else: self.suite_teardown = step #self.testsuite.tests.append(test) return step def start_test(self, name, attributes): if len(attributes.get('doc')) > 0: description = attributes.get('doc') else: description = name severity = 'critical' if attributes['critical'] == 'yes' else 'normal' test = TestCase(name=name, description=description, start=now(), attachments=[], severity=severity, labels=[], steps=[]) self.stack.append(test) return def end_test(self, name, attributes): # logger.console('\nend_test: ['+name+']') # logger.console(attributes) # logger.console(' [stack lenght] ['+str(len(self.stack))+'] [testsuite lenght] ['+ str(len(self.testsuite.tests))+']') test = self.stack.pop() if attributes.get('status') == Robot.PASS: test.status = Status.PASSED elif attributes.get('status') == Robot.FAIL: test.status = Status.FAILED test.failure = Failure(message=attributes.get('message'), trace='') elif attributes.get('doc') is not '': test.description = attributes.get('doc') if attributes['tags']: for tag in attributes['tags']: if re.search(self.AllureIssueIdRegEx, tag): test.labels.append(TestLabel(name=Label.ISSUE, value=tag)) if tag.startswith('feature'): test.labels.append( TestLabel(name='feature', value=tag.split(':')[-1])) if tag.startswith('story'): test.labels.append( TestLabel(name='story', value=tag.split(':')[-1])) elif tag in SEVERITIES: test.labels.append(TestLabel(name='severity', value=tag)) elif tag in STATUSSES: test.status = tag # overwrites the actual test status with this value. self.PabotPoolId = BuiltIn().get_variable_value( '${PABOTEXECUTIONPOOLID}') if (self.PabotPoolId is not None): self.threadId = 'PabotPoolId-' + str(self.PabotPoolId) else: self.threadId = threading._get_ident() test.labels.append(TestLabel(name='thread', value=str(self.threadId))) self.testsuite.tests.append(test) test.stop = now() return test def start_suite(self, name, attributes): self.SuitSrc = BuiltIn().get_variable_value('${SUITE_SOURCE}') # Reading the Allure Properties file for the Issue Id regular expression # for the Issues and the URL to where the Issues/Test Man links should go. if (self.AllurePropPath is None): self.AllurePropPath = os.path.join(self.SuitSrc, 'allure.properties') if os.path.exists(self.AllurePropPath) is True: self.AllureProperties = AllureProperties(self.AllurePropPath) self.AllureIssueIdRegEx = self.AllureProperties.get_property( 'allure.issues.id.pattern') # Not using &{ALLURE} as this is throwing an error and ${ALLURE} gives the # desired dictionary in Allure as well. BuiltIn().set_global_variable('${ALLURE}', self.AllureProperties.get_properties()) # When running a Robot folder, the folder itself is also considered a Suite # The full check depends on the availability of all the vars which are # only available when a Robot file has started. IsSuiteDirectory = os.path.isdir(self.SuitSrc) if (not (IsSuiteDirectory)): ''' Check if class received Output Directory Path in the properties file. ''' if self.AllureProperties.get_property( 'allure.cli.logs.xml') is None: ''' No Path was provided, so using output dir with additional sub folder. ''' self.allurelogdir = BuiltIn().get_variable_value( '${OUTPUT_DIR}') + "\\Allure" else: self.allurelogdir = self.AllureProperties.get_property( 'allure.cli.logs.xml') self.AllureImplc = AllureImpl(self.allurelogdir) ''' Clear the directory but not if run in parallel mode in Pabot''' PabotPoolId = BuiltIn().get_variable_value('${PABOTEXECUTIONPOOLID}') try: if (self.isFirstSuite == True and self.AllureProperties.get_property( 'allure.cli.logs.xml.clear') == 'True' and PabotPoolId is None): clear_directory( self.AllureProperties.get_property('allure.cli.logs.xml')) except Exception as e: logger.console(pprint.pformat(e)) finally: self.isFirstSuite = False if attributes.get('doc') is not '': description = attributes.get('doc') else: description = name self.testsuite = TestSuite(name=name, title=attributes['longname'], description=description, tests=[], labels=[], start=now()) return def end_suite(self, name, attributes): self.testsuite.stop = now() for test in self.testsuite.tests: if self.suite_setup: test.steps.insert(0, self.suite_setup) if self.suite_teardown: test.steps.append(self.suite_teardown) self.suite_setup = None self.suite_teardown = None logfilename = '%s-testsuite.xml' % uuid.uuid4() # When running a folder, the folder itself is also considered a Suite # The full check depends on the availability of all the vars which are # only available when a Robot file has started. IsSuiteDirectory = os.path.isdir( BuiltIn().get_variable_value("${SUITE_SOURCE}")) logger.console('end_suite SUITE_SOURCE ' + BuiltIn().get_variable_value("${SUITE_SOURCE}")) logger.console('end_suite log dir ' + self.AllureImplc.logdir) if (not (IsSuiteDirectory)): with self.AllureImplc._reportfile(logfilename) as f: self.AllureImplc._write_xml(f, self.testsuite) return def start_keyword(self, name, attributes, is_message=False): # logger.console('\nstart_keyword: ['+name+']') # logger.console(' ['+attributes['type']+'] [stack lenght] ['+str(len(self.stack))+'] [testsuite lenght] ['+ str(len(self.testsuite.tests))+']') if (not is_message and hasattr(self, attributes.get('kwname').replace(" ", "_")) and callable( getattr(self, attributes.get('kwname').replace(" ", "_")))): libraryMethodToCall = getattr( self, attributes.get('kwname').replace(" ", "_")) result = libraryMethodToCall(name, attributes) keyword = TestStep( name=name, title=attributes.get('kwname'), attachments=[], steps=[], start=now(), ) if self.stack: self.stack.append(keyword) return keyword if (attributes.get('type') == 'Keyword' or (attributes.get('type') == 'Teardown' and len(self.stack) is not 0)): keyword = TestStep( name=name, title=attributes.get('kwname'), attachments=[], steps=[], start=now(), ) if self.stack: self.stack.append(keyword) return keyword """ Processing the Suite Setup. Although there is no test case yet, a virtual one is created to allow for the inclusion of the keyword. """ if (attributes.get('type') == 'Setup'): self.start_suitesetup(name, attributes) return if (attributes.get('type') == 'Teardown' and len(self.stack) == 0): self.start_suitesetup(name, attributes) return def end_keyword(self, name, attributes): # logger.console('\nend_keyword: ['+name+']') # logger.console(' ['+attributes['type']+'] [stack lenght] ['+str(len(self.stack))+'] [testsuite lenght] ['+ str(len(self.testsuite.tests))+']') if len(self.stack) > 0: if (attributes.get('type') == 'Keyword' or (attributes.get('type') == 'Teardown' and isinstance(self.stack[-1], TestStep) is True)): step = self.stack.pop() if (attributes.get('status') == 'FAIL'): step.status = 'failed' elif (attributes.get('status') == 'PASS'): step.status = 'passed' step.stop = now() # Append the step to the previous item. This can be another step, or # another keyword. self.stack[-1].steps.append(step) return if (attributes.get('type') == 'Setup'): self.end_suitesetup(name, attributes) return if (attributes.get('type') == 'Teardown'): self.end_suitesetup(name, attributes) return return def message(self, msg): pass def log_message(self, msg): # logger.console(pprint.pformat(msg)) # logger.console(self.stack[-1].title) # Check to see if there are any items to add the log message to # this check is needed because otherwise Suite Setup may fail. if len(self.stack) > 0: if self.stack[-1].title == 'Capture Page Screenshot': screenshot = re.search('[a-z]+-[a-z]+-[0-9]+.png', msg['message']) if screenshot: self.attach('{}'.format(screenshot.group(0)), screenshot.group(0)) if (msg['html'] == 'yes'): screenshot = re.search('[a-z]+-[a-z]+-[0-9]+.png', msg['message']) if screenshot: kwname = '{}'.format(screenshot.group(0)) else: kwname = msg['message'] # logger.console('kwname: '+kwname) else: kwname = msg['message'] startKeywordArgs = { 'args': [], 'assign': [], 'doc': '', 'kwname': kwname, 'libname': 'BuiltIn', 'starttime': now(), 'tags': [], 'type': 'Keyword' } self.start_keyword('Log Message', startKeywordArgs, True) endKeywordArgs = { 'args': [], 'assign': [], 'doc': '', 'elapsedtime': 0, 'endtime': now(), 'kwname': kwname, 'libname': 'BuiltIn', 'starttime': now(), 'status': 'PASS', 'tags': [], 'type': 'Keyword' } self.end_keyword('Log Message', endKeywordArgs) return def close(self): IsSuiteDirectory = os.path.isdir(self.SuitSrc) if (not (IsSuiteDirectory)): self.save_environment() # self.save_properties() self.AllureProperties.save_properties() logger.console("pabot poolid: [" + str(self.PabotPoolId) + "]") if (self.AllureProperties.get_property('allure.cli.outputfiles') == 'True' and self.PabotPoolId is None): self.allure(self.AllureProperties) return # Helper functions def save_environment(self): environment = {} environment['id'] = 'Robot Framework' environment['name'] = socket.getfqdn() environment['url'] = 'http://' + socket.getfqdn() + ':8000' included_args = self.AllureProperties.get_property( 'robot.cli.arguments.included') rf_args = sys.argv[1:] if included_args: rf_args = [] for index, arg in enumerate(sys.argv): if arg in included_args: rf_args.append(sys.argv[index]) if index < len( sys.argv) and not sys.argv[index + 1].startswith('-'): rf_args.append(sys.argv[index + 1]) env_dict = (\ {'Robot Framework Full Version': get_full_version()},\ {'Robot Framework Version': get_version()},\ {'Interpreter': get_interpreter()},\ {'Python version': sys.version.split()[0]},\ {'Allure Adapter version': VERSION},\ {'Robot Framework CLI Arguments': rf_args},\ {'Robot Framework Hostname': socket.getfqdn()},\ {'Robot Framework Platform': sys.platform}\ ) for key in env_dict: self.AllureImplc.environment.update(key) self.AllureImplc.logdir = self.AllureProperties.get_property( 'allure.cli.logs.xml') self.AllureImplc.store_environment(environment) def allure(self, AllureProps): JAVA_PATH = AllureProps.get_property('allure.java.path') ALLURE_HOME = '-Dallure.home=' + AllureProps.get_property( 'allure.home') JAVA_CLASSPATH = '-cp "' + AllureProps.get_property( 'allure.java.classpath') + '"' ALLURE_LOGFILE = AllureProps.get_property('allure.cli.logs.xml') ALLURE_OUTPUT = '-o ' + AllureProps.get_property( 'allure.cli.logs.output') JAVA_CLASS = 'ru.yandex.qatools.allure.CommandLine' ALLURE_COMMAND = 'generate' ALLURE_URL = AllureProps.get_property('allure.results.url') allure_cmd = JAVA_PATH + ' ' + ALLURE_HOME + ' ' + JAVA_CLASSPATH + ' ' + JAVA_CLASS + ' ' + ALLURE_COMMAND + ' ' + ALLURE_LOGFILE + ' ' + ALLURE_OUTPUT if (AllureProps.get_property('allure.cli.outputfiles') == 'True'): FNULL = open(os.devnull, 'w') #stdout=FNULL, subprocess.Popen(allure_cmd, stderr=subprocess.STDOUT, shell=True).wait() if (AllureProps.get_property('allure.results.browser.open') == 'True'): webbrowser.open(ALLURE_URL, new=0, autoraise=True) def attach(self, title, contents, attach_type=AttachmentType.PNG): """ This functions created the attachments and append it to the test. """ # logger.console("attach-title: "+title) contents = os.path.join(BuiltIn().get_variable_value('${OUTPUT_DIR}'), contents) with open(contents, 'rb') as f: file_contents = f.read() attach = Attach(source=self.AllureImplc._save_attach( file_contents, attach_type), title=title, type=attach_type) self.stack[-1].attachments.append(attach) return def Set_Output_Dir(self, name, attributes): copy_dir_contents( self.AllureProperties.get_property('allure.cli.logs.xml'), attributes['args'][0]) self.AllureProperties.set_property('allure.cli.logs.xml', attributes['args'][0]) self.AllureImplc.logdir = attributes['args'][0]
def start_suite(self, name, attributes): self.SuitSrc = BuiltIn().get_variable_value('${SUITE_SOURCE}') # Reading the Allure Properties file for the Issue Id regular expression # for the Issues and the URL to where the Issues/Test Man links should go. if (self.AllurePropPath is None): self.AllurePropPath = os.path.join(self.SuitSrc, 'allure.properties') if os.path.exists(self.AllurePropPath) is True: self.AllureProperties = AllureProperties(self.AllurePropPath) self.AllureIssueIdRegEx = self.AllureProperties.get_property( 'allure.issues.id.pattern') # Not using &{ALLURE} as this is throwing an error and ${ALLURE} gives the # desired dictionary in Allure as well. BuiltIn().set_global_variable('${ALLURE}', self.AllureProperties.get_properties()) # When running a Robot folder, the folder itself is also considered a Suite # The full check depends on the availability of all the vars which are # only available when a Robot file has started. IsSuiteDirectory = os.path.isdir(self.SuitSrc) if (not (IsSuiteDirectory)): ''' Check if class received Output Directory Path in the properties file. ''' if self.AllureProperties.get_property( 'allure.cli.logs.xml') is None: ''' No Path was provided, so using output dir with additional sub folder. ''' self.allurelogdir = BuiltIn().get_variable_value( '${OUTPUT_DIR}') + "\\Allure" else: self.allurelogdir = self.AllureProperties.get_property( 'allure.cli.logs.xml') self.AllureImplc = AllureImpl(self.allurelogdir) ''' Clear the directory but not if run in parallel mode in Pabot''' PabotPoolId = BuiltIn().get_variable_value('${PABOTEXECUTIONPOOLID}') try: if (self.isFirstSuite == True and self.AllureProperties.get_property( 'allure.cli.logs.xml.clear') == 'True' and PabotPoolId is None): clear_directory( self.AllureProperties.get_property('allure.cli.logs.xml')) except Exception as e: logger.console(pprint.pformat(e)) finally: self.isFirstSuite = False if attributes.get('doc') is not '': description = attributes.get('doc') else: description = name self.testsuite = TestSuite(name=name, title=attributes['longname'], description=description, tests=[], labels=[], start=now()) return