def stats_by_keywords(inpath): result = ExecutionResult(inpath) visitor = ExecutionKeywordStats() result.visit(visitor) for kw in sorted(visitor.elapsed_by_kw, key=visitor.elapsed_by_kw.get, reverse=True): print kw, visitor.elapsed_by_kw[kw]
def run(self, args, opts): """Sub command 'check' runner""" if not opts.cases: raise UsageError("case path must be set with -c or --cases!") print " Syntax Checking ".center(70, '*') print '...' log_level = 'TRACE' cases_path = opts.cases tmp_path = tempfile.gettempdir() xml_result = os.path.join(tmp_path, "check_result.xml") output_file = os.path.join(tmp_path, "stdout.txt") with open(output_file, 'w') as stdout: dryrun_result = run(cases_path, dryrun=True, loglevel=log_level, log='NONE', report='NONE', output=xml_result, stdout=stdout) detail_result = ExecutionResult(xml_result) if opts.is_tcms: if not opts.plan_id: raise UsageError("plan id must be set with -p or --planid!") plan_id = opts.plan_id tcms = TCMS() ids = tcms.get_plan_case_ids(plan_id) detail_result.visit(ResultChecker(ids, plan_id)) elif dryrun_result == 0: print 'Contratulations, no syntax error' else: detail_result.visit(DryRunChecker()) print '\n( No news is good news:) )' print 'checing result is in the file: %s' % output_file print ' DONE '.center(70, '*')
def get_suite_names(output_file): if not os.path.isfile(output_file): return [] try: e = ExecutionResult(output_file) gatherer = GatherSuiteNames() e.visit(gatherer) return gatherer.result except: return []
def get_suite_names(output_file): if not os.path.isfile(output_file): print "get_suite_names: output_file='%s' does not exist" % output_file return [] try: e = ExecutionResult(output_file) gatherer = GatherSuiteNames() e.visit(gatherer) return gatherer.result except: print "Exception in get_suite_names!" return []
def __init__(self, workspace_dir, include_keywords = True): #self._verbose = Logger('Parser', verbose_stream) self._include_keywords = include_keywords self._workspace_dir = workspace_dir self._pyro_test_results = [] for dirname in os.walk(self._workspace_dir).next()[1]: testspace_path = os.path.join(os.path.abspath(self._workspace_dir),dirname) for xml_file in os.listdir(testspace_path): if xml_file.endswith(".xml"): xml_path = (os.path.join(testspace_path,xml_file)) #print xml_path try: result = ExecutionResult(xml_path, include_keywords=True) except DataError: print "EMPTY SUITE FOUND, TRYING TO REBOUND: %s " % xml_path self._pyro_test_results.append(PyroTestResult().empty_testsuite(xml_path)) #continue else: pyro_result = PyroTestResult() result.visit(TestResultChecker(pyro_result)) self._pyro_test_results.append(pyro_result)
updateResultInTestLink(self.tls, self.result_dict['testLinkTestProjectName'], self.result_dict['testLinkTestPlanName'], self.build, test.name.strip(":"), testCaseStatus, self.result_dict['testLinkPlatform']) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(description="IVTREE ROBOT FRAMEWORK") parser.add_argument("-b", action="store", default="False", dest="build_id") command_args = parser.parse_args() try: if command_args.build_id: print "running" run_robot('True', command_args.build_id) else: run_robot() finally: print "finally" complete_dir_path = os.path.join( os.getcwd(), "logs" + os.sep + command_args.build_id) complete_xml_path = os.path.join(complete_dir_path, "output.xml") print complete_xml_path result = ExecutionResult(complete_xml_path) result.suite.visit(PrintTestInfo(command_args.build_id))
def parse_output_xml(xml_file_path, csv_dir_path, version_id, platform, level): r""" Parse the robot-generated output.xml file and extract various test output data. Put the extracted information into a csv file in the "dest" folder. Description of argument(s): xml_file_path The path to a Robot-generated output.xml file. csv_dir_path The path to the directory that is to contain the .csv files generated by this function. version_id Version of the openbmc firmware (e.g. "v2.1-215-g6e7eacb"). platform Platform of the openbmc system. level Release level of the OpenBMC system (e.g. "OBMC920"). """ result = ExecutionResult(xml_file_path) result.configure(stat_config={'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother'}) stats = result.statistics print("--------------------------------------") total_tc = stats.total.critical.passed + stats.total.critical.failed print("Total Test Count:\t %d" % total_tc) print("Total Test Failed:\t %d" % stats.total.critical.failed) print("Total Test Passed:\t %d" % stats.total.critical.passed) print("Test Start Time:\t %s" % result.suite.starttime) print("Test End Time:\t\t %s" % result.suite.endtime) print("--------------------------------------") # Use ResultVisitor object and save off the test data info class TestResult(ResultVisitor): def __init__(self): self.testData = [] def visit_test(self, test): self.testData += [test] collectDataObj = TestResult() result.visit(collectDataObj) # Write the result statistics attributes to CSV file l_csvlist = [] # Default Test data l_subsys = 'OPENBMC' l_test_type = 'FTC' l_pse_rel = 'OBMC910' if level: l_pse_rel = level l_env = 'HW' l_proc = 'P9' l_platform_type = "" l_func_area = "" # System data from XML meta data # l_system_info = get_system_details(xml_file_path) # First let us try to collect information from keyboard input # If keyboard input cannot give both information, then find from xml file. if version_id and platform: l_driver = version_id l_platform_type = platform print("BMC Version_id:%s" % version_id) print("BMC Platform:%s" % platform) else: # System data from XML meta data l_system_info = get_system_details(xml_file_path) l_driver = l_system_info[0] l_platform_type = l_system_info[1] # Driver version id and platform are mandatorily required for CSV file # generation. If any one is not avaulable, exit CSV file generation # process. if l_driver and l_platform_type: print("Driver and system info set.") else: print("Both driver and system info need to be set.\ CSV file is not generated.") sys.exit() # Default header l_header = ['test_start', 'test_end', 'subsys', 'test_type', 'test_result', 'test_name', 'pse_rel', 'driver', 'env', 'proc', 'platform_type', 'test_func_area'] l_csvlist.append(l_header) # Generate CSV file onto the path with current time stamp l_base_dir = csv_dir_path l_timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H-%M-%S") # Example: 2017-02-20-08-47-22_Witherspoon.csv l_csvfile = l_base_dir + l_timestamp + "_" + l_platform_type + ".csv" print("Writing data into csv file:%s" % l_csvfile) for testcase in collectDataObj.testData: # Functional Area: Suite Name # Test Name: Test Case Name l_func_area = str(testcase.parent).split(' ', 1)[1] l_test_name = str(testcase) # Test Result pass=0 fail=1 if testcase.status == 'PASS': l_test_result = 0 else: l_test_result = 1 # Format datetime from robot output.xml to "%Y-%m-%d-%H-%M-%S" l_stime = xml_to_csv_time(testcase.starttime) l_etime = xml_to_csv_time(testcase.endtime) # Data Sequence: test_start,test_end,subsys,test_type, # test_result,test_name,pse_rel,driver, # env,proc,platform_tyep,test_func_area, l_data = [l_stime, l_etime, l_subsys, l_test_type, l_test_result, l_test_name, l_pse_rel, l_driver, l_env, l_proc, l_platform_type, l_func_area] l_csvlist.append(l_data) # Open the file and write to the CSV file l_file = open(l_csvfile, "w") l_writer = csv.writer(l_file, lineterminator='\n') l_writer.writerows(l_csvlist) l_file.close()
def check_tests(self): result = ExecutionResult(self.report) result.visit(ExecutionTimeChecker(self.report)) return suit_list
def process_output(self, inpath, outpath=None): result = ExecutionResult(inpath) result.suite.visit(self) result.save(outpath) return result
def get_result_data(xml_robot_output): """ Creates a result visitor from Robot API and accesses data from last Robot test run """ result = ExecutionResult(xml_robot_output) visitor = TestRailResultVisitor() result.visit(visitor) return visitor.suite_list, visitor.testcase_list
def get_suite_status(inpath): result = ExecutionResult(inpath) result.visit(SuiteStatus()) suites_object["suites"] = suite_list return suites_object
shown = 0 for k in s: if shown == shown_keywords: break if limit is not None and k.stdev_per_avgtime > limit: continue shown += 1 print str(k.elapsed).rjust(14)+' | '+str(k.calls).rjust(7)+ ' | ' + \ str(k.average_time).rjust(12) + ' | ' + str(k.median_time).rjust(15) + \ ' | ' + str(k.standard_deviation).rjust(9) + ' | ' + str(k.stdev_per_avgtime).rjust(16) + (' | "%s"' % k.name) print 'Showing %d of total keywords %d' % (shown, len(times.keywords)) if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--limit', '-l', type=float, help='Filter out keywords with larger percentage' ' of stdev/avg time than LIMIT. This helps by filtering out most used "primitive" keywords' ' such as Sleep and Run Keyword If etc. and let\'s you focus on the keywords that very often' ' take a lot of time to execute (in other words are most fruitful places to focus optimisation effort).') parser.add_argument('--show', '-s', default=100, type=int, help='Max number of shown keywords. Default is 100.') parser.add_argument('source', help='output from a Robot Framework execution to analyze') args = parser.parse_args() try: resu = ExecutionResult(args.source) times = KeywordTimes() resu.visit(times) _print_results(times, args.show, args.limit) except: print __doc__ raise
def generate_report(opts): writer = sys.stdout.write group = Group() if not FAILED_IMPORT else '' # ======================== START OF CUSTOMIZE REPORT ================================== # # URL or filepath of your company logo logo = opts.logo # Ignores following library keywords in metrics report ignore_library = IGNORE_LIBRARIES if opts.ignore: ignore_library.extend(opts.ignore) # Ignores following type keywords in metrics report ignore_type = IGNORE_TYPES if opts.ignoretype: ignore_type.extend(opts.ignoretype) # ======================== END OF CUSTOMIZE REPORT ================================== # # Report to support file location as arguments # Source Code Contributed By : Ruud Prijs # input directory path = os.path.abspath(os.path.expanduser(opts.path)) # output.xml file output_name = os.path.join(path, opts.output) # report.html file report_name = opts.report_name # log.html file log_name = opts.log_name required_files = ( output_name, # report_name, # log_name, ) missing_files = [ filename for filename in required_files if not os.path.exists(filename) ] if missing_files: # We have files missing. # writer("The following files are missing: {}\n".format(", ".join(missing_files))) writer("output.xml file is missing: {}".format( ", ".join(missing_files))) exit(1) # email status send_email = opts.email mtTime = datetime.now().strftime('%Y%m%d-%H%M%S') # Output result file location result_file_name = 'metrics-' + mtTime + '.html' result_file = os.path.join(path, result_file_name) # Read output.xml file result = ExecutionResult(output_name) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) writer("Converting .xml to .html file. This may take few minutes...") # ======= START OF EMAIL SETUP CONTENT ====== # if send_email: server = smtplib.SMTP('smtp.gmail.com:587') msg = MIMEMultipart() msg['Subject'] = 'Robotframework Automation Status' sender = opts.sender recipients = opts.to.split(',') if opts.to else '' ccrecipients = opts.cc.split(',') if opts.cc else '' msg['From'] = sender msg['To'] = ", ".join(recipients) msg['Cc'] = ", ".join(ccrecipients) password = opts.pwd msg.add_header('Content-Type', 'text/html') # ======= END OF EMAIL SETUP CONTENT ====== # head_content = """ <!doctype html> <html lang="en"> <head> <link rel="shortcut icon" href="https://png.icons8.com/windows/50/000000/bot.png" type="image/x-icon" /> <title>RF Metrics Report</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet"/> <link href="https://cdn.datatables.net/buttons/1.5.2/css/buttons.dataTables.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.3.1.js" type="text/javascript"></script> <!-- Bootstrap core Googleccharts --> <script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script> <script type="text/javascript">google.charts.load('current', {packages: ['corechart']});</script> <!-- Bootstrap core Datatable--> <script src="https://code.jquery.com/jquery-3.3.1.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/dataTables.buttons.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.flash.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.html5.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.print.min.js" type="text/javascript"></script> <style> .sidebar { position: fixed; top: 0; bottom: 0; left: 0; z-index: 100; /* Behind the navbar */ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); } .sidebar-sticky { position: relative; top: 0; height: calc(100vh - 48px); padding-top: .5rem; overflow-x: hidden; overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ } @supports ((position: -webkit-sticky) or (position: sticky)) { .sidebar-sticky { position: -webkit-sticky; position: sticky; } } .sidebar .nav-link { color: black; } .sidebar .nav-link.active { color: #007bff; } .sidebar .nav-link:hover .feather, .sidebar .nav-link.active .feather { color: inherit; } [role="main"] { padding-top: 8px; } /* Set height of body and the document to 100% */ body { height: 100%; margin: 0; //font-family: Comic Sans MS; background-color: white; } /* Style tab links */ .tablinkLog { cursor: pointer; } @import url(https://fonts.googleapis.com/css?family=Droid+Sans); .loader { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; z-index: 9999; background: url('http://www.downgraf.com/wp-content/uploads/2014/09/01-progress.gif?e44397') 50% 50% no-repeat rgb(249,249,249); } /* TILES */ .tile { width: 100%; float: left; margin: 0px; list-style: none; font-size: 30px; color: #FFF; -moz-border-radius: 5px; -webkit-border-radius: 5px; margin-bottom: 5px; position: relative; text-align: center; color: white!important; } .tile.tile-fail { background: #f44336!important; } .tile.tile-pass { background: #4CAF50!important; } .tile.tile-info { background: #009688!important; } .tile.tile-head { background: #616161!important; } .dt-buttons { margin-left: 5px; } </style> </head> """ soup = BeautifulSoup(head_content, "html.parser") body = soup.new_tag('body') soup.insert(20, body) icons_txt = """ <div class="loader"></div> <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar" style="font-size:16px;"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <img src="%s" style="height:18vh!important;width:95%%;"/> <br> <h6 class="sidebar-heading d-flex justify-content-between align-items-center text-muted"> <span>Metrics</span> <a class="d-flex align-items-center text-muted" href="#"></a> </h6> <li class="nav-item"> <a class="tablink nav-link" href="#" id="defaultOpen" onclick="openPage('dashboard', this, 'orange')"> <i class="fa fa-dashboard"></i> Dashboard </a> </li> <li class="nav-item"> <a class="tablink nav-link" href="#" onclick="openPage('suiteMetrics', this, 'orange');executeDataTable('#sm',5)" > <i class="fa fa-th-large"></i> Suite Metrics </a> </li> <li class="nav-item"> <a class="tablink nav-link" href="#" onclick="openPage('testMetrics', this, 'orange');executeDataTable('#tm',3)"> <i class="fa fa-list-alt"></i> Test Metrics </a> </li> <li class="nav-item"> <a class="tablink nav-link" href="#" onclick="openPage('keywordMetrics', this, 'orange');executeDataTable('#km',3)"> <i class="fa fa-table"></i> Keyword Metrics </a> </li> <li class="nav-item"> <a class="tablink nav-link" href="#" onclick="openPage('log', this, 'orange');"> <i class="fa fa-wpforms"></i> Robot Logs </a> </li> <li class="nav-item"> <a class="tablink nav-link" href="#" onclick="openPage('statistics', this, 'orange');"> <i class="fa fa-envelope-o"></i> Email Metrics </a> </li> </ul> <h6 class="sidebar-heading d-flex justify-content-between align-items-center text-muted"> <span>Project</span> <a class="d-flex align-items-center text-muted" href="#"></a> </h6> <ul class="nav flex-column mb-2"> <li class="nav-item"> <a style="color:blue;" class="tablink nav-link" target="_blank" href="https://www.github.com"> <i class="fa fa-external-link"></i> Git Hub </a> </li> <li class="nav-item"> <a style="color:blue;" class="tablink nav-link" target="_blank" href="https://www.jira.com"> <i class="fa fa-external-link"></i> JIRA </a> </li> </ul> </div> </nav> </div> """ % (logo) body.append(BeautifulSoup(icons_txt, 'html.parser')) page_content_div = soup.new_tag('div') page_content_div["role"] = "main" page_content_div["class"] = "col-md-9 ml-sm-auto col-lg-10 px-4" body.insert(50, page_content_div) writer("\n1 of 6: Capturing dashboard content...") ### ============================ START OF DASHBOARD ======================================= #### test_stats = TestStats() result.visit(test_stats) total_suite = test_stats.total_suite passed_suite = test_stats.passed_suite failed_suite = test_stats.failed_suite suitepp = math.ceil(passed_suite * 100.0 / total_suite) elapsedtime = datetime( 1970, 1, 1) + timedelta(milliseconds=result.suite.elapsedtime) elapsedtime = elapsedtime.strftime("%X") myResult = result.generated_by_robot if myResult: generator = "Robot" else: generator = "Rebot" stats = result.statistics total = stats.total.all.total passed = stats.total.all.passed failed = stats.total.all.failed testpp = round(passed * 100.0 / total, 2) kw_stats = KeywordStats(ignore_library, ignore_type) result.visit(kw_stats) total_keywords = kw_stats.total_keywords passed_keywords = kw_stats.passed_keywords failed_keywords = kw_stats.failed_keywords # Handling ZeroDivisionError exception when no keywords are found if total_keywords > 0: kwpp = round(passed_keywords * 100.0 / total_keywords, 2) else: kwpp = 0 dashboard_content = """ <div class="tabcontent" id="dashboard"> <div class="d-flex flex-column flex-md-row align-items-center p-1 mb-3 bg-light border-bottom shadow-sm"> <h5 class="my-0 mr-md-auto font-weight-normal"><i class="fa fa-dashboard"></i> Dashboard</h5> <nav class="my-2 my-md-0 mr-md-3" style="color:red"> <a class="p-2"><b style="color:black;">Execution Time: </b>%s h</a> <a class="p-2"><b style="color:black;cursor: pointer;" data-toggle="tooltip" title=".xml file is created by">Generated By: </b>%s</a> </nav> </div> <div class="row"> <div class="col-md-3" onclick="openPage('suiteMetrics', this, '')" data-toggle="tooltip" title="Click to view Suite metrics" style="cursor: pointer;"> <a class="tile tile-head"> Suite <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <div class="row"> <div class="col-md-3" onclick="openPage('testMetrics', this, '')" data-toggle="tooltip" title="Click to view Test metrics" style="cursor: pointer;"> <a class="tile tile-head"> Test <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <div class="row"> <div class="col-md-3" onclick="openPage('keywordMetrics', this, '')" data-toggle="tooltip" title="Click to view Keyword metrics" style="cursor: pointer;"> <a class="tile tile-head"> Keyword <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <hr></hr> <div class="row"> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Suite Status:</span> <div id="suiteChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Test Status:</span> <div id="testChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Keyword Status:</span> <div id="keywordChartID" style="height:250px;width:auto;"></div> </div> </div> <hr></hr> <div class="row"> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Suite Performance(sec):</span> <div id="suiteBarID" style="height:400px;width:auto;"></div> </div> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Test Performance(sec):</span> <div id="testsBarID" style="height:400px;width:auto;"></div> </div> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Keywords Performance(sec):</span> <div id="keywordsBarID" style="height:400px;width:auto;"></div> </div> </div> <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> <p class="text-muted" style="text-align:center;font-size:9px">robotframework-metrics</p> </div> </div> <script> window.onload = function(){ executeDataTable('#sm',5); executeDataTable('#tm',3); executeDataTable('#km',3); createPieChart(%s,%s,'suiteChartID','Suite Status:'); createBarGraph('#sm',0,5,10,'suiteBarID','Elapsed Time(s): ','Suite'); createPieChart(%s,%s,'testChartID','Tests Status:'); createBarGraph('#tm',1,3,10,'testsBarID','Elapsed Time(s): ','Test'); createPieChart(%s,%s,'keywordChartID','Keywords Status:'); createBarGraph('#km',1,3,10,'keywordsBarID','Elapsed Time(s): ','Keyword'); }; </script> <script> function openInNewTab(url,element_id) { var element_id= element_id; var win = window.open(url, '_blank'); win.focus(); $('body').scrollTo(element_id); } </script> </div> """ % (elapsedtime, generator, total_suite, passed_suite, failed_suite, total, passed, failed, total_keywords, passed_keywords, failed_keywords, passed_suite, failed_suite, passed, failed, passed_keywords, failed_keywords) page_content_div.append(BeautifulSoup(dashboard_content, 'html.parser')) ### ============================ END OF DASHBOARD ============================================ #### writer("\n2 of 6: Capturing suite metrics...") ### ============================ START OF SUITE METRICS ======================================= #### # Tests div suite_div = soup.new_tag('div') suite_div["id"] = "suiteMetrics" suite_div["class"] = "tabcontent" page_content_div.insert(50, suite_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Suite Metrics</b></h4> <hr></hr> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "sm" table["class"] = "table table-striped table-bordered" suite_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Status" tr.insert(1, th) th = soup.new_tag('th') th.string = "Total" tr.insert(2, th) th = soup.new_tag('th') th.string = "Pass" tr.insert(3, th) th = soup.new_tag('th') th.string = "Fail" tr.insert(4, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(5, th) suite_tbody = soup.new_tag('tbody') table.insert(11, suite_tbody) ### =============== GET SUITE METRICS =============== ### if group: group.spawn(result.visit, SuiteResults(soup, suite_tbody, log_name)) else: result.visit(SuiteResults(soup, suite_tbody, log_name)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF SUITE METRICS ============================================ #### writer("\n3 of 6: Capturing test metrics...") ### ============================ START OF TEST METRICS ======================================= #### # Tests div tm_div = soup.new_tag('div') tm_div["id"] = "testMetrics" tm_div["class"] = "tabcontent" page_content_div.insert(100, tm_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Test Metrics</b></h4> <hr></hr> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "tm" table["class"] = "table table-striped table-bordered" tm_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) test_tbody = soup.new_tag('tbody') table.insert(11, test_tbody) ### =============== GET TEST METRICS =============== ### if group: group.spawn(result.visit, TestResults(soup, test_tbody, log_name)) else: result.visit(TestResults(soup, test_tbody, log_name)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF TEST METRICS ============================================ #### writer("\n4 of 6: Capturing keyword metrics...") ### ============================ START OF KEYWORD METRICS ======================================= #### # Keywords div km_div = soup.new_tag('div') km_div["id"] = "keywordMetrics" km_div["class"] = "tabcontent" page_content_div.insert(150, km_div) keyword_icon_txt = """ <h4><b><i class="fa fa-table"></i> Keyword Metrics</b></h4> <hr></hr> """ km_div.append(BeautifulSoup(keyword_icon_txt, 'html.parser')) # Create table tag # <table id="myTable"> table = soup.new_tag('table') table["id"] = "km" table["class"] = "table table-striped table-bordered" km_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Keyword" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) kw_tbody = soup.new_tag('tbody') table.insert(1, kw_tbody) if group: group.spawn( result.visit, KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) group.join() else: result.visit( KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ km_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF KEYWORD METRICS ======================================= #### ### ============================ START OF LOGS ====================================== ### # Logs div log_div = soup.new_tag('div') log_div["id"] = "log" log_div["class"] = "tabcontent" page_content_div.insert(200, log_div) test_icon_txt = """ <p style="text-align:right">** <b>Report.html</b> and <b>Log.html</b> need to be in current folder in order to display here</p> <div class="embed-responsive embed-responsive-4by3"> <iframe class="embed-responsive-item" src=%s></iframe> </div> """ % (log_name) log_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF LOGS ======================================= #### ### ============================ EMAIL STATISTICS ================================== ### # Statistics div statisitcs_div = soup.new_tag('div') statisitcs_div["id"] = "statistics" statisitcs_div["class"] = "tabcontent" page_content_div.insert(300, statisitcs_div) emailStatistics = """ <h4><b><i class="fa fa-envelope-o"></i> Email Statistics</b></h4> <hr></hr> <button id="create" class="btn btn-primary active inner" role="button" onclick="updateTextArea();this.style.visibility= 'hidden';"><i class="fa fa-cogs"></i> Generate Statistics Email</button> <a download="message.eml" class="btn btn-primary active inner" role="button" id="downloadlink" style="display: none; width: 300px;"><i class="fa fa-download"></i> Click Here To Download Email</a> <script> function updateTextArea() { var suite = "<b>Top 10 Suite Performance:</b><br><br>" + $("#suiteBarID table")[0].outerHTML; var test = "<b>Top 10 Test Performance:</b><br><br>" + $("#testsBarID table")[0].outerHTML; var keyword ="<b>Top 10 Keyword Performance:</b><br><br>" + $("#keywordsBarID table")[0].outerHTML; var saluation="<pre><br>Please refer RF Metrics Report for detailed statistics.<br><br>Regards,<br>QA Team</pre></body></html>"; document.getElementById("textbox").value += "<br>" + suite + "<br>" + test + "<br>" + keyword + saluation; $("#create").click(function(){ $(this).remove(); }); } </script> <textarea id="textbox" class="col-md-12" style="height: 400px; padding:1em;"> To: [email protected] Subject: Automation Execution Status X-Unsent: 1 Content-Type: text/html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Test Email Sample</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0 " /> <style> body { background-color:#F2F2F2; } body, html, table,pre,b { font-family: Calibri, Arial, sans-serif; font-size: 1em; } .pastdue { color: crimson; } table { border: 1px solid silver; padding: 6px; margin-left: 30px; width: 600px; } thead { text-align: center; font-size: 1.1em; background-color: #B0C4DE; font-weight: bold; color: #2D2C2C; } tbody { text-align: center; } th { width: 25%%; word-wrap:break-word; } </style> </head> <body><pre>Hi Team, Following are the last build execution statistics. <b>Metrics:<b> </pre> <table> <thead> <th style="width: 25%%;">Statistics</th> <th style="width: 25%%;">Total</th> <th style="width: 25%%;">Pass</th> <th style="width: 25%%;">Fail</th> </thead> <tbody> <tr> <td style="text-align: left;font-weight: bold;"> SUITE </td> <td style="background-color: #F5DEB3;text-align: center;">%s</td> <td style="background-color: #90EE90;text-align: center;">%s</td> <td style="background-color: #F08080;text-align: center;">%s</td> </tr> <tr> <td style="text-align: left;font-weight: bold;"> TESTS </td> <td style="background-color: #F5DEB3;text-align: center;">%s</td> <td style="background-color: #90EE90;text-align: center;">%s</td> <td style="background-color: #F08080;text-align: center;">%s</td> </tr> <tr> <td style="text-align: left;font-weight: bold;"> KEYWORDS </td> <td style="background-color: #F5DEB3;text-align: center;">%s</td> <td style="background-color: #90EE90;text-align: center;">%s</td> <td style="background-color: #F08080;text-align: center;">%s</td> </tr> </tbody> </table> </textarea> """ % (total_suite, passed_suite, failed_suite, total, passed, failed, total_keywords, passed_keywords, failed_keywords) statisitcs_div.append(BeautifulSoup(emailStatistics, 'html.parser')) ### ============================ END OF EMAIL STATISTICS ================================== ### script_text = """ <script> (function () { var textFile = null, makeTextFile = function (text) { var data = new Blob([text], {type: 'text/plain'}); if (textFile !== null) { window.URL.revokeObjectURL(textFile); } textFile = window.URL.createObjectURL(data); return textFile; }; var create = document.getElementById('create'), textbox = document.getElementById('textbox'); create.addEventListener('click', function () { var link = document.getElementById('downloadlink'); link.href = makeTextFile(textbox.value); link.style.display = 'block'; }, false); })(); </script> <script> function createPieChart(passed_count,failed_count,ChartID,ChartName){ var status = []; status.push(['Status', 'Percentage']); status.push(['PASS',parseInt(passed_count)],['FAIL',parseInt(failed_count)]); var data = google.visualization.arrayToDataTable(status); var options = { pieHole: 0.6, legend: 'none', chartArea: {width: "95%",height: "90%"}, colors: ['green', 'red'], }; var chart = new google.visualization.PieChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function createBarGraph(tableID,keyword_column,time_column,limit,ChartID,Label,type){ var status = []; css_selector_locator = tableID + ' tbody >tr' var rows = $(css_selector_locator); var columns; var myColors = [ '#4F81BC', '#C0504E', '#9BBB58', '#24BEAA', '#8064A1', '#4AACC5', '#F79647', '#815E86', '#76A032', '#34558B' ]; status.push([type, Label,{ role: 'annotation'}, {role: 'style'}]); for (var i = 0; i < rows.length; i++) { if (i == Number(limit)){ break; } //status = []; name_value = $(rows[i]).find('td'); time=($(name_value[Number(time_column)]).html()).trim(); keyword=($(name_value[Number(keyword_column)]).html()).trim(); status.push([keyword,parseFloat(time),parseFloat(time),myColors[i]]); } var data = google.visualization.arrayToDataTable(status); var options = { legend: 'none', chartArea: {width: "92%",height: "75%"}, bar: { groupWidth: '90%' }, annotations: { alwaysOutside: true, textStyle: { fontName: 'Comic Sans MS', fontSize: 13, bold: true, italic: true, color: "black", // The color of the text. }, }, hAxis: { textStyle: { fontName: 'Arial', fontSize: 10, } }, vAxis: { gridlines: { count: 10 }, textStyle: { fontName: 'Comic Sans MS', fontSize: 10, } }, }; // Instantiate and draw the chart. var chart = new google.visualization.ColumnChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function executeDataTable(tabname,sortCol) { var fileTitle; switch(tabname) { case "#sm": fileTitle = "SuiteMetrics"; break; case "#tm": fileTitle = "TestMetrics"; break; case "#km": fileTitle = "KeywordMetrics"; break; default: fileTitle = "metrics"; } $(tabname).DataTable( { retrieve: true, "order": [[ Number(sortCol), "desc" ]], dom: 'l<".margin" B>frtip', buttons: [ 'copy', { extend: 'csv', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'excel', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'pdf', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'print', title : '', }, ], } ); } </script> <script> function openPage(pageName,elmnt,color) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } tablinks = document.getElementsByClassName("tablink"); for (i = 0; i < tablinks.length; i++) { tablinks[i].style.backgroundColor = ""; } document.getElementById(pageName).style.display = "block"; elmnt.style.backgroundColor = color; } // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> $(window).on('load',function(){$('.loader').fadeOut();}); </script> """ body.append(BeautifulSoup(script_text, 'html.parser')) ### ====== WRITE TO RF_METRICS_REPORT.HTML ===== ### # Write output as html file with open(result_file, 'w') as outfile: outfile.write(soup.prettify()) # Wait for 2 seconds - File is generated time.sleep(2) # ====== EMAIL CONTENT ========== # email_content = """ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Robotframework Metrics</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0 " /> <style> body { background-color:#F2F2F2; } body, html, table,span,b { font-family: Calibri, Arial, sans-serif; font-size: 1em; } .pastdue { color: crimson; } table { border: 1px solid silver; padding: 6px; margin-left: 30px; width: 600px; } thead { text-align: center; font-size: 1.1em; background-color: #B0C4DE; font-weight: bold; color: #2D2C2C; } tbody { text-align: center; } th { word-wrap:break-word; } td { height: 25px; } .dt-buttons { margin-left: 30px; } </style> </head> <body> <span>Hi Team,<br>Following are the last build execution status.<br><br><b>Metrics:<b><br><br></span> <table> <thead> <th style="width: 25vh;"> Stats </th> <th style="width: 20vh;"> Total </th> <th style="width: 20vh;"> Pass </th> <th style="width: 20vh;"> Fail </th> <th style="width: 15vh;"> Perc (%%)</th> </thead> <tbody> <tr> <td style="text-align: left;font-weight: bold;"> SUITE </td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> </tr> <tr> <td style="text-align: left;font-weight: bold;"> TESTS </td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> </tr> <tr> <td style="text-align: left;font-weight: bold;"> KEYWORDS </td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> <td style="text-align: center;">%s</td> </tr> </tbody> </table> <span><br><b>Info:<b><br><br></span> <table> <tbody> <tr> <td style="text-align: left;font-weight: normal;width: 30vh;"> Execution Time </td> <td style="text-align: center;font-weight: normal;">%s h</td> </tr> <tr> <td style="text-align: left;font-weight: normal;width: 50vh;"> Generated By </td> <td style="text-align: center;font-weight: normal;">%s</td> </tr> </tbody> </table> <span style="text-align: left;font-weight: normal;"><br>Please refer robotframework-metrics report for detailed info.<br><br>Regards,<br>QA Team</span> </body></html> """ % (total_suite, passed_suite, failed_suite, suitepp, total, passed, failed, testpp, total_keywords, passed_keywords, failed_keywords, kwpp, elapsedtime, generator) #msg.set_payload(email_content) msg.attach(MIMEText(email_content, 'html')) # Attach robotframework file rfmetrics = MIMEBase('application', "octet-stream") rfmetrics.set_payload(open(result_file, "rb").read()) encoders.encode_base64(rfmetrics) attachmentName = 'attachment; filename=%s' % (result_file_name) rfmetrics.add_header('Content-Disposition', attachmentName) msg.attach(rfmetrics) if send_email: # Start server server.starttls() writer("\n5 of 6: Sending email with robotmetrics.html...") # Login Credentials for sending the mail server.login(msg['From'], password) server.sendmail(sender, recipients, msg.as_string()) writer("\n6 of 6: Email sent successfully!") else: writer("\n6 of 6: Skipping step 5 (send email)!") writer( "\nResults file created successfully and can be found at {}\n".format( result_file))
def check_tests(seconds, inpath, outpath=None): result = ExecutionResult(inpath) result.visit(ExecutionTimeChecker(float(seconds))) result.save(outpath)
Usage as a script: {tool} path/to/output.xml max_seconds Usage as a modifier: robot --prerebotmodifier {tool}:max_seconds path/to/tests.robot rebot --prerebotmodifier {tool}:max_seconds path/to/output.xml """ import sys from robot.api import SuiteVisitor, ExecutionResult class FailSlowTests(SuiteVisitor): def __init__(self, max_seconds): self.max_seconds = float(max_seconds) def visit_test(self, test): if test.passed and test.elapsedtime > self.max_seconds * 1000: test.status = 'FAIL' test.message = 'Test was slower than %0.3f s.' % self.max_seconds if __name__ == '__main__': if len(sys.argv) != 3: sys.exit(__doc__.format(tool=sys.argv[0])) path, max_time = sys.argv[1:] result = ExecutionResult(path) result.suite.visit(FailSlowTests(max_time)) result.save() sys.exit(result.return_code)
def generate_report(opts): logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) group = Group() if not FAILED_IMPORT else '' # START OF CUSTOMIZE REPORT # URL or filepath of your company logo logo = opts.logo # Ignores following library keywords in metrics report ignore_library = IGNORE_LIBRARIES if opts.ignore: ignore_library.extend(opts.ignore) # Ignores following type keywords in metrics report ignore_type = IGNORE_TYPES if opts.ignoretype: ignore_type.extend(opts.ignoretype) # END OF CUSTOMIZE REPORT # Report to support file location as arguments # Source Code Contributed By : Ruud Prijs # input directory path = os.path.abspath(os.path.expanduser(opts.path)) # output.xml files output_names = [] # support "*.xml" of output files if (opts.output == "*.xml"): for item in os.listdir(path): if os.path.isfile(item) and item.endswith('.xml'): output_names.append(item) else: for curr_name in opts.output.split(","): curr_path = os.path.join(path, curr_name) output_names.append(curr_path) # log.html file log_name = opts.log_name # copy the list of output_names onto the one of required_files; the latter may (in the future) # contain files that should not be processed as output_names required_files = list(output_names) missing_files = [ filename for filename in required_files if not os.path.exists(filename) ] if missing_files: # We have files missing. exit("output.xml file is missing: {}".format(", ".join(missing_files))) mt_time = datetime.now().strftime('%Y%m%d-%H%M%S') # Output result file location if opts.metrics_report_name: result_file_name = opts.metrics_report_name else: result_file_name = 'metrics-' + mt_time + '.html' result_file = os.path.join(path, result_file_name) # Read output.xml file result = ExecutionResult(*output_names) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) logging.info("Converting .xml to .html file. This may take few minutes...") head_content = """ <!doctype html><html lang="en"> <head> <link rel="shortcut icon" href="https://png.icons8.com/windows/50/000000/bot.png" type="image/x-icon" /> <title>RF Metrics</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet"/> <link href="https://cdn.datatables.net/buttons/1.5.2/css/buttons.dataTables.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.3.1.js" type="text/javascript"></script> <!-- Bootstrap core Googleccharts --> <script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script> <script type="text/javascript">google.charts.load('current', {packages: ['corechart']});</script> <!-- Bootstrap core Datatable--> <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/dataTables.buttons.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.flash.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.html5.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.print.min.js" type="text/javascript"></script> <style> body { font-family: -apple-system,sans-serif; } .sidenav { height: 100%; width: 220px; position: fixed; z-index: 1; top: 0; left: 0; background-color: white; overflow-x: hidden; border-style: ridge; } .sidenav a { padding: 12px 10px 8px 12px; text-decoration: none; font-size: 18px; color: Black; display: block; } .main { padding-top: 10px; } @media screen and (max-height: 450px) { .sidenav {padding-top: 15px;} .sidenav a {font-size: 18px;} } .tile { width: 100%; float: left; margin: 0px; list-style: none; font-size: 30px; color: #FFF; -moz-border-radius: 5px; -webkit-border-radius: 5px; margin-bottom: 5px; position: relative; text-align: center; color: white!important; } .tile.tile-fail { background: #f44336!important; } .tile.tile-pass { background: #4CAF50!important; } .tile.tile-info { background: #009688!important; } .tile.tile-head { background: #616161!important; } .dt-buttons { margin-left: 5px; } .loader { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; z-index: 9999; background: url('https://www.downgraf.com/wp-content/uploads/2014/09/02-loading-blossom-2x.gif') 50% 50% no-repeat rgb(249,249,249); } </style> </head> """ soup = BeautifulSoup(head_content, "html.parser") body = soup.new_tag('body') soup.insert(20, body) icons_txt = """ <div class="loader"></div> <div class="sidenav"> <a> <img src="%s" style="height:20vh;max-width:98%%;"/> </a> <a class="tablink" href="#" id="defaultOpen" onclick="openPage('dashboard', this, 'orange')"><i class="fa fa-dashboard"></i> Dashboard</a> <a class="tablink" href="#" onclick="openPage('suiteMetrics', this, 'orange'); executeDataTable('#sm',5)"><i class="fa fa-th-large"></i> Suite Metrics</a> <a class="tablink" href="#" onclick="openPage('testMetrics', this, 'orange'); executeDataTable('#tm',3)"><i class="fa fa-list-alt"></i> Test Metrics</a> <a class="tablink" href="#" onclick="openPage('keywordMetrics', this, 'orange'); executeDataTable('#km',3)"><i class="fa fa-table"></i> Keyword Metrics</a> <a class="tablink" href="#" onclick="openPage('log', this, 'orange');"><i class="fa fa-wpforms"></i> Logs</a> <a class="tablink" href="#" onclick="openPage('statistics', this, 'orange');"><i class="fa fa-envelope-o"></i> Email</a> </div> """ % logo body.append(BeautifulSoup(icons_txt, 'html.parser')) page_content_div = soup.new_tag('div') page_content_div["class"] = "main col-md-9 ml-sm-auto col-lg-10 px-4" body.insert(50, page_content_div) logging.info("1 of 4: Capturing dashboard content...") test_stats = TestStats() result.visit(test_stats) total_suite = test_stats.total_suite passed_suite = test_stats.passed_suite failed_suite = test_stats.failed_suite suitepp = round(passed_suite * 100.0 / total_suite, 1) suitefp = round(failed_suite * 100.0 / total_suite, 1) elapsedtime = datetime( 1970, 1, 1) + timedelta(milliseconds=result.suite.elapsedtime) elapsedtime = elapsedtime.strftime("%X") my_results = result.generated_by_robot if my_results: generator = "Robot" else: generator = "Rebot" stats = result.statistics total = stats.total.all.total passed = stats.total.all.passed failed = stats.total.all.failed testpp = round(passed * 100.0 / total, 1) testfp = round(failed * 100.0 / total, 1) kw_stats = KeywordStats(ignore_library, ignore_type) result.visit(kw_stats) total_keywords = kw_stats.total_keywords passed_keywords = kw_stats.passed_keywords failed_keywords = kw_stats.failed_keywords # Handling ZeroDivisionError exception when no keywords are found if total_keywords > 0: kwpp = round(passed_keywords * 100.0 / total_keywords, 1) kwfp = round(failed_keywords * 100.0 / total_keywords, 1) else: kwpp = 0 kwfp = 0 dashboard_content = """ <div class="tabcontent" id="dashboard"> <div id="stats_screenshot_area"> <div class="d-flex flex-column flex-md-row align-items-center p-1 mb-3 bg-light border-bottom shadow-sm"> <h5 class="my-0 mr-md-auto font-weight-normal"><i class="fa fa-dashboard"></i> Dashboard</h5> <nav class="my-2 my-md-0 mr-md-3" style="color:red"> <a class="p-2"><b style="color:black;">Execution Time: </b>%s h</a> <a class="p-2"><b style="color:black;cursor: pointer;" data-toggle="tooltip" title=".xml file is created by">Generated By: </b>%s</a> </nav> </div> <div class="row"> <div class="col-md-3" onclick="openPage('suiteMetrics', this, '')" data-toggle="tooltip" title="Click to view Suite metrics" style="cursor: pointer;"> <a class="tile tile-head"> Suite <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <div class="row"> <div class="col-md-3" onclick="openPage('testMetrics', this, '')" data-toggle="tooltip" title="Click to view Test metrics" style="cursor: pointer;"> <a class="tile tile-head"> Test <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <div class="row"> <div class="col-md-3" onclick="openPage('keywordMetrics', this, '')" data-toggle="tooltip" title="Click to view Keyword metrics" style="cursor: pointer;"> <a class="tile tile-head"> Keyword <p style="font-size:12px">Statistics</p> </a> </div> <div class="col-md-3"> <a class="tile tile-info"> %s <p style="font-size:12px">Total</p> </a> </div> <div class="col-md-3"> <a class="tile tile-pass"> %s <p style="font-size:12px">Pass</p> </a> </div> <div class="col-md-3"> <a class="tile tile-fail"> %s <p style="font-size:12px">Fail</p> </a> </div> </div> <hr></hr> <div class="row"> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Suite Status:</span> <div id="suiteChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Test Status:</span> <div id="testChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="background-color:white;height:280px;width:auto;border:groove;"> <span style="font-weight:bold">Keyword Status:</span> <div id="keywordChartID" style="height:250px;width:auto;"></div> </div> </div> </div> <hr></hr> <div class="row"> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Suite Performance(sec):</span> <div id="suiteBarID" style="height:400px;width:auto;"></div> </div> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Test Performance(sec):</span> <div id="testsBarID" style="height:400px;width:auto;"></div> </div> <div class="col-md-12" style="background-color:white;height:450px;width:auto;border:groove;"> <span style="font-weight:bold">Top 10 Keywords Performance(sec):</span> <div id="keywordsBarID" style="height:400px;width:auto;"></div> </div> </div> <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> <p class="text-muted" style="text-align:center;font-size:9px"> <a target="_blank" href="https://github.com/adiralashiva8/robotframework-metrics"> robotframework-metrics </a> </p> </div> </div> <script> window.onload = function(){ executeDataTable('#sm',5); executeDataTable('#tm',3); executeDataTable('#km',3); createPieChart(%s,%s,'suiteChartID','Suite Status:'); createBarGraph('#sm',0,5,10,'suiteBarID','Elapsed Time (s) ','Suite'); createPieChart(%s,%s,'testChartID','Tests Status:'); createBarGraph('#tm',1,3,10,'testsBarID','Elapsed Time (s) ','Test'); createPieChart(%s,%s,'keywordChartID','Keywords Status:'); createBarGraph('#km',1,3,10,'keywordsBarID','Elapsed Time (s) ','Keyword'); }; </script> <script> function openInNewTab(url,element_id) { var element_id= element_id; var win = window.open(url, '_blank'); win.focus(); $('body').scrollTo(element_id); } </script> </div> """ % (elapsedtime, generator, total_suite, passed_suite, failed_suite, total, passed, failed, total_keywords, passed_keywords, failed_keywords, passed_suite, failed_suite, passed, failed, passed_keywords, failed_keywords) page_content_div.append(BeautifulSoup(dashboard_content, 'html.parser')) ### ============================ END OF DASHBOARD ============================================ #### logging.info("2 of 4: Capturing suite metrics...") ### ============================ START OF SUITE METRICS ======================================= #### # Tests div suite_div = soup.new_tag('div') suite_div["id"] = "suiteMetrics" suite_div["class"] = "tabcontent" page_content_div.insert(50, suite_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Suite Metrics</b></h4> <hr></hr> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "sm" table["class"] = "table table-striped table-bordered" suite_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Status" tr.insert(1, th) th = soup.new_tag('th') th.string = "Total" tr.insert(2, th) th = soup.new_tag('th') th.string = "Pass" tr.insert(3, th) th = soup.new_tag('th') th.string = "Fail" tr.insert(4, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(5, th) suite_tbody = soup.new_tag('tbody') table.insert(11, suite_tbody) # GET SUITE METRICS if group: group.spawn(result.visit, SuiteResults(soup, suite_tbody, log_name)) else: result.visit(SuiteResults(soup, suite_tbody, log_name)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF SUITE METRICS ============================================ #### logging.info("3 of 4: Capturing test metrics...") ### ============================ START OF TEST METRICS ======================================= #### # Tests div tm_div = soup.new_tag('div') tm_div["id"] = "testMetrics" tm_div["class"] = "tabcontent" page_content_div.insert(100, tm_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Test Metrics</b></h4> <hr></hr> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "tm" table["class"] = "table table-striped table-bordered" tm_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) th = soup.new_tag('th') th.string = "Error Message" tr.insert(4, th) test_tbody = soup.new_tag('tbody') table.insert(11, test_tbody) # GET TEST METRICS if group: group.spawn(result.visit, TestResults(soup, test_tbody, log_name)) else: result.visit(TestResults(soup, test_tbody, log_name)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF TEST METRICS ============================================ #### logging.info("4 of 4: Capturing keyword metrics...") ### ============================ START OF KEYWORD METRICS ======================================= #### # Keywords div km_div = soup.new_tag('div') km_div["id"] = "keywordMetrics" km_div["class"] = "tabcontent" page_content_div.insert(150, km_div) keyword_icon_txt = """ <h4><b><i class="fa fa-table"></i> Keyword Metrics</b></h4> <hr></hr> """ km_div.append(BeautifulSoup(keyword_icon_txt, 'html.parser')) # Create table tag # <table id="myTable"> table = soup.new_tag('table') table["id"] = "km" table["class"] = "table table-striped table-bordered" km_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Keyword" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) kw_tbody = soup.new_tag('tbody') table.insert(1, kw_tbody) if group: group.spawn( result.visit, KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) group.join() else: result.visit( KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> </div> </div> """ km_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # END OF KEYWORD METRICS # START OF LOGS # Logs div log_div = soup.new_tag('div') log_div["id"] = "log" log_div["class"] = "tabcontent" page_content_div.insert(200, log_div) test_icon_txt = """ <p style="text-align:right">** <b>Report.html</b> and <b>Log.html</b> need to be in current folder in order to display here</p> <div class="embed-responsive embed-responsive-4by3"> <iframe class="embed-responsive-item" src=%s></iframe> </div> """ % log_name log_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # END OF LOGS # EMAIL STATISTICS # Statistics div statisitcs_div = soup.new_tag('div') statisitcs_div["id"] = "statistics" statisitcs_div["class"] = "tabcontent" page_content_div.insert(300, statisitcs_div) emailStatistics = """ <h4><b><i class="fa fa-envelope-o"></i> Email Statistics</b></h4> <hr></hr> <button id="create" class="btn btn-primary active inner" role="button" onclick="this.style.visibility= 'hidden';"><i class="fa fa-cogs"></i> Generate Statistics Email</button> <a download="message.eml" class="btn btn-primary active inner" role="button" id="downloadlink" style="display: none; width: 300px;"><i class="fa fa-download"></i> Click Here To Download Email</a> <textarea id="textbox" class="col-md-12" style="height: 400px; padding:1em;"> To: [email protected] Subject: Automation Execution Status X-Unsent: 1 Content-Type: text/html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Test Email Sample</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0 " /> <style> body { background-color:#F2F2F2; } body, html, table { font-family: Courier New, Arial, sans-serif; font-size: 1em; } .pastdue { color: crimson; } table { padding: 5px; margin-left: 30px; width: 800px; } thead { text-align: center; font-size: 1.1em; background-color: #B0C4DE; font-weight: bold; color: #2D2C2C; } tbody { text-align: center; } th { width: 25%; word-wrap:break-word; } </style> </head> <body> <p>Hi Team,</p> <p>Following are the last build execution status</p> <p></p> <table> <tbody> <tr> <td style="text-align:left; padding-left:5px;color:#0b6690;"> <h2>Test Automation Report</h2> </td> <td style="text-align:right; padding-right:10px;color:#0b6690;"> <h3>Duration: elapsed_time</h3> </td> </tr> </tbody> </table> <table> <tr> <td></td> </tr> </table> <table> <tbody> <tr> <td style="background-color:#616161; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">Suite</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Statistics</td> </tr> </tbody> </table> </td> <td style="background-color:#009688; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">suite_total</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Total</td> </tr> </tbody> </table> </td> <td style="background-color:#4CAF50; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">suite_pass</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Pass</td> </tr> </tbody> </table> </td> <td style="background-color:#f44336; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">suite_fail</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Fail</td> </tr> </tbody> </table> </td> </tr> <tr> <td style="background-color:#616161; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">Test</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Statistics</td> </tr> </tbody> </table> </td> <td style="background-color:#009688; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">test_total</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Total</td> </tr> </tbody> </table> </td> <td style="background-color:#4CAF50; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">test_pass</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Pass</td> </tr> </tbody> </table> </td> <td style="background-color:#f44336; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">test_fail</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Fail</td> </tr> </tbody> </table> </td> </tr> <tr> <td style="background-color:#616161; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">Keyword</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Statistics</td> </tr> </tbody> </table> </td> <td style="background-color:#009688; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">keyword_total</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Total</td> </tr> </tbody> </table> </td> <td style="background-color:#4CAF50; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">keyword_pass</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Pass</td> </tr> </tbody> </table> </td> <td style="background-color:#f44336; color:white; width:25%"> <table style="width:100%;"> <tbody> <tr> <td style="text-align:center; color:white;font-size: 30px;">keyword_fail</td> </tr> <tr> <td style="text-align:center; color:white;font-size: 12px;">Fail</td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <table> <tr> <td></td> </tr> </table> <table> <tbody> <tr> <td style="width:33%;color:#0b6690;"><h3>Suite Status</h3></td> <td style="width:33%;color:#0b6690;"><h3>Test Status</h3></td> <td style="width:33%;color:#0b6690;"><h3>Keyword Status</h3></td> </tr> <tr> <td> <img src='https://chart.googleapis.com/chart?cht=p3&chd=t:suite-pass-perc,suite-fail-perc&chs=250x200&chco=3BB032|bc2d29&chdl=suite-pass-perc-pass|suite-fail-perc-fail'/> </td> <td> <img src='https://chart.googleapis.com/chart?cht=p3&chd=t:test-pass-perc,test-fail-perc&chs=250x200&chco=3BB032|bc2d29&chdl=test-pass-perc-pass|test-fail-perc-fail'/> </td> <td> <img src='https://chart.googleapis.com/chart?cht=p3&chd=t:keyword-pass-perc,keyword-fail-perc&chs=250x200&chco=3BB032|bc2d29&chdl=keyword-pass-perc-pass|keyword-fail-perc-fail'/> </td> </tr> </tbody> </table> <p>Please refer RF Metrics report for detailed statistics.<p> <strong>Team QA</strong> </body></html></textarea> """ emailStatistics = emailStatistics.replace("suite_total", str(total_suite)) emailStatistics = emailStatistics.replace("suite_pass", str(passed_suite)) emailStatistics = emailStatistics.replace("suite_fail", str(failed_suite)) emailStatistics = emailStatistics.replace("test_total", str(total)) emailStatistics = emailStatistics.replace("test_pass", str(passed)) emailStatistics = emailStatistics.replace("test_fail", str(failed)) emailStatistics = emailStatistics.replace("keyword_total", str(total_keywords)) emailStatistics = emailStatistics.replace("keyword_pass", str(passed_keywords)) emailStatistics = emailStatistics.replace("keyword_fail", str(failed_keywords)) emailStatistics = emailStatistics.replace("elapsed_time", str(elapsedtime)) emailStatistics = emailStatistics.replace("suite-pass-perc", str(suitepp)) emailStatistics = emailStatistics.replace("suite-fail-perc", str(suitefp)) emailStatistics = emailStatistics.replace("test-pass-perc", str(testpp)) emailStatistics = emailStatistics.replace("test-fail-perc", str(testfp)) emailStatistics = emailStatistics.replace("keyword-pass-perc", str(kwpp)) emailStatistics = emailStatistics.replace("keyword-fail-perc", str(kwfp)) statisitcs_div.append(BeautifulSoup(emailStatistics, 'html.parser')) # END OF EMAIL STATISTICS script_text = """ <script> (function () { var textFile = null, makeTextFile = function (text) { var data = new Blob([text], {type: 'text/plain'}); if (textFile !== null) { window.URL.revokeObjectURL(textFile); } textFile = window.URL.createObjectURL(data); return textFile; }; var create = document.getElementById('create'), textbox = document.getElementById('textbox'); create.addEventListener('click', function () { var link = document.getElementById('downloadlink'); link.href = makeTextFile(textbox.value); link.style.display = 'block'; }, false); })(); </script> <script> function createPieChart(passed_count,failed_count,ChartID,ChartName){ var status = []; status.push(['Status', 'Percentage']); status.push(['PASS',parseInt(passed_count)],['FAIL',parseInt(failed_count)]); var data = google.visualization.arrayToDataTable(status); var options = { pieHole: 0.6, legend: 'none', chartArea: {width: "95%",height: "90%"}, colors: ['green', 'red'], }; var chart = new google.visualization.PieChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function createBarGraph(tableID,keyword_column,time_column,limit,ChartID,Label,type){ var status = []; css_selector_locator = tableID + ' tbody >tr' var rows = $(css_selector_locator); var columns; var myColors = [ '#4F81BC', '#C0504E', '#9BBB58', '#24BEAA', '#8064A1', '#4AACC5', '#F79647', '#815E86', '#76A032', '#34558B' ]; status.push([type, Label,{ role: 'annotation'}, {role: 'style'}]); for (var i = 0; i < rows.length; i++) { if (i == Number(limit)){ break; } //status = []; name_value = $(rows[i]).find('td'); time=($(name_value[Number(time_column)]).html()).trim(); keyword=($(name_value[Number(keyword_column)]).html()).trim(); status.push([keyword,parseFloat(time),parseFloat(time),myColors[i]]); } var data = google.visualization.arrayToDataTable(status); var options = { legend: 'none', chartArea: {width: "92%",height: "75%"}, bar: { groupWidth: '90%' }, annotations: { alwaysOutside: true, textStyle: { fontName: 'Comic Sans MS', fontSize: 13, bold: true, italic: true, color: "black", // The color of the text. }, }, hAxis: { textStyle: { fontName: 'Arial', fontSize: 10, } }, vAxis: { gridlines: { count: 10 }, textStyle: { fontName: 'Comic Sans MS', fontSize: 10, } }, }; // Instantiate and draw the chart. var chart = new google.visualization.ColumnChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function executeDataTable(tabname,sortCol) { var fileTitle; switch(tabname) { case "#sm": fileTitle = "SuiteMetrics"; break; case "#tm": fileTitle = "TestMetrics"; break; case "#km": fileTitle = "KeywordMetrics"; break; default: fileTitle = "metrics"; } $(tabname).DataTable( { retrieve: true, "order": [[ Number(sortCol), "desc" ]], dom: 'l<".margin" B>frtip', buttons: [ 'copy', { extend: 'csv', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'excel', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'pdf', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, title : '', }, { extend: 'print', title : '', }, ], } ); } </script> <script> function openPage(pageName,elmnt,color) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } tablinks = document.getElementsByClassName("tablink"); for (i = 0; i < tablinks.length; i++) { tablinks[i].style.backgroundColor = ""; } document.getElementById(pageName).style.display = "block"; elmnt.style.backgroundColor = color; } // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> $(window).on('load',function(){$('.loader').fadeOut();}); </script> """ body.append(BeautifulSoup(script_text, 'html.parser')) # WRITE TO RF_METRICS_REPORT.HTML # Write output as html file with open(result_file, 'w') as outfile: outfile.write(soup.prettify()) logging.info( "Results file created successfully and can be found at {}".format( result_file))
def output_file(self, path): result = ExecutionResult(path) result.visit(OxygenVisitor(self.run_time_data)) result.save()
from robot.api import ExecutionResult, ResultVisitor from robot.utils import timestamp_to_secs as ts, secs_to_timestamp as st res = ExecutionResult('output.xml') class SuiteAndTestTimes(ResultVisitor): def __init__(self): self.result_by_start_time = [] self.result_by_end_time = [] self.suite_names = set() def visit_test(self, test): self.suite_names.add(test.parent.longname) self.result_by_start_time.append( [ts(test.starttime), test.parent.longname, test.longname]) self.result_by_end_time.append( [ts(test.endtime), test.parent.longname, test.longname]) test_times = SuiteAndTestTimes() res.visit(test_times) result_by_start_time = sorted(test_times.result_by_start_time) result_by_end_time = sorted(test_times.result_by_end_time) starting, result_by_start_time = result_by_start_time[0], result_by_start_time[ 1:] ending, result_by_end_time = result_by_end_time[0], result_by_end_time[1:] currenttime = starting[0]
def process(infile="output.xml"): visitor = result_visitor.RobotResultsVisitor() test_run = ExecutionResult(infile) test_run.visit(visitor)
def get_testcases(xml_robotfwk_output): """ Return the list of Testcase ID with status """ result = ExecutionResult(xml_robotfwk_output) visitor = TestRailResultVisitor() result.visit(visitor) return visitor.result_testcase_list
def lastResults(request): try: os.environ['TEST_PATH'] except: with open('settings.yml', 'r') as outfile: stngs = yaml.load(outfile) os.environ["TEST_PATH"] = str(stngs["TestPath"]) print "Path updated!" try: path = os.path.join(os.environ['TEST_PATH'], "results") dir_list = next(os.walk(path))[1] test_dir_results = reversed(sorted(dir_list)) results_files = {} for folder in test_dir_results: run_results_path = os.path.join(path, folder) results_files[folder] = {} if os.path.isdir(run_results_path): for result_file in os.listdir(run_results_path): if os.path.splitext(result_file)[1] == ".html" and "Report" not in os.path.splitext(result_file)[0]: results_files[folder][os.path.splitext(result_file)[0]]=[] xml_file = os.path.splitext(result_file)[0]+'.xml' xml_file_path = os.path.join(path, folder, xml_file) results_files[folder][os.path.splitext(result_file)[0]].append({'failed' : ExecutionResult(xml_file_path).statistics.total.critical.failed }) results_files[folder][os.path.splitext(result_file)[0]].append({'passed' : ExecutionResult(xml_file_path).statistics.total.critical.passed }) sorted_results = reversed(sorted(results_files.items())) except: sorted_results = [] return render(request, 'lastresults.html', {"folders":sorted_results,})
def parse_output_xml(xml_file_path, csv_dir_path): result = ExecutionResult(xml_file_path) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) stats = result.statistics print "--------------------------------------" print "Total Test Count:\t",\ stats.total.critical.passed + stats.total.critical.failed print "Total Test Failed:\t", stats.total.critical.failed print "Total Test Passed:\t", stats.total.critical.passed print "Test Start Time:\t", result.suite.starttime print "Test End Time:\t\t", result.suite.endtime print "--------------------------------------" # Use ResultVisitor object and save off the test data info class TestResult(ResultVisitor): def __init__(self): self.testData = [] def visit_test(self, test): self.testData += [test] collectDataObj = TestResult() result.visit(collectDataObj) # Write the result statistics attributes to CSV file l_csvlist = [] # Default Test data l_subsys = 'OPENBMC' l_test_type = 'FTC' l_pse_rel = 'OBMC910' l_env = 'HW' l_proc = 'P9' l_platform_type = "" l_func_area = "" # System data from XML meta data l_system_info = get_system_details(xml_file_path) l_driver = l_system_info[0] if l_system_info[1]: l_platform_type = l_system_info[1] else: print "System model is not set" sys.exit() # Default header l_header = [ 'test_start', 'test_end', 'subsys', 'test_type', 'test_result', 'test_name', 'pse_rel', 'driver', 'env', 'proc', 'platform_type', 'test_func_area' ] l_csvlist.append(l_header) # Generate CSV file onto the path with current time stamp l_base_dir = csv_dir_path l_timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H-%M-%S") # Example: 2017-02-20-08-47-22_Witherspoon.csv l_csvfile = l_base_dir + l_timestamp + "_" + l_platform_type + ".csv" print "Writing data into csv file:", l_csvfile for testcase in collectDataObj.testData: # Functional Area: Suite Name # Test Name: Test Case Name l_func_area = str(testcase.parent).split(' ', 1)[1] l_test_name = str(testcase) # Test Result pass=0 fail=1 if testcase.status == 'PASS': l_test_result = 0 else: l_test_result = 1 # Format datetime from robot output.xml to "%Y-%m-%d-%H-%M-%S" l_stime = xml_to_csv_time(testcase.starttime) l_etime = xml_to_csv_time(testcase.endtime) # Data Sequence: test_start,test_end,subsys,test_type, # test_result,test_name,pse_rel,driver, # env,proc,platform_tyep,test_func_area, l_data = [ l_stime, l_etime, l_subsys, l_test_type, l_test_result, l_test_name, l_pse_rel, l_driver, l_env, l_proc, l_platform_type, l_func_area ] l_csvlist.append(l_data) # Open the file and write to the CSV file l_file = open(l_csvfile, "w") l_writer = csv.writer(l_file, lineterminator='\n') l_writer.writerows(l_csvlist) l_file.close()
print('You can also mail it to mailto:[email protected].\n') print('Thanks you very much for your support!') print('Your Browser-Team (Mikko, Tatu, Kerkko, Janne and René)') def print_stats(self, kw_calls): longest_keyword = 0 for kw_name in kw_calls: current_length = len(kw_name) longest_keyword = current_length \ if current_length > longest_keyword \ else longest_keyword print(f'+-{"".ljust(longest_keyword, "-") }-+-------+---------+') print(f'| {"Keyword".ljust(longest_keyword, " ")} | count | parents |') print(f'+-{"".ljust(longest_keyword, "-") }-+-------+---------+') for kw_name in kw_calls: print(f'| {kw_name.ljust(longest_keyword , " ")} |' f' {str(kw_calls[kw_name]["call_count"]).ljust(5," ")} |' f' {str(kw_calls[kw_name]["parent_count"]).ljust(7, " ")} |') print(f'+-{"".ljust(longest_keyword, "-")}-+-------+---------+') if __name__ == "__main__": if len(sys.argv) > 1: original_output_xml = sys.argv[1] if not os.path.isfile(original_output_xml): raise FileNotFoundError(f'{original_output_xml} is no file') print(f'reading results from: {os.path.abspath(original_output_xml)}') ExecutionResult(original_output_xml).visit(ResultAnalyzer()) else: print('Use the path to a output.xml as first arguemnt. Example: python -m SeleniumStats ../output.xml')
def _suites_from_outputxml(outputxml): res = ExecutionResult(outputxml) suite_times = SuiteNotPassingsAndTimes() res.visit(suite_times) return [suite for (_, _, suite) in reversed(sorted(suite_times.suites))]
print('Showing %d of total keywords %d' % (shown, len(times.keywords))) if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( '--limit', '-l', type=float, help='Filter out keywords with larger percentage' ' of stdev/avg time than LIMIT. This helps by filtering out most used "primitive" keywords' ' such as Sleep and Run Keyword If etc. and let\'s you focus on the keywords that very often' ' take a lot of time to execute (in other words are most fruitful places to focus optimisation effort).' ) parser.add_argument('--show', '-s', default=100, type=int, help='Max number of shown keywords. Default is 100.') parser.add_argument( 'source', help='output from a Robot Framework execution to analyze') args = parser.parse_args() try: resu = ExecutionResult(args.source) times = KeywordTimes() resu.visit(times) _print_results(times, args.show, args.limit) except: print(__doc__) raise
def generate_report(opts): path = os.path.abspath(os.path.expanduser(opts.path)) # output.xml files output_names = [] # support "*.xml" of output files if (opts.output == "*.xml"): for item in os.listdir(path): if os.path.isfile(item) and item.endswith('.xml'): output_names.append(item) else: for curr_name in opts.output.split(","): curr_path = os.path.join(path, curr_name) output_names.append(curr_path) required_files = list(output_names) missing_files = [ filename for filename in required_files if not os.path.exists(filename) ] if missing_files: # We have files missing. exit("output.xml file is missing: {}".format(", ".join(missing_files))) # Read output.xml file result = ExecutionResult(*output_names) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) print("Capturing execution results, This may take few minutes...") # connect to database mydb = connect_to_mysql_db(opts.host, opts.port, opts.username, opts.password, opts.projectname) rootdb = connect_to_mysql_db(opts.host, opts.port, opts.username, opts.password, 'robothistoric') test_stats = SuiteStats() result.visit(test_stats) stotal = test_stats.total_suite spass = test_stats.passed_suite sfail = test_stats.failed_suite stats = result.statistics total = stats.total.all.total passed = stats.total.all.passed failed = stats.total.all.failed elapsedtime = datetime.datetime( 1970, 1, 1) + datetime.timedelta(milliseconds=result.suite.elapsedtime) elapsedtime = get_time_in_min(elapsedtime.strftime("%X")) elapsedtime = float("{0:.2f}".format(elapsedtime)) # insert test results info into db result_id = insert_into_execution_table(mydb, rootdb, opts.executionname, total, passed, failed, elapsedtime, stotal, spass, sfail, opts.projectname) print("INFO: Capturing suite results") result.visit(SuiteResults(mydb, result_id)) print("INFO: Capturing test results") result.visit(TestMetrics(mydb, result_id)) print("INFO: Writing execution results") commit_and_close_db(mydb)
def output_file(self, path): result = ExecutionResult(path) result.visit(ScreenShotVisitor(self.keywords, self.args[0])) result.save(path)
def get_task_list(app, username, project): job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s" % (username, project) next_build = 0 task = [] if exists_path(job_path): next_build = get_next_build_number(job_path) if next_build != 0: # 遍历所有任务结果 # 判断最近一个任务状态 icons = { "running": url_for('static', filename='img/running.gif'), "success": url_for('static', filename='img/success.png'), "fail": url_for('static', filename='img/fail.png'), "exception": url_for('static', filename='img/exception.png') } #if exists_path(job_path + "/%s" % (next_build - 1)): running = False lock = threading.Lock() lock.acquire() remove_robot(app) for p in app.config["AUTO_ROBOT"]: if p["name"] == project: running = True break lock.release() if running: task.append({ "status": icons["running"], "name": "%s_#%s" % (project, next_build - 1), "success": "", "fail": "" }) last = 1 if running: last = 2 for i in range(next_build - last, -1, -1): if exists_path(job_path + "/%s" % i): try: suite = ExecutionResult(job_path + "/%s/output.xml" % i).suite stat = suite.statistics.critical if stat.failed != 0: status = icons["fail"] else: status = icons['success'] task.append({ "task_no": i, "status": status, "name": "<a href='/view_report/%s/%s' target='_blank'>%s_#%s</a>" % (project, i, project, i), "success": stat.passed, "fail": stat.failed, "starttime": suite.starttime, "endtime": suite.endtime, "elapsedtime": suite.elapsedtime, "note": "" }) except: status = icons["exception"] if i == next_build - last: status = icons["running"] task.append({ "task_no": i, "status": status, "name": "%s_#%s" % (project, i), "success": "-", "fail": "-", "starttime": "-", "endtime": "-", "elapsedtime": "-", "note": "异常" }) return {"total": next_build - 1, "rows": task}
def parse_output_xml(xml_file_path, csv_dir_path): result = ExecutionResult(xml_file_path) result.configure(stat_config={'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother'}) stats = result.statistics print "--------------------------------------" print "Total Test Count:\t",\ stats.total.critical.passed + stats.total.critical.failed print "Total Test Failed:\t", stats.total.critical.failed print "Total Test Passed:\t", stats.total.critical.passed print "Test Start Time:\t", result.suite.starttime print "Test End Time:\t\t", result.suite.endtime print "--------------------------------------" # Use ResultVisitor object and save off the test data info class TestResult(ResultVisitor): def __init__(self): self.testData = [] def visit_test(self, test): self.testData += [test] collectDataObj = TestResult() result.visit(collectDataObj) # Write the result statistics attributes to CSV file l_csvlist = [] # Default Test data l_subsys = 'OPENBMC' l_test_type = 'FTC' l_pse_rel = 'OBMC910' l_env = 'HW' l_proc = 'P9' l_platform_type = "" l_func_area = "" # System data from XML meta data l_system_info = get_system_details(xml_file_path) l_driver = l_system_info[0] if l_system_info[1]: l_platform_type = l_system_info[1] else: print "System model is not set" sys.exit() # Default header l_header = ['test_start', 'test_end', 'subsys', 'test_type', 'test_result', 'test_name', 'pse_rel', 'driver', 'env', 'proc', 'platform_type', 'test_func_area'] l_csvlist.append(l_header) # Generate CSV file onto the path with current time stamp l_base_dir = csv_dir_path l_timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H-%M-%S") # Example: 2017-02-20-08-47-22_Witherspoon.csv l_csvfile = l_base_dir + l_timestamp + "_" + l_platform_type + ".csv" print "Writing data into csv file:", l_csvfile for testcase in collectDataObj.testData: # Functional Area: Suite Name # Test Name: Test Case Name l_func_area = str(testcase.parent).split(' ', 1)[1] l_test_name = str(testcase) # Test Result pass=0 fail=1 if testcase.status == 'PASS': l_test_result = 0 else: l_test_result = 1 # Format datetime from robot output.xml to "%Y-%m-%d-%H-%M-%S" l_stime = xml_to_csv_time(testcase.starttime) l_etime = xml_to_csv_time(testcase.endtime) # Data Sequence: test_start,test_end,subsys,test_type, # test_result,test_name,pse_rel,driver, # env,proc,platform_tyep,test_func_area, l_data = [l_stime, l_etime, l_subsys, l_test_type, l_test_result, l_test_name, l_pse_rel, l_driver, l_env, l_proc, l_platform_type, l_func_area] l_csvlist.append(l_data) # Open the file and write to the CSV file l_file = open(l_csvfile, "w") l_writer = csv.writer(l_file, lineterminator='\n') l_writer.writerows(l_csvlist) l_file.close()
def generate_dependency(self): output = "./output.xml" result = ExecutionResult(output) visitor = FileDependencyVisitor() result.visit(visitor) self.dependency = visitor.dependency
def parse_output_xml(xml_file_path, csv_dir_path, version_id, platform, level): r""" Parse the robot-generated output.xml file and extract various test output data. Put the extracted information into a csv file in the "dest" folder. Description of argument(s): xml_file_path The path to a Robot-generated output.xml file. csv_dir_path The path to the directory that is to contain the .csv files generated by this function. version_id Version of the openbmc firmware (e.g. "v2.1-215-g6e7eacb"). platform Platform of the openbmc system. level Release level of the OpenBMC system (e.g. "OBMC920"). """ result = ExecutionResult(xml_file_path) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) stats = result.statistics print("--------------------------------------") total_tc = stats.total.critical.passed + stats.total.critical.failed print("Total Test Count:\t %d" % total_tc) print("Total Test Failed:\t %d" % stats.total.critical.failed) print("Total Test Passed:\t %d" % stats.total.critical.passed) print("Test Start Time:\t %s" % result.suite.starttime) print("Test End Time:\t\t %s" % result.suite.endtime) print("--------------------------------------") # Use ResultVisitor object and save off the test data info class TestResult(ResultVisitor): def __init__(self): self.testData = [] def visit_test(self, test): self.testData += [test] collectDataObj = TestResult() result.visit(collectDataObj) # Write the result statistics attributes to CSV file l_csvlist = [] # Default Test data l_subsys = 'OPENBMC' l_test_type = 'FTC' l_pse_rel = 'OBMC910' if level: l_pse_rel = level l_env = 'HW' l_proc = 'P9' l_platform_type = "" l_func_area = "" # System data from XML meta data # l_system_info = get_system_details(xml_file_path) # First let us try to collect information from keyboard input # If keyboard input cannot give both information, then find from xml file. if version_id and platform: l_driver = version_id l_platform_type = platform print("BMC Version_id:%s" % version_id) print("BMC Platform:%s" % platform) else: # System data from XML meta data l_system_info = get_system_details(xml_file_path) l_driver = l_system_info[0] l_platform_type = l_system_info[1] # Driver version id and platform are mandatorily required for CSV file # generation. If any one is not avaulable, exit CSV file generation # process. if l_driver and l_platform_type: print("Driver and system info set.") else: print("Both driver and system info need to be set.\ CSV file is not generated.") sys.exit() # Default header l_header = [ 'test_start', 'test_end', 'subsys', 'test_type', 'test_result', 'test_name', 'pse_rel', 'driver', 'env', 'proc', 'platform_type', 'test_func_area' ] l_csvlist.append(l_header) # Generate CSV file onto the path with current time stamp l_base_dir = csv_dir_path l_timestamp = datetime.utcnow().strftime("%Y-%m-%d-%H-%M-%S") # Example: 2017-02-20-08-47-22_Witherspoon.csv l_csvfile = l_base_dir + l_timestamp + "_" + l_platform_type + ".csv" print("Writing data into csv file:%s" % l_csvfile) for testcase in collectDataObj.testData: # Functional Area: Suite Name # Test Name: Test Case Name l_func_area = str(testcase.parent).split(' ', 1)[1] l_test_name = str(testcase) # Test Result pass=0 fail=1 if testcase.status == 'PASS': l_test_result = 0 else: l_test_result = 1 # Format datetime from robot output.xml to "%Y-%m-%d-%H-%M-%S" l_stime = xml_to_csv_time(testcase.starttime) l_etime = xml_to_csv_time(testcase.endtime) # Data Sequence: test_start,test_end,subsys,test_type, # test_result,test_name,pse_rel,driver, # env,proc,platform_tyep,test_func_area, l_data = [ l_stime, l_etime, l_subsys, l_test_type, l_test_result, l_test_name, l_pse_rel, l_driver, l_env, l_proc, l_platform_type, l_func_area ] l_csvlist.append(l_data) # Open the file and write to the CSV file l_file = open(l_csvfile, "w") l_writer = csv.writer(l_file, lineterminator='\n') l_writer.writerows(l_csvlist) l_file.close()
def generate_report(opts): logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) group = Group() if not FAILED_IMPORT else '' # START OF CUSTOMIZE REPORT # URL or filepath of your company logo logo = opts.logo # Ignores following library keywords in metrics report ignore_library = IGNORE_LIBRARIES if opts.ignore: ignore_library.extend(opts.ignore) # Ignores following type keywords in metrics report ignore_type = IGNORE_TYPES if opts.ignoretype: ignore_type.extend(opts.ignoretype) # END OF CUSTOMIZE REPORT # Report to support file location as arguments # Source Code Contributed By : Ruud Prijs # input directory path = os.path.abspath(os.path.expanduser(opts.path)) # output.xml files output_names = [] # support "*.xml" of output files if (opts.output == "*.xml"): for item in os.listdir(path): item = os.path.join(path, item) if os.path.isfile(item) and item.endswith('.xml'): output_names.append(item) else: for curr_name in opts.output.split(","): curr_path = os.path.join(path, curr_name) output_names.append(curr_path) # log.html file log_name = opts.log_name # copy the list of output_names onto the one of required_files; the latter may (in the future) # contain files that should not be processed as output_names required_files = list(output_names) missing_files = [ filename for filename in required_files if not os.path.exists(filename) ] if missing_files: # We have files missing. exit("output.xml file is missing: {}".format(", ".join(missing_files))) mt_time = datetime.now().strftime('%Y%m%d-%H%M%S') # Output result file location if opts.metrics_report_name: result_file_name = opts.metrics_report_name else: result_file_name = 'metrics-' + mt_time + '.html' result_file = os.path.join(path, result_file_name) # Read output.xml file result = ExecutionResult(*output_names) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) logging.info("Converting .xml to .html file. This may take few minutes...") head_content = """ <!DOCTYPE doctype html> <html lang="en"> <head> <link href="https://png.icons8.com/windows/50/000000/bot.png" rel="shortcut icon" type="image/x-icon" /> <title>RF Metrics</title> <meta charset="utf-8" /> <meta content="width=device-width, initial-scale=1" name="viewport" /> <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet" /> <link href="https://cdn.datatables.net/buttons/1.5.2/css/buttons.dataTables.min.css" rel="stylesheet" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" /> <script src="https://code.jquery.com/jquery-3.3.1.js" type="text/javascript"/> <!-- Bootstrap core Googleccharts --> <script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"/> <script type="text/javascript"> google.charts.load('current', { packages: ['corechart'] }); </script> <!-- Bootstrap core Datatable--> <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/dataTables.buttons.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.html5.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.5.2/js/buttons.print.min.js" type="text/javascript"></script> <script src="https://cdn.datatables.net/buttons/1.6.1/js/buttons.colVis.min.js" type="text/javascript"></script> <style> body { font-family: -apple-system, sans-serif; background-color: #eeeeee; } .sidenav { height: 100%; width: 220px; position: fixed; z-index: 1; top: 0; left: 0; background-color: white; overflow-x: hidden; } .sidenav a { padding: 12px 10px 8px 12px; text-decoration: none; font-size: 18px; color: Black; display: block; } .main { padding-top: 10px; } @media screen and (max-height: 450px) { .sidenav { padding-top: 15px; } .sidenav a { font-size: 18px; } } .wrimagecard { margin-top: 0; margin-bottom: 0.6rem; border-radius: 10px; transition: all 0.3s ease; background-color: #f8f9fa; } .rowcard { padding-top: 10px; box-shadow: 12px 15px 20px 0px rgba(46, 61, 73, 0.15); border-radius: 15px; transition: all 0.3s ease; background-color: white; } .tablecard { background-color: white; font-size: 14px; } tr { height: 40px; } .dt-buttons { margin-left: 5px; } th, td, tr { text-align:center; vertical-align: middle; } .loader { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; z-index: 9999; background: url('https://i.ibb.co/cXnKsNR/Cube-1s-200px.gif') 50% 50% no-repeat rgb(249, 249, 249); } </style> </head> """ if opts.ignorekeywords == "True": hide_keyword = "hidden" else: hide_keyword = "" if opts.ignorelogs == "True": hide_logs = "hidden" else: hide_logs = "" soup = BeautifulSoup(head_content, "html.parser") body = soup.new_tag('body') soup.insert(20, body) icons_txt = """ <div class="loader"></div> <div class="sidenav"> <a> <img class="wrimagecard" src="%s" style="height:20vh;max-width:98%%;"/> </a> <a class="tablink" href="#" id="defaultOpen" onclick="openPage('dashboard', this, '#fc6666')"><i class="fa fa-dashboard" style="color:CORNFLOWERBLUE"></i> Dashboard</a> <a class="tablink" href="#" onclick="openPage('suiteMetrics', this, '#fc6666'); executeDataTable('#sm',5)"><i class="fa fa-th-large" style="color:CADETBLUE"></i> Suite Metrics</a> <a class="tablink" href="#" onclick="openPage('testMetrics', this, '#fc6666'); executeDataTable('#tm',3)"><i class="fa fa-list-alt" style="color:PALEVIOLETRED"></i> Test Metrics</a> <a %s class="tablink" href="#" onclick="openPage('keywordMetrics', this, '#fc6666'); executeDataTable('#km',3)"><i class="fa fa-table" style="color:STEELBLUE"></i> Keyword Metrics</a> <a %s class="tablink" href="#" onclick="openPage('log', this, '#fc6666');"><i class="fa fa-wpforms" style="color:CHOCOLATE"></i> Logs</a> </div> """ % (logo, hide_keyword, hide_logs) body.append(BeautifulSoup(icons_txt, 'html.parser')) page_content_div = soup.new_tag('div') page_content_div["class"] = "main col-md-9 ml-sm-auto col-lg-10 px-4" body.insert(50, page_content_div) logging.info("1 of 4: Capturing dashboard content...") test_stats = TestStats() result.visit(test_stats) try: test_stats_obj = test_stats.all except: test_stats_obj = test_stats total_suite = test_stats_obj.total_suite passed_suite = test_stats_obj.passed_suite failed_suite = test_stats_obj.failed_suite try: skipped_suite = test_stats_obj.skipped_suite except: skipped_suite = 0 #suitepp = round(passed_suite * 100.0 / total_suite, 1) #suitefp = round(failed_suite * 100.0 / total_suite, 1) elapsedtime = datetime( 1970, 1, 1) + timedelta(milliseconds=result.suite.elapsedtime) elapsedtime = elapsedtime.strftime("%X") my_results = result.generated_by_robot if my_results: generator = "Robot" else: generator = "Rebot" stats = result.statistics try: stats_obj = stats.total.all except: stats_obj = stats.total total = stats_obj.total passed = stats_obj.passed failed = stats_obj.failed try: skipped = stats_obj.skipped except: skipped = 0 #testpp = round(passed * 100.0 / total, 1) #testfp = round(failed * 100.0 / total, 1) kw_stats = KeywordStats(ignore_library, ignore_type) result.visit(kw_stats) total_keywords = kw_stats.total_keywords passed_keywords = kw_stats.passed_keywords failed_keywords = kw_stats.failed_keywords try: skipped_keywords = kw_stats.skipped_keywords except: skipped_keywords = 0 # Handling ZeroDivisionError exception when no keywords are found # if total_keywords > 0: # kwpp = round(passed_keywords * 100.0 / total_keywords, 1) # kwfp = round(failed_keywords * 100.0 / total_keywords, 1) # else: # kwpp = 0 # kwfp = 0 dashboard_content = """ <div class="tabcontent" id="dashboard"> <div id="stats_screenshot_area"> <div class="d-flex flex-column flex-md-row align-items-center p-1 mb-3 bg-light border-bottom shadow-sm rowcard"> <h5 class="my-0 mr-md-auto font-weight-normal"><i class="fa fa-dashboard"></i> Dashboard</h5> <nav class="my-2 my-md-0 mr-md-3" style="color:#fc6666"> <a class="p-2"><b style="color:black;">Execution Time: </b>__TIME__ h</a> <a class="p-2"><b style="color:black;cursor: pointer;" data-toggle="tooltip" title=".xml file is created by">Generated By: </b>__GENERATED-BY__</a> </nav> </div> <div class="row rowcard"> <div class="col-md-4 border-right" onclick="openPage('suiteMetrics', this, '')" data-toggle="tooltip" title="Click to view Suite metrics" style="cursor: pointer;"> <span style="font-weight:bold; padding-left:5px;color:gray">Suite Statistics:</span> <table style="width:100%;height:200px;text-align: center;"> <tbody> <tr style="height:60%"> <td> <table style="width:100%"> <tbody> <tr style="height:100%"> <td style="font-size:60px; color:#2ecc71">__SPASS__</td> </tr> <tr> <td><span style="color: #999999;font-size:12px">Pass</span></td> </tr> </tbody> </table> </td> </tr> <tr style="height:25%"> <td> <table style="width:100%"> <tbody> <tr style="height:70%;font-size:25px" align="center" valign="middle"> <td style="width: 33%; color:brown">__STOTAL__</td> <td style="width: 33%; color:orange">__SSKIP__</td> <td style="width: 33%; color:#fc6666">__SFAIL__</td> </tr> <tr style="height:30%" align="center" valign="top"> <td style="width: 33%"><span style="color: #999999;font-size:10px">Total</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Skip</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Fail</span></td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <div class="col-md-4 borders" onclick="openPage('testMetrics', this, '')" data-toggle="tooltip" title="Click to view Test metrics" style="cursor: pointer;"> <span style="font-weight:bold; padding-left:5px;color:gray">Test Statistics:</span> <table style="width:100%;height:200px;text-align: center;"> <tbody> <tr style="height:60%"> <td> <table style="width:100%"> <tbody> <tr style="height:100%"> <td style="font-size:60px; color:#2ecc71">__TPASS__</td> </tr> <tr> <td><span style="color: #999999;font-size:12px">Pass</span></td> </tr> </tbody> </table> </td> </tr> <tr style="height:25%"> <td> <table style="width:100%"> <tbody> <tr style="height:70%;font-size:25px" align="center" valign="middle"> <td style="width: 33%; color:brown">__TTOTAL__</td> <td style="width: 33%; color:orange">__TSKIP__</td> <td style="width: 33%; color:#fc6666">__TFAIL__</td> </tr> <tr style="height:30%" align="center" valign="top"> <td style="width: 33%"><span style="color: #999999;font-size:10px">Total</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Skip</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Fail</span></td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <div class="col-md-4 border-left" onclick="openPage('keywordMetrics', this, '')" data-toggle="tooltip" title="Click to view Keyword metrics" style="cursor: pointer;"> <span style="font-weight:bold; padding-left:5px;color:gray">Keyword Statistics:</span> <table style="width:100%;height:200px;text-align: center;"> <tbody> <tr style="height:60%"> <td> <table style="width:100%"> <tbody> <tr style="height:100%"> <td style="font-size:60px; color:#2ecc71">__KPASS__</td> </tr> <tr> <td><span style="color: #999999;font-size:12px">Pass</span></td> </tr> </tbody> </table> </td> </tr> <tr style="height:25%"> <td> <table style="width:100%"> <tbody> <tr style="height:70%;font-size:25px" align="center" valign="middle"> <td style="width: 33%; color:brown">__KTOTAL__</td> <td style="width: 33%; color:orange">__KSKIP__</td> <td style="width: 33%; color:#fc6666">__KFAIL__</td> </tr> <tr style="height:30%" align="center" valign="top"> <td style="width: 33%"><span style="color: #999999;font-size:10px">Total</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Skip</span></td> <td style="width: 33%"><span style="color: #999999;font-size:10px">Fail</span></td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> </div> <hr/> <div class="row rowcard"> <div class="col-md-4" style="height:280px;width:auto;"> <span style="font-weight:bold;color:gray">Suite Status:</span> <div id="suiteChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="height:280px;width:auto;"> <span style="font-weight:bold;color:gray">Test Status:</span> <div id="testChartID" style="height:250px;width:auto;"></div> </div> <div class="col-md-4" style="height:280px;width:auto;"> <span style="font-weight:bold;color:gray">Keyword Status:</span> <div id="keywordChartID" style="height:250px;width:auto;"></div> </div> </div> <hr/> <div class="row rowcard"> <div class="col-md-12" style="height:450px;width:auto;"> <span style="font-weight:bold;color:gray">Top 10 Suite Performance(sec):</span> <div id="suiteBarID" style="height:400px;width:auto;"></div> </div> </div> <hr/> <div class="row rowcard"> <div class="col-md-12" style="height:450px;width:auto;"> <span style="font-weight:bold;color:gray">Top 10 Test Performance(sec):</span> <div id="testsBarID" style="height:400px;width:auto;"> </div> </div> </div> <hr/> <div class="row rowcard" __KHIDE__> <div class="col-md-12" style="height:450px;width:auto;"> <span style="font-weight:bold;color:gray">Top 10 Keywords Performance(sec):</span> <div id="keywordsBarID" style="height:400px;width:auto;"></div> </div> </div> <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"> <p class="text-muted" style="text-align:center;font-size:9px"> <a href="https://github.com/adiralashiva8/robotframework-metrics" target="_blank" style="color:gray">robotframework-metrics</a> </p> </div> </div> <script> window.onload = function(){ executeDataTable('#sm',6); executeDataTable('#tm',3); executeDataTable('#km',3); createPieChart(__SPASS__,__SFAIL__,__SSKIP__,'suiteChartID','Suite Status:'); createBarGraph('#sm',0,6,10,'suiteBarID','Elapsed Time (s) ','Suite'); createPieChart(__TPASS__,__TFAIL__,__TSKIP__,'testChartID','Tests Status:'); createBarGraph('#tm',1,3,10,'testsBarID','Elapsed Time (s) ','Test'); createPieChart(__KPASS__,__KFAIL__,__KSKIP__,'keywordChartID','Keywords Status:'); createBarGraph('#km',1,3,10,'keywordsBarID','Elapsed Time (s) ','Keyword'); }; </script> <script> function openInNewTab(url,element_id) { var element_id= element_id; var win = window.open(url, '_blank'); win.focus(); $('body').scrollTo(element_id); } </script> </div> """ dashboard_content = dashboard_content.replace("__TIME__", str(elapsedtime)) dashboard_content = dashboard_content.replace("__GENERATED-BY__", str(generator)) dashboard_content = dashboard_content.replace("__STOTAL__", str(total_suite)) dashboard_content = dashboard_content.replace("__SPASS__", str(passed_suite)) dashboard_content = dashboard_content.replace("__SFAIL__", str(failed_suite)) dashboard_content = dashboard_content.replace("__SSKIP__", str(skipped_suite)) dashboard_content = dashboard_content.replace("__TTOTAL__", str(total)) dashboard_content = dashboard_content.replace("__TPASS__", str(passed)) dashboard_content = dashboard_content.replace("__TFAIL__", str(failed)) dashboard_content = dashboard_content.replace("__TSKIP__", str(skipped)) dashboard_content = dashboard_content.replace("__KTOTAL__", str(total_keywords)) dashboard_content = dashboard_content.replace("__KPASS__", str(passed_keywords)) dashboard_content = dashboard_content.replace("__KFAIL__", str(failed_keywords)) dashboard_content = dashboard_content.replace("__KSKIP__", str(skipped_keywords)) dashboard_content = dashboard_content.replace("__KHIDE__", str(hide_keyword)) page_content_div.append(BeautifulSoup(dashboard_content, 'html.parser')) ### ============================ END OF DASHBOARD ============================================ #### logging.info("2 of 4: Capturing suite metrics...") ### ============================ START OF SUITE METRICS ======================================= #### # Tests div suite_div = soup.new_tag('div') suite_div["id"] = "suiteMetrics" suite_div["class"] = "tabcontent" page_content_div.insert(50, suite_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Suite Metrics</b></h4> <hr></hr> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "sm" table["class"] = "table row-border tablecard" suite_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Status" tr.insert(1, th) th = soup.new_tag('th') th.string = "Total" tr.insert(2, th) th = soup.new_tag('th') th.string = "Pass" tr.insert(3, th) th = soup.new_tag('th') th.string = "Fail" tr.insert(4, th) th = soup.new_tag('th') th.string = "Fail" tr.insert(5, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(6, th) suite_tbody = soup.new_tag('tbody') table.insert(11, suite_tbody) result.visit(SuiteResults(soup, suite_tbody, log_name, opts.fullsuitename)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"></div> </div> """ suite_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF SUITE METRICS ============================================ #### logging.info("3 of 4: Capturing test metrics...") ### ============================ START OF TEST METRICS ======================================= #### # Tests div tm_div = soup.new_tag('div') tm_div["id"] = "testMetrics" tm_div["class"] = "tabcontent" page_content_div.insert(100, tm_div) test_icon_txt = """ <h4><b><i class="fa fa-table"></i> Test Metrics</b></h4> <hr></hr> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # Create table tag table = soup.new_tag('table') table["id"] = "tm" table["class"] = "table row-border tablecard" tm_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Suite Name" tr.insert(0, th) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) th = soup.new_tag('th') th.string = "Error Message" tr.insert(4, th) if opts.showtags == "True": th = soup.new_tag('th') th.string = "Tags" tr.insert(5, th) test_tbody = soup.new_tag('tbody') table.insert(11, test_tbody) # GET TEST METRICS result.visit( TestResults(soup, test_tbody, log_name, opts.fullsuitename, opts.showtags)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"></div> </div> """ tm_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) ### ============================ END OF TEST METRICS ============================================ #### logging.info("4 of 4: Capturing keyword metrics...") ### ============================ START OF KEYWORD METRICS ======================================= #### # Keywords div km_div = soup.new_tag('div') km_div["id"] = "keywordMetrics" km_div["class"] = "tabcontent" page_content_div.insert(150, km_div) keyword_icon_txt = """ <h4><b><i class="fa fa-table"></i> Keyword Metrics</b></h4> <hr></hr> """ km_div.append(BeautifulSoup(keyword_icon_txt, 'html.parser')) # Create table tag # <table id="myTable"> table = soup.new_tag('table') table["id"] = "km" table["class"] = "table row-border tablecard" km_div.insert(10, table) thead = soup.new_tag('thead') table.insert(0, thead) tr = soup.new_tag('tr') thead.insert(0, tr) th = soup.new_tag('th') th.string = "Test Case" tr.insert(1, th) th = soup.new_tag('th') th.string = "Keyword" tr.insert(1, th) th = soup.new_tag('th') th.string = "Status" tr.insert(2, th) th = soup.new_tag('th') th.string = "Time (s)" tr.insert(3, th) kw_tbody = soup.new_tag('tbody') table.insert(1, kw_tbody) if opts.ignorekeywords == "True": pass else: if group: group.spawn( result.visit, KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) group.join() else: result.visit( KeywordResults(soup, kw_tbody, ignore_library, ignore_type)) test_icon_txt = """ <div class="row"> <div class="col-md-12" style="height:25px;width:auto;"></div> </div> """ km_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # END OF KEYWORD METRICS # START OF LOGS # Logs div if opts.ignorelogs == "True": pass else: log_div = soup.new_tag('div') log_div["id"] = "log" log_div["class"] = "tabcontent" page_content_div.insert(200, log_div) test_icon_txt = """ <p style="text-align:right">** <b>Report.html</b> and <b>Log.html</b> need to be in current folder in order to display here</p> <div class="embed-responsive embed-responsive-4by3"> <iframe class="embed-responsive-item" src=%s></iframe> </div> """ % log_name log_div.append(BeautifulSoup(test_icon_txt, 'html.parser')) # END OF LOGS script_text = """ <script> function createPieChart(passed_count, failed_count, skipped_count, ChartID, ChartName){ var status = []; status.push(['Status', 'Percentage']); status.push(['PASS',parseInt(passed_count)],['FAIL',parseInt(failed_count)],['SKIP',parseInt(skipped_count)]); var data = google.visualization.arrayToDataTable(status); var options = { pieHole: 0.6, legend: 'none', chartArea: {width: "95%",height: "90%"}, colors: ['#2ecc71', '#fc6666', '#ffa500'], }; var chart = new google.visualization.PieChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function createBarGraph(tableID,keyword_column,time_column,limit,ChartID,Label,type){ var status = []; css_selector_locator = tableID + ' tbody >tr' var rows = $(css_selector_locator); var columns; var myColors = [ '#4F81BC', '#C0504E', '#9BBB58', '#24BEAA', '#8064A1', '#4AACC5', '#F79647', '#815E86', '#76A032', '#34558B' ]; status.push([type, Label,{ role: 'annotation'}, {role: 'style'}]); for (var i = 0; i < rows.length; i++) { if (i == Number(limit)){ break; } //status = []; name_value = $(rows[i]).find('td'); time=($(name_value[Number(time_column)]).html()).trim(); keyword=($(name_value[Number(keyword_column)]).html()).trim(); status.push([keyword,parseFloat(time),parseFloat(time),myColors[i]]); } var data = google.visualization.arrayToDataTable(status); var options = { legend: 'none', chartArea: {width: "92%",height: "75%"}, bar: { groupWidth: '90%' }, annotations: { alwaysOutside: true, textStyle: { fontName: 'Comic Sans MS', fontSize: 12, //bold: true, italic: true, color: "black", // The color of the text. }, }, hAxis: { textStyle: { //fontName: 'Arial', fontName: 'Comic Sans MS', fontSize: 10, } }, vAxis: { gridlines: { count: 10 }, textStyle: { fontName: 'Comic Sans MS', fontSize: 10, } }, }; // Instantiate and draw the chart. var chart = new google.visualization.ColumnChart(document.getElementById(ChartID)); chart.draw(data, options); } </script> <script> function executeDataTable(tabname,sortCol) { var fileTitle; switch(tabname) { case "#sm": fileTitle = "SuiteMetrics"; break; case "#tm": fileTitle = "TestMetrics"; break; case "#km": fileTitle = "KeywordMetrics"; break; default: fileTitle = "metrics"; } $(tabname).DataTable( { retrieve: true, "order": [[ Number(sortCol), "desc" ]], dom: 'l<".margin" B>frtip', "aoColumnDefs": [ { "aTargets": [ -1, -2 ], "mRender": function ( data, type, full ) { return $("<div/>").html(data).text(); } } ], buttons: [ { extend: 'copyHtml5', text: '<i class="fa fa-files-o"></i>', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, titleAttr: 'Copy', exportOptions: { columns: ':visible' } }, { extend: 'csvHtml5', text: '<i class="fa fa-file-text-o"></i>', titleAttr: 'CSV', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, exportOptions: { columns: ':visible' } }, { extend: 'excelHtml5', text: '<i class="fa fa-file-excel-o"></i>', titleAttr: 'Excel', filename: function() { return fileTitle + '-' + new Date().toLocaleString(); }, exportOptions: { columns: ':visible' } }, { extend: 'print', text: '<i class="fa fa-print"></i>', titleAttr: 'Print', exportOptions: { columns: ':visible', alignment: 'left', } }, { extend: 'colvis', collectionLayout: 'fixed two-column', text: '<i class="fa fa-low-vision"></i>', titleAttr: 'Hide Column', exportOptions: { columns: ':visible' }, postfixButtons: [ 'colvisRestore' ] }, ], columnDefs: [ { visible: false, } ] } ); } </script> <script> function openPage(pageName,elmnt,color) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } tablinks = document.getElementsByClassName("tablink"); for (i = 0; i < tablinks.length; i++) { tablinks[i].style.color = ""; } document.getElementById(pageName).style.display = "block"; elmnt.style.color = color; } // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> // Get the element with id="defaultOpen" and click on it document.getElementById("defaultOpen").click(); </script> <script> $(window).on('load',function(){$('.loader').fadeOut();}); </script> """ body.append(BeautifulSoup(script_text, 'html.parser')) # WRITE TO RF_METRICS_REPORT.HTML # Write output as html file with open(result_file, 'w') as outfile: outfile.write(soup.prettify()) logging.info( "Results file created successfully and can be found at {}".format( result_file))
else: log_name = 'log.html' # output.xml file if '-output' in myargs: output_name = os.path.join(path, myargs['-output'][0]) else: output_name = os.path.join(path, 'output.xml') mtTime = datetime.now().strftime('%Y%m%d-%H%M%S') # Output result file location result_file_name = 'metrics-' + mtTime + '.html' result_file = os.path.join(path, result_file_name) # Read output.xml file result = ExecutionResult(output_name) result.configure(stat_config={ 'suite_stat_level': 2, 'tag_stat_combine': 'tagANDanother' }) print("Converting .xml to .html file. This may take few minutes...") head_content = """ <!doctype html> <html lang="en"> <head> <link rel="shortcut icon" href="https://png.icons8.com/windows/50/000000/bot.png" type="image/x-icon" /> <title>RF Metrics Report</title> <meta charset="utf-8">
def get_test_suite_names(output_xml_path): """" Parses an output.xml file and returns the names of test suites that contain tests that were executed """ er = ExecutionResult(output_xml_path) return _recursively_list_test_suites(er.suite)
half_low = int(math.floor(half)) half_high = int(math.ceil(half)) return round(float(s[half_low]+s[half_high])/2000, 3) @property def variance(self): squares = [(float(i)/1000)**2 for i in self.elapsedtimes] return sum(squares)/len(squares)-(self.elapsed/self.calls)**2 @property def standard_deviation(self): return round(self.variance**0.5, 3) def __cmp__(self, other): return other.elapsed - self.elapsed if __name__ == '__main__': import sys resu = ExecutionResult(sys.argv[1]) times = KeywordTimes() resu.visit(times) s = sorted(times.keywords.values()) shown_keywords = 100 print 'Total time (s) | Number of calls | avg time (s) | median time (s) | standard deviation (s) | Keyword name' for k in s[:shown_keywords]: print str(k.elapsed).rjust(14)+' | '+str(k.calls).rjust(15)+ ' | ' + \ str(k.average_time).rjust(12) + ' | ' + str(k.median_time).rjust(15) + \ ' | ' + str(k.standard_deviation).rjust(22) + (' | "%s"' % k.name) print 'Showing %d of total keywords %d' % (shown_keywords, len(times.keywords))