├── Core ├── CRFKeywords │ ├── Appium.py │ ├── Archive.py │ ├── BuiltIn.py │ ├── Database.py │ ├── Diff.py │ ├── Ftp.py │ ├── Mongo.py │ ├── QT.py │ ├── Requests.py │ └── Selenium.py ├── Keywords.py └── Runner │ ├── HtmlTestRunner │ ├── __init__.py │ ├── result.py │ ├── runner.py │ └── template │ │ └── report_template.html │ ├── TestRunner.py │ └── xmlrunner │ ├── __init__.py │ ├── __main__.py │ ├── builder.py │ ├── extra │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-35.pyc │ │ └── djangotestrunner.cpython-35.pyc │ └── djangotestrunner.py │ ├── result.py │ ├── runner.py │ ├── unittest.py │ ├── version.py │ └── xmlrunner.py ├── Library └── CommonLibrary.py ├── README.md ├── Resource └── Variables │ └── variables.py ├── Results ├── output.xml └── report.html ├── RunTestSuites.py └── TestCase └── UserSystem └── UserGUI.py /Core/CRFKeywords/Appium.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from AppiumLibrary import AppiumLibrary 4 | 5 | 6 | class CRFAppium(AppiumLibrary): 7 | def __init__(self): 8 | super(CRFAppium, self).__init__() -------------------------------------------------------------------------------- /Core/CRFKeywords/Archive.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from ArchiveLibrary import ArchiveLibrary 4 | 5 | class CRFArchive(ArchiveLibrary): 6 | def __init__(self): 7 | super(CRFArchive, self).__init__() 8 | -------------------------------------------------------------------------------- /Core/CRFKeywords/BuiltIn.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | 3 | from robot.libraries.BuiltIn import BuiltIn as RobotBuiltIn 4 | from robot.libraries.Collections import Collections 5 | from robot.libraries.OperatingSystem import OperatingSystem 6 | from robot.libraries.Process import Process 7 | from robot.libraries.Remote import Remote 8 | from robot.libraries.Reserved import Reserved 9 | from robot.libraries.Screenshot import Screenshot 10 | from robot.libraries.String import String 11 | from robot.libraries.Telnet import Telnet 12 | from robot.libraries.XML import XML 13 | import time 14 | 15 | 16 | class CRFBuiltIn( 17 | RobotBuiltIn, 18 | Collections, 19 | OperatingSystem, 20 | Process, 21 | Remote, 22 | Reserved, 23 | Screenshot, 24 | String, 25 | Telnet, 26 | XML): 27 | def __init__(self): 28 | Process.__init__(self) 29 | Remote.__init__(self) 30 | Screenshot.__init__(self) 31 | Telnet.__init__(self) 32 | XML.__init__(self) 33 | 34 | def print_log(self, message): 35 | message = '\n[{}] {}'.format(time.strftime('%Y-%m-%d %H:%M:%S'), message) 36 | self.log('{}'.format(message), console=True) 37 | print('{}'.format(message)) 38 | -------------------------------------------------------------------------------- /Core/CRFKeywords/Database.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | 4 | from DatabaseLibrary import DatabaseLibrary 5 | 6 | 7 | class CRFDatabase(DatabaseLibrary): 8 | def __init__(self): 9 | super(CRFDatabase, self).__init__() 10 | -------------------------------------------------------------------------------- /Core/CRFKeywords/Diff.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from DiffLibrary import DiffLibrary 4 | 5 | 6 | class CRFDiff(DiffLibrary): 7 | def __init__(self): 8 | super(CRFDiff, self).__init__() 9 | -------------------------------------------------------------------------------- /Core/CRFKeywords/Ftp.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from FtpLibrary import FtpLibrary 4 | 5 | 6 | class CRFFtp(FtpLibrary): 7 | def __init__(self): 8 | super(CRFFtp, self).__init__() -------------------------------------------------------------------------------- /Core/CRFKeywords/Mongo.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from MongoDBLibrary import MongoDBLibrary 4 | 5 | 6 | class CRFMongo(MongoDBLibrary): 7 | def __init__(self): 8 | super(CRFMongo, self).__init__() 9 | -------------------------------------------------------------------------------- /Core/CRFKeywords/QT.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | 3 | 4 | from QTLibrary import QTLibrary 5 | 6 | 7 | class CRFQT(QTLibrary): 8 | def __init__(self): 9 | super(CRFQT, self).__init__() 10 | -------------------------------------------------------------------------------- /Core/CRFKeywords/Requests.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | 4 | from RequestsLibrary import RequestsLibrary 5 | 6 | 7 | class CRFRequests(RequestsLibrary): 8 | def __init__(self): 9 | super(CRFRequests, self).__init__() -------------------------------------------------------------------------------- /Core/CRFKeywords/Selenium.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | 3 | 4 | import os 5 | from SeleniumLibrary.base.robotlibcore import PY2 6 | from SeleniumLibrary import SeleniumLibrary 7 | from SeleniumLibrary.keywords import (AlertKeywords, 8 | BrowserManagementKeywords, 9 | CookieKeywords, 10 | ElementKeywords, 11 | FormElementKeywords, 12 | JavaScriptKeywords, 13 | RunOnFailureKeywords, 14 | ScreenshotKeywords, 15 | SelectElementKeywords, 16 | TableElementKeywords, 17 | WaitingKeywords) 18 | from Core.CRFKeywords.BuiltIn import CRFBuiltIn 19 | from robot.libraries.BuiltIn import RobotNotRunningError 20 | 21 | 22 | class CRFSelenium( 23 | AlertKeywords, 24 | BrowserManagementKeywords, 25 | CookieKeywords, 26 | ElementKeywords, 27 | FormElementKeywords, 28 | JavaScriptKeywords, 29 | RunOnFailureKeywords, 30 | ScreenshotKeywords, 31 | SelectElementKeywords, 32 | TableElementKeywords, 33 | WaitingKeywords): 34 | def __init__(self): 35 | ctx = SeleniumLibrary(screenshot_root_directory='Results') 36 | AlertKeywords.__init__(self, ctx) 37 | BrowserManagementKeywords.__init__(self, ctx) 38 | ElementKeywords.__init__(self, ctx) 39 | FormElementKeywords.__init__(self, ctx) 40 | ScreenshotKeywords.__init__(self, ctx) 41 | SelectElementKeywords.__init__(self, ctx) 42 | WaitingKeywords.__init__(self, ctx) 43 | self.screenshot_directory = ctx.screenshot_root_directory 44 | self.builtIn = CRFBuiltIn() 45 | 46 | @property 47 | def log_dir(self): 48 | try: 49 | if os.path.isdir(self.screenshot_directory): 50 | return os.path.abspath(self.screenshot_directory) 51 | else: 52 | os.makedirs(self.screenshot_directory) 53 | return os.path.abspath(self.screenshot_directory) 54 | except RobotNotRunningError: 55 | return os.getcwdu() if PY2 else os.getcwd() 56 | 57 | def open_wdBrowser(self, url, browser='Chrome'): 58 | """ 59 | 通过WebDriver启动浏览器 60 | 启动浏览器类型,可选:Firefox、Chrome、PhantomJS 61 | """ 62 | self.builtIn.print_log('==============通过 WebDriver 打开 {} 浏览器=============='.format(browser)) 63 | service_args = ['--ssl-protocol=any', '--web-security=false', '--debug=true', 64 | '--ignore-ssl-errors=true', '--webdriver-loglevel=WARN'] 65 | if browser == 'PhantomJS': 66 | self.create_webdriver(browser, service_args=service_args) 67 | else: 68 | self.create_webdriver(browser) 69 | self.go_to(url) 70 | self.maximize_browser_window() 71 | 72 | def make_element_visible(self, xpath): 73 | """Make Element Visible""" 74 | self.builtIn.print_log('============使节点元素 {} 可见==========='.format(xpath)) 75 | self.wait_until_page_contains_element(xpath) 76 | self.execute_javascript("document.evaluate('{}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.style.display = 'block';".format(xpath)) 77 | self.wait_until_emement_is_visible(xpath) 78 | -------------------------------------------------------------------------------- /Core/Keywords.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | 3 | 4 | from Core.CRFKeywords.Appium import CRFAppium 5 | from Core.CRFKeywords.Archive import CRFArchive 6 | from Core.CRFKeywords.BuiltIn import CRFBuiltIn 7 | from Core.CRFKeywords.Database import CRFDatabase 8 | from Core.CRFKeywords.Diff import CRFDiff 9 | # from Core.CRFKeywords.Ftp import CRFFtp 10 | # from Core.CRFKeywords.Mongo import CRFMongo 11 | # from Core.CRFKeywords.QT import CRFQT 12 | from Core.CRFKeywords.Requests import CRFRequests 13 | from Core.CRFKeywords.Selenium import CRFSelenium 14 | 15 | 16 | # 初始化关键字 17 | selenium = CRFSelenium() 18 | requests = CRFRequests() 19 | # qt = CRFQT() 20 | # mongo = CRFMongo() 21 | # ftp = CRFFtp() 22 | diff = CRFDiff() 23 | database = CRFDatabase() 24 | archive = CRFArchive() 25 | appium = CRFAppium() 26 | builtIn = CRFBuiltIn() 27 | -------------------------------------------------------------------------------- /Core/Runner/HtmlTestRunner/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .runner import HTMLTestRunner 3 | 4 | 5 | __author__ = """Ordanis Sanchez Suero""" 6 | __email__ = 'ordanisanchez@gmail.com' 7 | __version__ = '1.1.1' 8 | -------------------------------------------------------------------------------- /Core/Runner/HtmlTestRunner/result.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import sys 4 | import time 5 | import traceback 6 | import re 7 | import six 8 | import html 9 | from unittest import TestResult, _TextTestResult 10 | from unittest.result import failfast 11 | from jinja2 import Template 12 | 13 | 14 | DEFAULT_TEMPLATE = os.path.join(os.path.dirname(__file__), "template", 15 | "report_template.html") 16 | 17 | 18 | def load_template(template): 19 | """ Try to read a file from a given path, if file 20 | does not exist, load default one. """ 21 | file = None 22 | try: 23 | if template: 24 | with open(template, "r") as f: 25 | file = f.read() 26 | except Exception as err: 27 | print("Error: Your Template wasn't loaded", err, 28 | "Loading Default Template", sep="\n") 29 | finally: 30 | if not file: 31 | with open(DEFAULT_TEMPLATE, "r") as f: 32 | file = f.read() 33 | return file 34 | 35 | 36 | def render_html(template, **kwargs): 37 | template_file = load_template(template) 38 | if template_file: 39 | template = Template(template_file) 40 | return template.render(**kwargs) 41 | 42 | 43 | def testcase_name(test_method): 44 | testcase = type(test_method) 45 | 46 | module = testcase.__module__ + "." 47 | if module == "__main__.": 48 | module = "" 49 | result = module + testcase.__name__ 50 | return result 51 | 52 | 53 | class _TestInfo(object): 54 | """" Keeps information about the execution of a test method. """ 55 | 56 | (SUCCESS, FAILURE, ERROR, SKIP) = range(4) 57 | 58 | def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=None): 59 | self.test_result = test_result 60 | self.outcome = outcome 61 | self.elapsed_time = 0 62 | self.err = err 63 | self.stdout = test_result._stdout_data 64 | self.stderr = test_result._stderr_data 65 | self.screenshot = '' 66 | self.rerun = 0 67 | test_description = self.test_result.getDescription(test_method) 68 | self.test_description = test_description.split()[0] if len(test_description.split())<3 else test_description.split()[-1] 69 | 70 | self.test_exception_info = ( 71 | '' if outcome in (self.SUCCESS, self.SKIP) 72 | else self.test_result._exc_info_to_string( 73 | self.err, test_method) 74 | ) 75 | 76 | self.test_doc = test_method._testMethodDoc 77 | self.test_name = testcase_name(test_method) 78 | self.test_id = test_method.id() 79 | if subTest: 80 | self.test_id = subTest.id() 81 | 82 | def id(self): 83 | return self.test_id 84 | 85 | def test_finished(self): 86 | self.elapsed_time = \ 87 | self.test_result.stop_time - self.test_result.start_time 88 | 89 | def get_description(self): 90 | return self.test_description 91 | 92 | def get_error_info(self): 93 | return self.test_exception_info 94 | 95 | 96 | class _HtmlTestResult(_TextTestResult): 97 | """ A test result class that express test results in Html. """ 98 | 99 | def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, 100 | elapsed_times=True, properties=None, infoclass=None): 101 | _TextTestResult.__init__(self, stream, descriptions, verbosity) 102 | self.rerun = 0 103 | self.retry = 0 104 | self.tb_locals = False 105 | self.buffer = True 106 | self._stdout_data = None 107 | self._stderr_data = None 108 | self.successes = [] 109 | self.tested_fail_error = [] 110 | self.callback = None 111 | self.properties = properties 112 | self.elapsed_times = elapsed_times 113 | if infoclass is None: 114 | self.infoclass = _TestInfo 115 | else: 116 | self.infoclass = infoclass 117 | self.testRun = 0 118 | 119 | def _prepare_callback(self, test_info, target_list, verbose_str, 120 | short_str): 121 | """ Appends a 'info class' to the given target list and sets a 122 | callback method to be called by stopTest method.""" 123 | target_list.append(test_info) 124 | 125 | def callback(): 126 | """ Print test method outcome to the stream and ellapse time too.""" 127 | test_info.test_finished() 128 | 129 | if not self.elapsed_times: 130 | self.start_time = self.stop_time = 0 131 | 132 | if self.showAll: 133 | self.stream.writeln( 134 | "{} ({:6f})s".format(verbose_str, test_info.elapsed_time)) 135 | elif self.dots: 136 | self.stream.write(short_str) 137 | self.callback = callback 138 | 139 | def startTest(self, test): 140 | """ Called before execute each method. """ 141 | if self.retry == 0: 142 | self.testRun += 1 143 | self.start_time = time.time() 144 | TestResult.startTest(self, test) 145 | 146 | if self.showAll: 147 | self.stream.write(" " + self.getDescription(test)) 148 | self.stream.write(" ... ") 149 | 150 | def _save_output_data(self): 151 | try: 152 | self._stdout_data = sys.stdout.getvalue() 153 | self._stderr_data = sys.stderr.getvalue() 154 | except AttributeError: 155 | pass 156 | 157 | def stopTest(self, test): 158 | """ Called after excute each test method. """ 159 | self._save_output_data() 160 | _TextTestResult.stopTest(self, test) 161 | self.stop_time = time.time() 162 | 163 | if self.callback and callable(self.callback): 164 | self.callback() 165 | self.callback = None 166 | 167 | if self.rerun > self.retry: 168 | self.retry += 1 169 | self.stream.write('Rerun {} time...'.format(self.retry)) 170 | test(self) 171 | else: 172 | self.retry = 0 173 | 174 | def addSuccess(self, test): 175 | """ Called when a test executes successfully. """ 176 | self._save_output_data() 177 | testinfo = self.infoclass(self, test) 178 | testinfo.rerun = self.retry 179 | self._prepare_callback( 180 | testinfo, self.successes, "OK", ".") 181 | self.retry = self.rerun 182 | if testinfo.test_id in self.tested_fail_error: 183 | self._remove_test(testinfo.test_id) 184 | 185 | @failfast 186 | def addFailure(self, test, err): 187 | """ Called when a test method fails. """ 188 | self._save_output_data() 189 | testinfo = self.infoclass( 190 | self, test, self.infoclass.FAILURE, err) 191 | if testinfo.test_id in self.tested_fail_error: 192 | self._remove_test(testinfo.test_id) 193 | try: 194 | testinfo.screenshot = test.driver.capture_page_screenshot() 195 | test.driver.close_browser() 196 | except Exception as e: 197 | pass 198 | testinfo.rerun = self.retry 199 | self.failures.append((testinfo, 200 | self._exc_info_to_string(err, test))) 201 | self.tested_fail_error.append(testinfo.test_id) 202 | self._prepare_callback(testinfo, [], "FAIL", "F") 203 | 204 | @failfast 205 | def addError(self, test, err): 206 | """" Called when a test method raises an error. """ 207 | self._save_output_data() 208 | testinfo = self.infoclass( 209 | self, test, self.infoclass.ERROR, err) 210 | if testinfo.test_id in self.tested_fail_error: 211 | self._remove_test(testinfo.test_id) 212 | try: 213 | testinfo.screenshot = test.driver.capture_page_screenshot() 214 | test.driver.close_browser() 215 | except Exception as e: 216 | pass 217 | testinfo.rerun = self.retry 218 | self.errors.append((testinfo, 219 | self._exc_info_to_string(err, test))) 220 | self.tested_fail_error.append(testinfo.test_id) 221 | self._prepare_callback(testinfo, [], 'ERROR', 'E') 222 | 223 | def addSubTest(self, testcase, test, err): 224 | """ Called when a subTest method raise an error. """ 225 | if err is not None: 226 | self._save_output_data() 227 | testinfo = self.infoclass( 228 | self, testcase, self.infoclass.ERROR, err, subTest=test) 229 | if testinfo.test_id in self.tested_fail_error: 230 | self._remove_test(testinfo.test_id) 231 | try: 232 | testinfo.screenshot = test.driver.capture_page_screenshot() 233 | test.driver.close_browser() 234 | except Exception as e: 235 | pass 236 | testinfo.rerun = self.retry 237 | self.errors.append(( 238 | testinfo, 239 | self._exc_info_to_string(err, testcase) 240 | )) 241 | self.tested_fail_error.append(testinfo.test_id) 242 | self._prepare_callback(testinfo, [], "ERROR", "E") 243 | 244 | def addSkip(self, test, reason): 245 | """" Called when a test method was skipped. """ 246 | self._save_output_data() 247 | testinfo = self.infoclass( 248 | self, test, self.infoclass.SKIP, reason) 249 | self.skipped.append((testinfo, reason)) 250 | self._prepare_callback(testinfo, [], "SKIP", "S") 251 | self.retry = self.rerun 252 | 253 | def _remove_test(self, test_id): 254 | for test in self.failures: 255 | if test[0].test_id == test_id: 256 | self.failures.remove(test) 257 | for test in self.errors: 258 | if test[0].test_id == test_id: 259 | self.errors.remove(test) 260 | 261 | def printErrorList(self, flavour, errors): 262 | """ 263 | Writes information about the FAIL or ERROR to the stream. 264 | """ 265 | for test_info, dummy in errors: 266 | self.stream.writeln(self.separator1) 267 | self.stream.writeln( 268 | '{} [{:6f}s]: {}'.format(flavour, test_info.elapsed_time, 269 | test_info.get_description()) 270 | ) 271 | self.stream.writeln(self.separator2) 272 | self.stream.writeln('%s' % test_info.get_error_info()) 273 | 274 | def _get_info_by_testcase(self): 275 | """ Organize test results by TestCase module. """ 276 | 277 | tests_by_testcase = {} 278 | for tests in (self.successes, self.failures, self.errors, self.skipped): 279 | for test_info in tests: 280 | if isinstance(test_info, tuple): 281 | test_info = test_info[0] 282 | testcase_name = test_info.test_name 283 | if testcase_name not in tests_by_testcase: 284 | tests_by_testcase[testcase_name] = [] 285 | tests_by_testcase[testcase_name].append(test_info) 286 | 287 | return tests_by_testcase 288 | 289 | def get_report_attributes(self, tests, start_time, elapsed_time): 290 | """ Setup the header info for the report. """ 291 | 292 | failures = errors = skips = success = 0 293 | for test in tests: 294 | outcome = test.outcome 295 | if outcome == test.ERROR: 296 | errors += 1 297 | elif outcome == test.FAILURE: 298 | failures += 1 299 | elif outcome == test.SKIP: 300 | skips += 1 301 | elif outcome == test.SUCCESS: 302 | success += 1 303 | status = [] 304 | 305 | if success: 306 | status.append('Pass: {}'.format(success)) 307 | if failures: 308 | status.append('Fail: {}'.format(failures)) 309 | if errors: 310 | status.append('Error: {}'.format(errors)) 311 | if skips: 312 | status.append('Skip: {}'.format(skips)) 313 | result = ', '.join(status) 314 | 315 | hearders = { 316 | "start_time": str(start_time)[:19], 317 | "duration": str(elapsed_time), 318 | "status": result 319 | } 320 | total_runned_test = success + skips + errors + failures 321 | return hearders, total_runned_test 322 | 323 | def _test_method_name(self, test_id): 324 | """ Return a test name of the test id. """ 325 | return test_id.split('.')[-1] 326 | 327 | def report_testcase(self, testCase, test_cases_list): 328 | """ Return a list with test name or desciption, status and error 329 | msg if fail or skip. """ 330 | 331 | test_name = self._test_method_name(testCase.test_id) 332 | test_description = testCase.test_description 333 | desc = "{} ({})".format(test_description, test_name) 334 | class_name = re.sub(r'^__main__.', '', testCase.id()) 335 | class_name = class_name.rpartition('.')[0] 336 | status = ('success', 'danger', 'warning', 'info')[testCase.outcome] 337 | test_doc = testCase.test_doc 338 | test_doc = test_doc if test_doc else '======' 339 | test_doc = test_doc.split('\n', 1)[-1] 340 | sep = re.findall(r'(======+)', test_doc)[0] if len(re.findall(r'(======+)', test_doc)) != 0 else '======' 341 | test_doc = test_doc.split(sep, 1) 342 | detail_step = test_doc[0] 343 | expect_result = test_doc[1] if len(test_doc) > 1 else '' 344 | error_type = "{}\n\n
ErrorType: ".format('
'.join(detail_step.split('\n'))).strip() 345 | if testCase.outcome != testCase.SKIP and testCase.outcome != testCase.SUCCESS: 346 | error_type += testCase.err[0].__name__ 347 | error_message = testCase.err[1] 348 | else: 349 | error_message = testCase.err 350 | times = "%.6f" % testCase.elapsed_time 351 | error_message = error_message if not error_message else html.escape(str(error_message)) 352 | rerun = testCase.rerun 353 | screenshot = testCase.screenshot if testCase.screenshot is not None else '' 354 | return test_cases_list.append([desc, class_name, expect_result, status, error_type, error_message, times, rerun, screenshot]) 355 | 356 | def get_test_number(self, test): 357 | """ Return the number of a test case or 0. """ 358 | test_number = 0 359 | try: 360 | test_name = self._test_method_name(test.test_id) 361 | test_number = int(test_name.split('_')[1]) 362 | 363 | except ValueError: 364 | pass 365 | return test_number 366 | 367 | def sort_test_list(self, test_list): 368 | """ Try to sort a list of test runned by numbers if have. """ 369 | return sorted(test_list, key=self.get_test_number) 370 | 371 | def report_tests(self, test_class_name, tests, testRunner): 372 | """ Generate a html file for a given suite. """ 373 | report_name = testRunner.report_title 374 | start_time = testRunner.start_time 375 | elapsed_time = testRunner.time_taken 376 | 377 | report_headers, total_test = self.get_report_attributes(tests, start_time, elapsed_time) 378 | testcase_name = '' 379 | test_cases_list = [] 380 | 381 | # Sort test by number if they have 382 | tests = self.sort_test_list(tests) 383 | 384 | for test in tests: 385 | self.report_testcase(test, test_cases_list) 386 | 387 | html_file = render_html(testRunner.template, title=report_name, 388 | headers=report_headers, 389 | testcase_name=testcase_name, 390 | tests_results=test_cases_list, 391 | total_tests=total_test) 392 | return html_file 393 | 394 | def generate_reports(self, testRunner): 395 | """ Generate report for all given runned test object. """ 396 | all_results = self._get_info_by_testcase() 397 | all_tests = list() 398 | for testcase_class_name, all_test in all_results.items(): 399 | all_tests += all_test 400 | if testRunner.outsuffix: 401 | testcase_class_name = "report.html" 402 | 403 | tests = self.report_tests(testcase_class_name, all_tests, 404 | testRunner) 405 | self.generate_file(testRunner.output, testcase_class_name, 406 | tests) 407 | 408 | def generate_file(self, output, report_name, report): 409 | """ Generate the report file in the given path. """ 410 | current_dir = os.getcwd() 411 | dir_to = os.path.join(current_dir, output) 412 | if not os.path.exists(dir_to): 413 | os.makedirs(dir_to) 414 | path_file = os.path.join(dir_to, report_name) 415 | with open(path_file, 'w', encoding='utf8') as report_file: 416 | report_file.write(report) 417 | 418 | def _exc_info_to_string(self, err, test): 419 | """ Converts a sys.exc_info()-style tuple of values into a string.""" 420 | if six.PY3: 421 | # It works fine in python 3 422 | try: 423 | return super(_TextTestResult, self)._exc_info_to_string( 424 | err, test) 425 | except AttributeError: 426 | # We keep going using the legacy python <= 2 way 427 | pass 428 | 429 | # This comes directly from python2 unittest 430 | exctype, value, tb = err 431 | # Skip test runner traceback levels 432 | while tb and self._is_relevant_tb_level(tb): 433 | tb = tb.tb_next 434 | 435 | if exctype is test.failureException: 436 | # Skip assert*() traceback levels 437 | length = self._count_relevant_tb_levels(tb) 438 | msgLines = traceback.format_exception(exctype, value, tb, length) 439 | else: 440 | msgLines = traceback.format_exception(exctype, value, tb) 441 | 442 | if self.buffer: 443 | # Only try to get sys.stdout and sys.sterr as they not be 444 | # StringIO yet, e.g. when test fails during __call__ 445 | try: 446 | output = sys.stdout.getvalue() 447 | except AttributeError: 448 | output = None 449 | try: 450 | error = sys.stderr.getvalue() 451 | except AttributeError: 452 | error = None 453 | if output: 454 | if not output.endswith('\n'): 455 | output += '\n' 456 | msgLines.append(output) 457 | if error: 458 | if not error.endswith('\n'): 459 | error += '\n' 460 | msgLines.append(error) 461 | # This is the extra magic to make sure all lines are str 462 | encoding = getattr(sys.stdout, 'encoding', 'utf-8') 463 | lines = [] 464 | for line in msgLines: 465 | if not isinstance(line, str): 466 | # utf8 shouldnt be hard-coded, but not sure f 467 | line = line.encode(encoding) 468 | lines.append(line) 469 | 470 | return ''.join(lines) 471 | -------------------------------------------------------------------------------- /Core/Runner/HtmlTestRunner/runner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from datetime import datetime 4 | 5 | from unittest import TextTestRunner 6 | from .result import _HtmlTestResult 7 | 8 | UTF8 = "UTF-8" 9 | 10 | 11 | class HTMLTestRunner(TextTestRunner): 12 | """" A test runner class that output the results. """ 13 | 14 | def __init__(self, output, verbosity=2, stream=sys.stderr, 15 | descriptions=True, failfast=False, buffer=False, tb_locals=False, 16 | report_title=None, template=None, resultclass=None, rerun=0): 17 | TextTestRunner.__init__(self, stream, descriptions, verbosity) 18 | self.rerun = rerun 19 | self.verbosity = verbosity 20 | self.output = output 21 | self.encoding = UTF8 22 | self.tb_locals = tb_locals 23 | 24 | self.outsuffix = time.strftime("%Y-%m-%d_%H-%M-%S") 25 | self.elapsed_times = True 26 | if resultclass is None: 27 | self.resultclass = _HtmlTestResult 28 | else: 29 | self.resultclass = resultclass 30 | 31 | self.report_title = report_title or "Test Result" 32 | self.template = template 33 | 34 | def _make_result(self): 35 | """ Create a TestResult object which will be used to store 36 | information about the executed tests. """ 37 | return self.resultclass(self.stream, self.descriptions, self.verbosity, self.elapsed_times) 38 | 39 | def run(self, test): 40 | """ Runs the given testcase or testsuite. """ 41 | try: 42 | 43 | result = self._make_result() 44 | result.failfast = self.failfast 45 | result.tb_locals = self.tb_locals 46 | result.rerun = self.rerun 47 | if hasattr(test, 'properties'): 48 | # junit testsuite properties 49 | result.properties = test.properties 50 | 51 | self.stream.writeln() 52 | self.stream.writeln("Running tests... ") 53 | self.stream.writeln(result.separator2) 54 | 55 | self.start_time = datetime.now() 56 | test(result) 57 | stop_time = datetime.now() 58 | self.time_taken = stop_time - self.start_time 59 | 60 | result.printErrors() 61 | self.stream.writeln(result.separator2) 62 | run = result.testRun 63 | self.stream.writeln("Ran {} test{} in {}".format(run, 64 | run != 1 and "s" or "", str(self.time_taken))) 65 | self.stream.writeln() 66 | 67 | expectedFails = len(result.expectedFailures) 68 | unexpectedSuccesses = len(result.unexpectedSuccesses) 69 | skipped = len(result.skipped) 70 | 71 | infos = [] 72 | if not result.wasSuccessful(): 73 | self.stream.writeln("FAILED") 74 | failed, errors = map(len, (result.failures, result.errors)) 75 | if failed: 76 | infos.append("Failures={0}".format(failed)) 77 | if errors: 78 | infos.append("Errors={0}".format(errors)) 79 | else: 80 | self.stream.writeln("OK") 81 | 82 | if skipped: 83 | infos.append("Skipped={}".format(skipped)) 84 | if expectedFails: 85 | infos.append("expected failures={}".format(expectedFails)) 86 | if unexpectedSuccesses: 87 | infos.append("unexpected successes={}".format(unexpectedSuccesses)) 88 | 89 | if infos: 90 | self.stream.writeln(" ({})".format(", ".join(infos))) 91 | else: 92 | self.stream.writeln("\n") 93 | 94 | self.stream.writeln() 95 | self.stream.writeln('Generating HTML reports... ') 96 | result.generate_reports(self) 97 | finally: 98 | pass 99 | return result 100 | -------------------------------------------------------------------------------- /Core/Runner/HtmlTestRunner/template/report_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 11 | 12 | 13 |
14 |
15 |
16 |

{{title}}

17 |

Start Time: {{headers.start_time}}

18 |

Duration: {{headers.duration}}

19 |

Status: {{headers.status}}

20 |
21 |
22 |
23 |
24 |

        

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for eachTestCase, className,expected, status, errorType, errorMessage, times, rerun, screenshot in tests_results %} 37 | 38 | 39 | 40 | 41 | 42 | 61 | 62 | {% if "success" != status %} 63 | 64 | 68 | 69 | {% endif %} 70 | {% endfor %} 71 | 72 | 75 | 76 | 77 | 80 | 81 | 82 |
TestCaseClassNameExpectedTime  (Rerun)Status
{{eachTestCase}}{{className}}{{expected}}{{times}}  ({{rerun}}) 43 | 44 | {% if "success" == status %} 45 | Pass 46 | {% elif "info" == status %} 47 | Skip 48 | {% elif "danger" == status%} 49 | Fail 50 | {% else %} 51 | Error 52 | {% endif %} 53 | 54 | {% if "success" not in status %} 55 |   56 | {% endif %} 57 | {% if screenshot != '' %} 58 |  Screenshot 59 | {% endif %} 60 |
73 | Total Test Runned: {{total_tests}} 74 | 78 | {{headers.status}} 79 |
83 |
84 |
85 |
86 | 87 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /Core/Runner/TestRunner.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import sys 4 | import time 5 | from datetime import datetime 6 | from unittest import TextTestRunner, _TextTestResult 7 | from .xmlrunner import XMLTestRunner 8 | from .xmlrunner.result import _XMLTestResult 9 | from .HtmlTestRunner.result import _HtmlTestResult 10 | 11 | 12 | class _TestResult(_XMLTestResult, _HtmlTestResult): 13 | def generate_html_reports(self, testRunner): 14 | """ Generate report for all given runned test object. """ 15 | all_results = self._get_info_by_testcase() 16 | all_tests = list() 17 | for testcase_class_name, all_test in all_results.items(): 18 | all_tests += all_test 19 | if testRunner.outsuffix: 20 | testcase_class_name = "report.html" 21 | 22 | tests = self.report_tests(testcase_class_name, all_tests, 23 | testRunner) 24 | self.generate_file(testRunner.output, testcase_class_name, 25 | tests) 26 | 27 | 28 | class TestRunner(XMLTestRunner): 29 | """ 30 | A test runner class that outputs the results in JUnit like XML files. 31 | """ 32 | def __init__(self, output='.', outsuffix=None, stream=sys.stderr, 33 | descriptions=True, verbosity=1, elapsed_times=True, 34 | failfast=False, report_title=None, template=None, tb_locals=False, 35 | buffer=False, encoding='UTF-8', resultclass=None, rerun=0): 36 | TextTestRunner.__init__(self, stream, descriptions, verbosity) 37 | self.rerun = rerun 38 | self.tb_locals = tb_locals 39 | self.verbosity = verbosity 40 | self.output = output 41 | self.encoding = encoding 42 | # None means default timestamped suffix 43 | # '' (empty) means no suffix 44 | if outsuffix is None: 45 | outsuffix = time.strftime("%Y%m%d%H%M%S") 46 | self.outsuffix = outsuffix 47 | self.elapsed_times = elapsed_times 48 | if resultclass is None: 49 | self.resultclass = _TestResult 50 | else: 51 | self.resultclass = resultclass 52 | 53 | self.report_title = report_title or "Test Report" 54 | self.template = template 55 | 56 | def run(self, test): 57 | """ Runs the given testcase or testsuite. """ 58 | try: 59 | result = self._make_result() 60 | result.failfast = self.failfast 61 | result.tb_locals = self.tb_locals 62 | result.rerun = self.rerun 63 | if hasattr(test, 'properties'): 64 | # junit testsuite properties 65 | result.properties = test.properties 66 | 67 | self.stream.writeln() 68 | self.stream.writeln("Running tests... ") 69 | self.stream.writeln(result.separator2) 70 | 71 | self.start_time = datetime.now() 72 | test(result) 73 | stop_time = datetime.now() 74 | self.time_taken = stop_time - self.start_time 75 | 76 | result.printErrors() 77 | self.stream.writeln(result.separator2) 78 | run = result.testRun 79 | self.stream.writeln("Ran {} test{} in {}".format(run, 80 | run != 1 and "s" or "", str(self.time_taken))) 81 | self.stream.writeln() 82 | 83 | expectedFails = len(result.expectedFailures) 84 | unexpectedSuccesses = len(result.unexpectedSuccesses) 85 | skipped = len(result.skipped) 86 | 87 | infos = [] 88 | if not result.wasSuccessful(): 89 | self.stream.write("FAILED") 90 | failed, errors = map(len, (result.failures, result.errors)) 91 | if failed: 92 | infos.append("Failures={0}".format(failed)) 93 | if errors: 94 | infos.append("Errors={0}".format(errors)) 95 | else: 96 | self.stream.write("OK") 97 | 98 | if skipped: 99 | infos.append("Skipped={}".format(skipped)) 100 | if expectedFails: 101 | infos.append("expected failures={}".format(expectedFails)) 102 | if unexpectedSuccesses: 103 | infos.append("unexpected successes={}".format(unexpectedSuccesses)) 104 | 105 | if infos: 106 | self.stream.writeln(" ({})".format(", ".join(infos))) 107 | else: 108 | self.stream.writeln("\n") 109 | 110 | self.stream.writeln() 111 | self.stream.writeln('Generating HTML reports... ') 112 | result.generate_html_reports(self) 113 | self.stream.writeln('Generating XML reports... ') 114 | result.generate_reports(self) 115 | finally: 116 | pass 117 | return result 118 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module provides the XMLTestRunner class, which is heavily based on the 5 | default TextTestRunner. 6 | """ 7 | 8 | # Allow version to be detected at runtime. 9 | from .version import __version__ 10 | 11 | from .runner import XMLTestRunner 12 | 13 | __all__ = ('__version__', 'XMLTestRunner') 14 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/__main__.py: -------------------------------------------------------------------------------- 1 | """Main entry point""" 2 | 3 | import sys 4 | from .unittest import TestProgram 5 | from .runner import XMLTestRunner 6 | 7 | if sys.argv[0].endswith("__main__.py"): 8 | import os.path 9 | # We change sys.argv[0] to make help message more useful 10 | # use executable without path, unquoted 11 | # (it's just a hint anyway) 12 | # (if you have spaces in your executable you get what you deserve!) 13 | executable = os.path.basename(sys.executable) 14 | sys.argv[0] = executable + " -m xmlrunner" 15 | del os 16 | 17 | __unittest = True 18 | 19 | 20 | main = TestProgram 21 | 22 | main( 23 | module=None, testRunner=XMLTestRunner, 24 | # see issue #59 25 | failfast=False, catchbreak=False, buffer=False) 26 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/builder.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import time 4 | import six 5 | 6 | from xml.dom.minidom import Document 7 | 8 | 9 | __all__ = ('TestXMLBuilder', 'TestXMLContext') 10 | 11 | 12 | # see issue #74, the encoding name needs to be one of 13 | # http://www.iana.org/assignments/character-sets/character-sets.xhtml 14 | UTF8 = 'UTF-8' 15 | 16 | # Workaround for Python bug #5166 17 | # http://bugs.python.org/issue5166 18 | 19 | _char_tail = '' 20 | 21 | if sys.maxunicode > 0x10000: 22 | _char_tail = six.u('%s-%s') % ( 23 | six.unichr(0x10000), 24 | six.unichr(min(sys.maxunicode, 0x10FFFF)) 25 | ) 26 | 27 | _nontext_sub = re.compile( 28 | six.u(r'[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD%s]') % _char_tail, 29 | re.U 30 | ).sub 31 | 32 | 33 | def replace_nontext(text, replacement=six.u('\uFFFD')): 34 | return _nontext_sub(replacement, text) 35 | 36 | 37 | class TestXMLContext(object): 38 | """A XML report file have a distinct hierarchy. The outermost element is 39 | 'testsuites', which contains one or more 'testsuite' elements. The role of 40 | these elements is to give the proper context to 'testcase' elements. 41 | 42 | These contexts have a few things in common: they all have some sort of 43 | counters (i.e. how many testcases are inside that context, how many failed, 44 | and so on), they all have a 'time' attribute indicating how long it took 45 | for their testcases to run, etc. 46 | 47 | The purpose of this class is to abstract the job of composing this 48 | hierarchy while keeping track of counters and how long it took for a 49 | context to be processed. 50 | """ 51 | 52 | # Allowed keys for self.counters 53 | _allowed_counters = ('tests', 'errors', 'failures', 'skipped',) 54 | 55 | def __init__(self, xml_doc, parent_context=None): 56 | """Creates a new instance of a root or nested context (depending whether 57 | `parent_context` is provided or not). 58 | """ 59 | self.xml_doc = xml_doc 60 | self.parent = parent_context 61 | self._start_time = self._stop_time = 0 62 | self.counters = {} 63 | 64 | def element_tag(self): 65 | """Returns the name of the tag represented by this context. 66 | """ 67 | return self.element.tagName 68 | 69 | def begin(self, tag, name): 70 | """Begins the creation of this context in the XML document by creating 71 | an empty tag . 72 | """ 73 | self.element = self.xml_doc.createElement(tag) 74 | self.element.setAttribute('name', replace_nontext(name)) 75 | self._start_time = time.time() 76 | 77 | def end(self): 78 | """Closes this context (started with a call to `begin`) and creates an 79 | attribute for each counter and another for the elapsed time. 80 | """ 81 | self._stop_time = time.time() 82 | self.element.setAttribute('time', self.elapsed_time()) 83 | self._set_result_counters() 84 | return self.element 85 | 86 | def _set_result_counters(self): 87 | """Sets an attribute in this context's tag for each counter considering 88 | what's valid for each tag name. 89 | """ 90 | tag = self.element_tag() 91 | 92 | for counter_name in TestXMLContext._allowed_counters: 93 | valid_counter_for_element = False 94 | 95 | if counter_name == 'skipped': 96 | valid_counter_for_element = ( 97 | tag == 'testsuite' 98 | ) 99 | else: 100 | valid_counter_for_element = ( 101 | tag in ('testsuites', 'testsuite') 102 | ) 103 | 104 | if valid_counter_for_element: 105 | value = six.text_type( 106 | self.counters.get(counter_name, 0) 107 | ) 108 | self.element.setAttribute(counter_name, value) 109 | 110 | def increment_counter(self, counter_name): 111 | """Increments a counter named by `counter_name`, which can be any one 112 | defined in `_allowed_counters`. 113 | """ 114 | if counter_name in TestXMLContext._allowed_counters: 115 | self.counters[counter_name] = \ 116 | self.counters.get(counter_name, 0) + 1 117 | 118 | def elapsed_time(self): 119 | """Returns the time the context took to run between the calls to 120 | `begin()` and `end()`, in seconds. 121 | """ 122 | return format(self._stop_time - self._start_time, '.3f') 123 | 124 | 125 | class TestXMLBuilder(object): 126 | """This class encapsulates most rules needed to create a XML test report 127 | behind a simple interface. 128 | """ 129 | 130 | def __init__(self): 131 | """Creates a new instance. 132 | """ 133 | self._xml_doc = Document() 134 | self._current_context = None 135 | 136 | def current_context(self): 137 | """Returns the current context. 138 | """ 139 | return self._current_context 140 | 141 | def begin_context(self, tag, name): 142 | """Begins a new context in the XML test report, which usually is defined 143 | by one on the tags 'testsuites', 'testsuite', or 'testcase'. 144 | """ 145 | context = TestXMLContext(self._xml_doc, self._current_context) 146 | context.begin(tag, name) 147 | 148 | self._current_context = context 149 | 150 | def context_tag(self): 151 | """Returns the tag represented by the current context. 152 | """ 153 | return self._current_context.element_tag() 154 | 155 | def _create_cdata_section(self, content): 156 | """Returns a new CDATA section containing the string defined in 157 | `content`. 158 | """ 159 | filtered_content = replace_nontext(content) 160 | return self._xml_doc.createCDATASection(filtered_content) 161 | 162 | def append_cdata_section(self, tag, content): 163 | """Appends a tag in the format CDATA into the tag represented 164 | by the current context. Returns the created tag. 165 | """ 166 | element = self._xml_doc.createElement(tag) 167 | 168 | pos = content.find(']]>') 169 | while pos >= 0: 170 | tmp = content[0:pos+2] 171 | element.appendChild(self._create_cdata_section(tmp)) 172 | content = content[pos+2:] 173 | pos = content.find(']]>') 174 | 175 | element.appendChild(self._create_cdata_section(content)) 176 | 177 | self._append_child(element) 178 | return element 179 | 180 | def append(self, tag, content, **kwargs): 181 | """Apends a tag in the format CDATA 182 | into the tag represented by the current context. Returns the created 183 | tag. 184 | """ 185 | element = self._xml_doc.createElement(tag) 186 | 187 | for key, value in kwargs.items(): 188 | filtered_value = replace_nontext(six.text_type(value)) 189 | element.setAttribute(key, filtered_value) 190 | 191 | if content: 192 | element.appendChild(self._create_cdata_section(content)) 193 | 194 | self._append_child(element) 195 | return element 196 | 197 | def _append_child(self, element): 198 | """Appends a tag object represented by `element` into the tag 199 | represented by the current context. 200 | """ 201 | if self._current_context: 202 | self._current_context.element.appendChild(element) 203 | else: 204 | self._xml_doc.appendChild(element) 205 | 206 | def increment_counter(self, counter_name): 207 | """Increments a counter in the current context and their parents. 208 | """ 209 | context = self._current_context 210 | 211 | while context: 212 | context.increment_counter(counter_name) 213 | context = context.parent 214 | 215 | def end_context(self): 216 | """Ends the current context and sets the current context as being the 217 | previous one (if it exists). Also, when a context ends, its tag is 218 | appended in the proper place inside the document. 219 | """ 220 | if not self._current_context: 221 | return False 222 | 223 | element = self._current_context.end() 224 | 225 | self._current_context = self._current_context.parent 226 | self._append_child(element) 227 | 228 | return True 229 | 230 | def finish(self): 231 | """Ends all open contexts and returns a pretty printed version of the 232 | generated XML document. 233 | """ 234 | while self.end_context(): 235 | pass 236 | return self._xml_doc.toprettyxml(indent='\t', encoding=UTF8) 237 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/extra/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSTester/CodeRobotFramework/5db39bf74d17817a994896bbf3963d71ca5b1322/Core/Runner/xmlrunner/extra/__init__.py -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/extra/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSTester/CodeRobotFramework/5db39bf74d17817a994896bbf3963d71ca5b1322/Core/Runner/xmlrunner/extra/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/extra/__pycache__/djangotestrunner.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSTester/CodeRobotFramework/5db39bf74d17817a994896bbf3963d71ca5b1322/Core/Runner/xmlrunner/extra/__pycache__/djangotestrunner.cpython-35.pyc -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/extra/djangotestrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Custom Django test runner that runs the tests using the 5 | XMLTestRunner class. 6 | 7 | This script shows how to use the XMLTestRunner in a Django project. To learn 8 | how to configure a custom TestRunner in a Django project, please read the 9 | Django docs website. 10 | """ 11 | 12 | import xmlrunner 13 | import os.path 14 | from django.conf import settings 15 | from django.test.runner import DiscoverRunner 16 | 17 | 18 | class XMLTestRunner(DiscoverRunner): 19 | 20 | def run_suite(self, suite, **kwargs): 21 | dummy = kwargs # unused 22 | verbosity = getattr(settings, 'TEST_OUTPUT_VERBOSE', 1) 23 | # XXX: verbosity = self.verbosity 24 | if isinstance(verbosity, bool): 25 | verbosity = (1, 2)[verbosity] 26 | descriptions = getattr(settings, 'TEST_OUTPUT_DESCRIPTIONS', False) 27 | output_dir = getattr(settings, 'TEST_OUTPUT_DIR', '.') 28 | single_file = getattr(settings, 'TEST_OUTPUT_FILE_NAME', None) 29 | 30 | kwargs = dict( 31 | verbosity=verbosity, descriptions=descriptions, 32 | failfast=self.failfast) 33 | if single_file is not None: 34 | file_path = os.path.join(output_dir, single_file) 35 | with open(file_path, 'wb') as xml: 36 | return xmlrunner.XMLTestRunner( 37 | output=xml, **kwargs).run(suite) 38 | else: 39 | return xmlrunner.XMLTestRunner( 40 | output=output_dir, **kwargs).run(suite) 41 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/result.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import time 5 | from datetime import datetime 6 | import traceback 7 | import six 8 | import re 9 | from os import path 10 | from six.moves import StringIO 11 | 12 | from .unittest import TestResult, _TextTestResult, failfast 13 | 14 | 15 | # Matches invalid XML1.0 unicode characters, like control characters: 16 | # http://www.w3.org/TR/2006/REC-xml-20060816/#charsets 17 | # http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python 18 | 19 | _illegal_unichrs = [ 20 | (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), 21 | (0x7F, 0x84), (0x86, 0x9F), 22 | (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF), 23 | ] 24 | if sys.maxunicode >= 0x10000: # not narrow build 25 | _illegal_unichrs.extend([ 26 | (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), 27 | (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), 28 | (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), 29 | (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), 30 | (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), 31 | (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 32 | (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), 33 | (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF), 34 | ]) 35 | 36 | _illegal_ranges = [ 37 | "%s-%s" % (six.unichr(low), six.unichr(high)) 38 | for (low, high) in _illegal_unichrs 39 | ] 40 | 41 | INVALID_XML_1_0_UNICODE_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) 42 | 43 | 44 | STDOUT_LINE = '\nStdout:\n%s' 45 | STDERR_LINE = '\nStderr:\n%s' 46 | 47 | 48 | def xml_safe_unicode(base, encoding='utf-8'): 49 | """Return a unicode string containing only valid XML characters. 50 | 51 | encoding - if base is a byte string it is first decoded to unicode 52 | using this encoding. 53 | """ 54 | if isinstance(base, six.binary_type): 55 | base = base.decode(encoding) 56 | return INVALID_XML_1_0_UNICODE_RE.sub('', base) 57 | 58 | 59 | def to_unicode(data): 60 | """Returns unicode in Python2 and str in Python3""" 61 | if six.PY3: 62 | return six.text_type(data) 63 | try: 64 | # Try utf8 65 | return six.text_type(data) 66 | except UnicodeDecodeError: 67 | return repr(data).decode('utf8', 'replace') 68 | 69 | 70 | def safe_unicode(data, encoding=None): 71 | return xml_safe_unicode(to_unicode(data), encoding) 72 | 73 | 74 | def testcase_name(test_method): 75 | testcase = type(test_method) 76 | 77 | # Ignore module name if it is '__main__' 78 | module = testcase.__module__ + '.' 79 | if module == '__main__.': 80 | module = '' 81 | result = module + testcase.__name__ 82 | return result 83 | 84 | 85 | class _TestInfo(object): 86 | """ 87 | This class keeps useful information about the execution of a 88 | test method. 89 | """ 90 | 91 | # Possible test outcomes 92 | (SUCCESS, FAILURE, ERROR, SKIP) = range(4) 93 | 94 | def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=None): 95 | self.test_result = test_result 96 | self.outcome = outcome 97 | self.start_time = 0 98 | self.stop_time = 0 99 | self.elapsed_time = 0 100 | self.err = err 101 | self.stdout = test_result._stdout_data 102 | self.stderr = test_result._stderr_data 103 | self.screenshot = '' 104 | self.rerun = 0 105 | test_description = self.test_result.getDescription(test_method) 106 | self.test_description = test_description.split()[0] if len(test_description.split())<3 else test_description.split()[-1] 107 | 108 | self.test_exception_info = ( 109 | '' if outcome in (self.SUCCESS, self.SKIP) 110 | else self.test_result._exc_info_to_string( 111 | self.err, test_method) 112 | ) 113 | 114 | self.suite_doc = test_method.__doc__ 115 | self.test_doc = test_method._testMethodDoc 116 | self.test_name = testcase_name(test_method) 117 | self.test_id = test_method.id() 118 | if subTest: 119 | self.test_id = subTest.id() 120 | 121 | def id(self): 122 | return self.test_id 123 | 124 | def test_finished(self): 125 | """Save info that can only be calculated once a test has run. 126 | """ 127 | self.elapsed_time = \ 128 | self.test_result.stop_time - self.test_result.start_time 129 | self.start_time = self.test_result.start_time 130 | self.stop_time = self.test_result.stop_time 131 | 132 | def get_description(self): 133 | """ 134 | Return a text representation of the test method. 135 | """ 136 | return self.test_description 137 | 138 | def get_error_info(self): 139 | """ 140 | Return a text representation of an exception thrown by a test 141 | method. 142 | """ 143 | return self.test_exception_info 144 | 145 | 146 | class _XMLTestResult(_TextTestResult): 147 | """ 148 | A test result class that can express test results in a XML report. 149 | 150 | Used by XMLTestRunner. 151 | """ 152 | def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, 153 | elapsed_times=True, properties=None, infoclass=None): 154 | _TextTestResult.__init__(self, stream, descriptions, verbosity) 155 | self.rerun = 0 156 | self.retry = 0 157 | self.tb_locals = False 158 | self.buffer = True # we are capturing test output 159 | self._stdout_data = None 160 | self._stderr_data = None 161 | self.successes = [] 162 | self.tested_fail_error = [] 163 | self.callback = None 164 | self.elapsed_times = elapsed_times 165 | self.properties = properties # junit testsuite properties 166 | if infoclass is None: 167 | self.infoclass = _TestInfo 168 | else: 169 | self.infoclass = infoclass 170 | self.testRun = 0 171 | 172 | def _prepare_callback(self, test_info, target_list, verbose_str, 173 | short_str): 174 | """ 175 | Appends a `infoclass` to the given target list and sets a callback 176 | method to be called by stopTest method. 177 | """ 178 | target_list.append(test_info) 179 | 180 | def callback(): 181 | """Prints the test method outcome to the stream, as well as 182 | the elapsed time. 183 | """ 184 | test_info.test_finished() 185 | 186 | # Ignore the elapsed times for a more reliable unit testing 187 | if not self.elapsed_times: 188 | self.start_time = self.stop_time = 0 189 | 190 | if self.showAll: 191 | self.stream.writeln( 192 | '%s (%.6fs)' % (verbose_str, test_info.elapsed_time) 193 | ) 194 | elif self.dots: 195 | self.stream.write(short_str) 196 | 197 | self.callback = callback 198 | 199 | def startTest(self, test): 200 | """ 201 | Called before execute each test method. 202 | """ 203 | if self.retry == 0: 204 | self.testRun += 1 205 | self.start_time = time.time() 206 | TestResult.startTest(self, test) 207 | 208 | if self.showAll: 209 | self.stream.write(' ' + self.getDescription(test)) 210 | self.stream.write(" ... ") 211 | 212 | def _save_output_data(self): 213 | # Only try to get sys.stdout and sys.sterr as they not be 214 | # StringIO yet, e.g. when test fails during __call__ 215 | try: 216 | self._stdout_data = sys.stdout.getvalue() 217 | self._stderr_data = sys.stderr.getvalue() 218 | except AttributeError: 219 | pass 220 | 221 | def stopTest(self, test): 222 | """ 223 | Called after execute each test method. 224 | """ 225 | self._save_output_data() 226 | # self._stdout_data = sys.stdout.getvalue() 227 | # self._stderr_data = sys.stderr.getvalue() 228 | 229 | _TextTestResult.stopTest(self, test) 230 | self.stop_time = time.time() 231 | 232 | if self.callback and callable(self.callback): 233 | self.callback() 234 | self.callback = None 235 | 236 | if self.rerun > self.retry: 237 | self.retry += 1 238 | self.stream.write('Rerun {} time...'.format(self.retry)) 239 | test(self) 240 | else: 241 | self.retry = 0 242 | 243 | def addSuccess(self, test): 244 | """ 245 | Called when a test executes successfully. 246 | """ 247 | self._save_output_data() 248 | testinfo = self.infoclass(self, test) 249 | testinfo.rerun = self.retry 250 | self._prepare_callback( 251 | testinfo, self.successes, 'OK', '.' 252 | ) 253 | self.retry = self.rerun 254 | if testinfo.test_id in self.tested_fail_error: 255 | self._remove_test(testinfo.test_id) 256 | 257 | @failfast 258 | def addFailure(self, test, err): 259 | """ 260 | Called when a test method fails. 261 | """ 262 | self._save_output_data() 263 | testinfo = self.infoclass( 264 | self, test, self.infoclass.FAILURE, err) 265 | if testinfo.test_id in self.tested_fail_error: 266 | self._remove_test(testinfo.test_id) 267 | try: 268 | testinfo.screenshot = test.driver.capture_page_screenshot() 269 | test.driver.close_browser() 270 | except Exception as e: 271 | pass 272 | testinfo.rerun = self.retry 273 | self.failures.append((testinfo, 274 | self._exc_info_to_string(err, test))) 275 | self.tested_fail_error.append(testinfo.test_id) 276 | self._prepare_callback(testinfo, [], 'FAIL', 'F') 277 | 278 | @failfast 279 | def addError(self, test, err): 280 | """ 281 | Called when a test method raises an error. 282 | """ 283 | self._save_output_data() 284 | testinfo = self.infoclass( 285 | self, test, self.infoclass.ERROR, err) 286 | if testinfo.test_id in self.tested_fail_error: 287 | self._remove_test(testinfo.test_id) 288 | try: 289 | testinfo.screenshot = test.driver.capture_page_screenshot() 290 | test.driver.close_browser() 291 | except Exception as e: 292 | pass 293 | testinfo.rerun = self.retry 294 | self.errors.append((testinfo, 295 | self._exc_info_to_string(err, test))) 296 | self.tested_fail_error.append(testinfo.test_id) 297 | self._prepare_callback(testinfo, [], 'ERROR', 'E') 298 | 299 | def addSubTest(self, testcase, test, err): 300 | """ 301 | Called when a subTest method raises an error. 302 | """ 303 | if err is not None: 304 | self._save_output_data() 305 | testinfo = self.infoclass( 306 | self, testcase, self.infoclass.ERROR, err, subTest=test) 307 | if testinfo.test_id in self.tested_fail_error: 308 | self._remove_test(testinfo.test_id) 309 | try: 310 | testinfo.screenshot = test.driver.capture_page_screenshot() 311 | test.driver.close_browser() 312 | except Exception as e: 313 | pass 314 | testinfo.rerun = self.retry 315 | self.errors.append(( 316 | testinfo, 317 | self._exc_info_to_string(err, testcase) 318 | )) 319 | self.tested_fail_error.append(testinfo.test_id) 320 | self._prepare_callback(testinfo, [], 'ERROR', 'E') 321 | 322 | def addSkip(self, test, reason): 323 | """ 324 | Called when a test method was skipped. 325 | """ 326 | self._save_output_data() 327 | testinfo = self.infoclass( 328 | self, test, self.infoclass.SKIP, reason) 329 | self.skipped.append((testinfo, reason)) 330 | self._prepare_callback(testinfo, [], 'SKIP', 'S') 331 | self.retry = self.rerun 332 | 333 | def _remove_test(self, test_id): 334 | for test in self.failures: 335 | if test[0].test_id == test_id: 336 | self.failures.remove(test) 337 | for test in self.errors: 338 | if test[0].test_id == test_id: 339 | self.errors.remove(test) 340 | 341 | def printErrorList(self, flavour, errors): 342 | """ 343 | Writes information about the FAIL or ERROR to the stream. 344 | """ 345 | for test_info, dummy in errors: 346 | self.stream.writeln(self.separator1) 347 | self.stream.writeln( 348 | '%s [%.6fs]: %s' % (flavour, test_info.elapsed_time, 349 | test_info.get_description()) 350 | ) 351 | self.stream.writeln(self.separator2) 352 | self.stream.writeln('%s' % test_info.get_error_info()) 353 | 354 | def _get_info_by_testcase(self): 355 | """ 356 | Organizes test results by TestCase module. This information is 357 | used during the report generation, where a XML report will be created 358 | for each TestCase. 359 | """ 360 | tests_by_testcase = {} 361 | for tests in (self.successes, self.failures, self.errors, 362 | self.skipped): 363 | for test_info in tests: 364 | if isinstance(test_info, tuple): 365 | # This is a skipped, error or a failure test case 366 | test_info = test_info[0] 367 | testcase_name = test_info.suite_doc if test_info.suite_doc else test_info.test_name 368 | if testcase_name not in tests_by_testcase: 369 | tests_by_testcase[testcase_name] = [] 370 | tests_by_testcase[testcase_name].append(test_info) 371 | 372 | return tests_by_testcase 373 | 374 | def _report_testsuite_properties(xml_testsuite, xml_document, properties): 375 | if properties: 376 | xml_properties = xml_document.createElement('properties') 377 | xml_testsuite.appendChild(xml_properties) 378 | for key, value in properties.items(): 379 | prop = xml_document.createElement('property') 380 | prop.setAttribute('name', str(key)) 381 | prop.setAttribute('value', str(value)) 382 | xml_properties.appendChild(prop) 383 | 384 | _report_testsuite_properties = staticmethod(_report_testsuite_properties) 385 | 386 | def _report_testsuite(suite_name, tests, xml_document, parentElement, 387 | properties): 388 | """ 389 | Appends the testsuite section to the XML document. 390 | """ 391 | testsuite = xml_document.createElement('testsuite') 392 | parentElement.appendChild(testsuite) 393 | 394 | testsuite.setAttribute('name', suite_name) 395 | testsuite.setAttribute('tests', str(len(tests))) 396 | 397 | testsuite.setAttribute( 398 | 'time', '%.6f' % sum(map(lambda e: e.elapsed_time, tests)) 399 | ) 400 | failures = filter(lambda e: e.outcome == e.FAILURE, tests) 401 | failures = len(list(failures)) 402 | testsuite.setAttribute('failures', str(failures)) 403 | 404 | errors = filter(lambda e: e.outcome == e.ERROR, tests) 405 | errors = len(list(errors)) 406 | testsuite.setAttribute('errors', str(errors)) 407 | 408 | skips = filter(lambda e: e.outcome == _TestInfo.SKIP, tests) 409 | skips = len(list(skips)) 410 | testsuite.setAttribute('skipped', str(skips)) 411 | 412 | parentElement.setAttribute('tests', str(int(parentElement.getAttribute('tests'))+len(tests))) 413 | parentElement.setAttribute('failures', str(int(parentElement.getAttribute('failures'))+failures)) 414 | parentElement.setAttribute('errors', str(int(parentElement.getAttribute('errors'))+errors)) 415 | parentElement.setAttribute('skipped', str(int(parentElement.getAttribute('skipped'))+skips)) 416 | parentElement.setAttribute('time', '%.6f' % (sum(map(lambda e: e.elapsed_time, tests))+float(parentElement.getAttribute('time')))) 417 | 418 | _XMLTestResult._report_testsuite_properties( 419 | testsuite, xml_document, properties) 420 | 421 | for test in tests: 422 | _XMLTestResult._report_testcase(test, testsuite, xml_document) 423 | 424 | systemout = xml_document.createElement('system-out') 425 | testsuite.appendChild(systemout) 426 | 427 | stdout = StringIO() 428 | for test in tests: 429 | # Merge the stdout from the tests in a class 430 | if test.stdout is not None: 431 | stdout.write(test.stdout) 432 | _XMLTestResult._createCDATAsections( 433 | xml_document, systemout, stdout.getvalue()) 434 | 435 | systemerr = xml_document.createElement('system-err') 436 | testsuite.appendChild(systemerr) 437 | 438 | stderr = StringIO() 439 | for test in tests: 440 | # Merge the stderr from the tests in a class 441 | if test.stderr is not None: 442 | stderr.write(test.stderr) 443 | _XMLTestResult._createCDATAsections( 444 | xml_document, systemerr, stderr.getvalue()) 445 | 446 | return testsuite 447 | 448 | _report_testsuite = staticmethod(_report_testsuite) 449 | 450 | def _test_method_name(test_id): 451 | """ 452 | Returns the test method name. 453 | """ 454 | return test_id.split('.')[-1] 455 | 456 | _test_method_name = staticmethod(_test_method_name) 457 | 458 | def _createCDATAsections(xmldoc, node, text): 459 | text = safe_unicode(text) 460 | pos = text.find(']]>') 461 | while pos >= 0: 462 | tmp = text[0:pos+2] 463 | cdata = xmldoc.createCDATASection(tmp) 464 | node.appendChild(cdata) 465 | text = text[pos+2:] 466 | pos = text.find(']]>') 467 | cdata = xmldoc.createCDATASection(text) 468 | node.appendChild(cdata) 469 | 470 | _createCDATAsections = staticmethod(_createCDATAsections) 471 | 472 | def _report_testcase(test_result, xml_testsuite, xml_document): 473 | """ 474 | Appends a testcase section to the XML document. 475 | """ 476 | testcase = xml_document.createElement('testcase') 477 | xml_testsuite.appendChild(testcase) 478 | 479 | class_name = re.sub(r'^__main__.', '', test_result.id()) 480 | class_name = class_name.rpartition('.')[0] 481 | testcase.setAttribute('classname', class_name) 482 | testcase.setAttribute( 483 | 'name', '{} ({})'.format(_XMLTestResult._test_method_name(test_result.test_description), test_result.test_id.split('.')[-1]) 484 | ) 485 | 486 | testcase.setAttribute('starttime', datetime.fromtimestamp(test_result.start_time).strftime('%Y-%m-%d %H:%M:%S.%f')) 487 | testcase.setAttribute('stoptime', datetime.fromtimestamp(test_result.stop_time).strftime('%Y-%m-%d %H:%M:%S.%f')) 488 | testcase.setAttribute('time', '%.6f' % test_result.elapsed_time) 489 | testcase.setAttribute('status', "PASS") 490 | testcase.setAttribute('rerun', str(test_result.rerun)) 491 | testcase.setAttribute('screenshot', test_result.screenshot) 492 | property_step = xml_document.createElement('step') 493 | property_expected = xml_document.createElement('expected') 494 | testcase.appendChild(property_step) 495 | testcase.appendChild(property_expected) 496 | test_doc = test_result.test_doc 497 | test_doc = test_doc if test_doc else '======' 498 | test_doc = test_doc.split('\n', 1)[-1] 499 | sep = re.findall(r'(======+)', test_doc)[0] if len(re.findall(r'(======+)', test_doc)) != 0 else '======' 500 | test_doc = test_doc.split(sep, 1) 501 | detail_step = test_doc[0] 502 | expected = test_doc[1] if len(test_doc) > 1 else '' 503 | property_step.setAttribute('message', detail_step) 504 | property_expected.setAttribute('message', expected.strip()) 505 | 506 | if (test_result.outcome != test_result.SUCCESS): 507 | elem_name = ('failure', 'error', 'skipped')[test_result.outcome-1] 508 | failure = xml_document.createElement(elem_name) 509 | testcase.appendChild(failure) 510 | if test_result.outcome != test_result.SKIP: 511 | testcase.setAttribute('status', "FAIL") 512 | failure.setAttribute( 513 | 'type', 514 | safe_unicode(test_result.err[0].__name__) 515 | ) 516 | failure.setAttribute( 517 | 'message', 518 | safe_unicode(test_result.err[1]) 519 | ) 520 | error_info = safe_unicode(test_result.get_error_info()) 521 | _XMLTestResult._createCDATAsections( 522 | xml_document, failure, error_info) 523 | else: 524 | testcase.setAttribute('status', "SKIP") 525 | failure.setAttribute('type', 'skip') 526 | failure.setAttribute('message', safe_unicode(test_result.err)) 527 | 528 | _report_testcase = staticmethod(_report_testcase) 529 | 530 | def generate_reports(self, test_runner): 531 | """ 532 | Generates the XML reports to a given XMLTestRunner object. 533 | """ 534 | from xml.dom.minidom import Document 535 | all_results = self._get_info_by_testcase() 536 | 537 | outputHandledAsString = \ 538 | isinstance(test_runner.output, six.string_types) 539 | 540 | if (outputHandledAsString and not os.path.exists(test_runner.output)): 541 | os.makedirs(test_runner.output) 542 | 543 | doc = Document() 544 | testsuite = doc.createElement('testsuites') 545 | doc.appendChild(testsuite) 546 | parentElement = testsuite 547 | 548 | parentElement.setAttribute('name', test_runner.report_title) 549 | parentElement.setAttribute('tests', '0') 550 | parentElement.setAttribute('time', '0.000') 551 | parentElement.setAttribute('failures', '0') 552 | parentElement.setAttribute('errors', '0') 553 | parentElement.setAttribute('skipped', '0') 554 | 555 | for suite, tests in all_results.items(): 556 | suite_name = suite 557 | if test_runner.outsuffix: 558 | # not checking with 'is not None', empty means no suffix. 559 | suite_name = '%s-%s' % (suite, test_runner.outsuffix) 560 | 561 | # Build the XML file 562 | testsuite = _XMLTestResult._report_testsuite( 563 | suite_name, tests, doc, parentElement, self.properties 564 | ) 565 | xml_content = doc.toprettyxml( 566 | indent='\t', 567 | encoding=test_runner.encoding 568 | ) 569 | 570 | if outputHandledAsString: 571 | filename = path.join( 572 | test_runner.output, 573 | 'output.xml') 574 | with open(filename, 'wb') as report_file: 575 | report_file.write(xml_content) 576 | 577 | if not outputHandledAsString: 578 | # Assume that test_runner.output is a stream 579 | test_runner.output.write(xml_content) 580 | 581 | def _exc_info_to_string(self, err, test): 582 | """Converts a sys.exc_info()-style tuple of values into a string.""" 583 | if six.PY3: 584 | # It works fine in python 3 585 | try: 586 | return super(_XMLTestResult, self)._exc_info_to_string( 587 | err, test) 588 | except AttributeError: 589 | # We keep going using the legacy python <= 2 way 590 | pass 591 | 592 | # This comes directly from python2 unittest 593 | exctype, value, tb = err 594 | # Skip test runner traceback levels 595 | while tb and self._is_relevant_tb_level(tb): 596 | tb = tb.tb_next 597 | 598 | if exctype is test.failureException: 599 | # Skip assert*() traceback levels 600 | length = self._count_relevant_tb_levels(tb) 601 | msgLines = traceback.format_exception(exctype, value, tb, length) 602 | else: 603 | msgLines = traceback.format_exception(exctype, value, tb) 604 | 605 | if self.buffer: 606 | # Only try to get sys.stdout and sys.sterr as they not be 607 | # StringIO yet, e.g. when test fails during __call__ 608 | try: 609 | output = sys.stdout.getvalue() 610 | except AttributeError: 611 | output = None 612 | try: 613 | error = sys.stderr.getvalue() 614 | except AttributeError: 615 | error = None 616 | if output: 617 | if not output.endswith('\n'): 618 | output += '\n' 619 | msgLines.append(STDOUT_LINE % output) 620 | if error: 621 | if not error.endswith('\n'): 622 | error += '\n' 623 | msgLines.append(STDERR_LINE % error) 624 | # This is the extra magic to make sure all lines are str 625 | encoding = getattr(sys.stdout, 'encoding', 'utf-8') 626 | lines = [] 627 | for line in msgLines: 628 | if not isinstance(line, str): 629 | # utf8 shouldnt be hard-coded, but not sure f 630 | line = line.encode(encoding) 631 | lines.append(line) 632 | 633 | return ''.join(lines) 634 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/runner.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import time 4 | 5 | from .unittest import TextTestRunner 6 | from .result import _XMLTestResult 7 | 8 | # see issue #74, the encoding name needs to be one of 9 | # http://www.iana.org/assignments/character-sets/character-sets.xhtml 10 | UTF8 = 'UTF-8' 11 | 12 | 13 | class XMLTestRunner(TextTestRunner): 14 | """ 15 | A test runner class that outputs the results in JUnit like XML files. 16 | """ 17 | def __init__(self, output='.', outsuffix=None, stream=sys.stderr, 18 | descriptions=True, verbosity=1, elapsed_times=True, 19 | failfast=False, buffer=False, encoding=UTF8, tb_locals=False, 20 | resultclass=None, report_title=None, template=None, rerun=0): 21 | TextTestRunner.__init__(self, stream, descriptions, verbosity) 22 | self.rerun = rerun 23 | self.verbosity = verbosity 24 | self.output = output 25 | self.encoding = encoding 26 | self.tb_locals = tb_locals 27 | # None means default timestamped suffix 28 | # '' (empty) means no suffix 29 | if outsuffix is None: 30 | outsuffix = time.strftime("%Y%m%d%H%M%S") 31 | self.outsuffix = outsuffix 32 | self.elapsed_times = elapsed_times 33 | if resultclass is None: 34 | self.resultclass = _XMLTestResult 35 | else: 36 | self.resultclass = resultclass 37 | 38 | self.report_title = report_title or "Test Report" 39 | self.template = template 40 | 41 | def _make_result(self): 42 | """ 43 | Creates a TestResult object which will be used to store 44 | information about the executed tests. 45 | """ 46 | # override in subclasses if necessary. 47 | return self.resultclass(self.stream, self.descriptions, self.verbosity, self.elapsed_times) 48 | 49 | def run(self, test): 50 | """ 51 | Runs the given test case or test suite. 52 | """ 53 | try: 54 | # Prepare the test execution 55 | result = self._make_result() 56 | result.failfast = self.failfast 57 | result.tb_locals = self.tb_locals 58 | result.rerun = self.rerun 59 | if hasattr(test, 'properties'): 60 | # junit testsuite properties 61 | result.properties = test.properties 62 | 63 | # Print a nice header 64 | self.stream.writeln() 65 | self.stream.writeln('Running tests...') 66 | self.stream.writeln(result.separator2) 67 | 68 | # Execute tests 69 | start_time = time.time() 70 | test(result) 71 | stop_time = time.time() 72 | time_taken = stop_time - start_time 73 | 74 | # Print results 75 | result.printErrors() 76 | self.stream.writeln(result.separator2) 77 | run = result.testRun 78 | self.stream.writeln("Ran %d test%s in %.6fs" % ( 79 | run, run != 1 and "s" or "", time_taken) 80 | ) 81 | self.stream.writeln() 82 | 83 | # other metrics 84 | expectedFails = len(result.expectedFailures) 85 | unexpectedSuccesses = len(result.unexpectedSuccesses) 86 | skipped = len(result.skipped) 87 | 88 | # Error traces 89 | infos = [] 90 | if not result.wasSuccessful(): 91 | self.stream.write("FAILED") 92 | failed, errored = map(len, (result.failures, result.errors)) 93 | if failed: 94 | infos.append("failures={0}".format(failed)) 95 | if errored: 96 | infos.append("errors={0}".format(errored)) 97 | else: 98 | self.stream.write("OK") 99 | 100 | if skipped: 101 | infos.append("skipped={0}".format(skipped)) 102 | if expectedFails: 103 | infos.append("expected failures={0}".format(expectedFails)) 104 | if unexpectedSuccesses: 105 | infos.append("unexpected successes={0}".format( 106 | unexpectedSuccesses)) 107 | 108 | if infos: 109 | self.stream.writeln(" ({0})".format(", ".join(infos))) 110 | else: 111 | self.stream.write("\n") 112 | 113 | # Generate reports 114 | self.stream.writeln() 115 | self.stream.writeln('Generating XML reports...') 116 | result.generate_reports(self) 117 | finally: 118 | pass 119 | 120 | return result 121 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/unittest.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import absolute_import 3 | 4 | import sys 5 | # pylint: disable-msg=W0611 6 | import unittest 7 | from unittest import TextTestRunner 8 | from unittest import TestResult, _TextTestResult 9 | from unittest.result import failfast 10 | from unittest.main import TestProgram 11 | try: 12 | from unittest.main import USAGE_AS_MAIN 13 | TestProgram.USAGE = USAGE_AS_MAIN 14 | except ImportError: 15 | pass 16 | 17 | __all__ = ( 18 | 'unittest', 'TextTestRunner', 'TestResult', '_TextTestResult', 19 | 'TestProgram', 'failfast') 20 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/version.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '2.1.0' 3 | -------------------------------------------------------------------------------- /Core/Runner/xmlrunner/xmlrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This module provides the XMLTestRunner class, which is heavily based on the 5 | default TextTestRunner. 6 | """ 7 | 8 | import os 9 | import sys 10 | import time 11 | import codecs 12 | try: 13 | from unittest2.runner import TextTestRunner 14 | from unittest2.runner import TextTestResult as _TextTestResult 15 | from unittest2.result import TestResult 16 | except ImportError: 17 | from unittest import TestResult, _TextTestResult, TextTestRunner 18 | 19 | try: 20 | # Removed in Python 3 21 | from cStringIO import StringIO 22 | except ImportError: 23 | from io import StringIO 24 | 25 | if sys.version_info[0] >= 3: 26 | unicode=str 27 | 28 | # Allow version to be detected at runtime. 29 | from .version import __version__, __version_info__ 30 | 31 | 32 | class _DelegateIO(object): 33 | """ 34 | This class defines an object that captures whatever is written to 35 | a stream or file. 36 | """ 37 | 38 | def __init__(self, delegate): 39 | self._captured = StringIO() 40 | self.delegate = delegate 41 | 42 | def write(self, text): 43 | self._captured.write(text) 44 | self.delegate.write(text) 45 | 46 | def __getattr__(self, attr): 47 | return getattr(self._captured, attr) 48 | 49 | 50 | def testcase_name(test_method): 51 | testcase = type(test_method) 52 | 53 | # Ignore module name if it is '__main__' 54 | module = testcase.__module__ + '.' 55 | if module == '__main__.': 56 | module = '' 57 | result = module + testcase.__name__ 58 | return result 59 | 60 | 61 | class _TestInfo(object): 62 | """ 63 | This class keeps useful information about the execution of a 64 | test method. 65 | """ 66 | 67 | # Possible test outcomes 68 | (SUCCESS, FAILURE, ERROR, SKIP) = range(4) 69 | 70 | def __init__(self, test_result, test_method, outcome=SUCCESS, err=None): 71 | self.test_result = test_result 72 | self.test_method = test_method 73 | self.outcome = outcome 74 | self.elapsed_time = 0 75 | self.err = err 76 | 77 | self.test_description = self.test_result.getDescription(test_method) 78 | self.test_exception_info = ( 79 | '' if outcome in (self.SUCCESS, self.SKIP) 80 | else self.test_result._exc_info_to_string( 81 | self.err, test_method) 82 | ) 83 | 84 | self.test_name = testcase_name(test_method) 85 | self.test_id = test_method.id() 86 | 87 | def id(self): 88 | return self.test_method.id() 89 | 90 | def test_finished(self): 91 | """Save info that can only be calculated once a test has run. 92 | """ 93 | self.elapsed_time = \ 94 | self.test_result.stop_time - self.test_result.start_time 95 | 96 | def get_description(self): 97 | """ 98 | Return a text representation of the test method. 99 | """ 100 | return self.test_description 101 | 102 | def get_error_info(self): 103 | """ 104 | Return a text representation of an exception thrown by a test 105 | method. 106 | """ 107 | return self.test_exception_info 108 | 109 | 110 | class _XMLTestResult(_TextTestResult): 111 | """ 112 | A test result class that can express test results in a XML report. 113 | 114 | Used by XMLTestRunner. 115 | """ 116 | def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, 117 | elapsed_times=True): 118 | _TextTestResult.__init__(self, stream, descriptions, verbosity) 119 | self.successes = [] 120 | self.callback = None 121 | self.elapsed_times = elapsed_times 122 | 123 | def _prepare_callback(self, test_info, target_list, verbose_str, 124 | short_str): 125 | """ 126 | Appends a _TestInfo to the given target list and sets a callback 127 | method to be called by stopTest method. 128 | """ 129 | target_list.append(test_info) 130 | 131 | def callback(): 132 | """Prints the test method outcome to the stream, as well as 133 | the elapsed time. 134 | """ 135 | 136 | test_info.test_finished() 137 | 138 | # Ignore the elapsed times for a more reliable unit testing 139 | if not self.elapsed_times: 140 | self.start_time = self.stop_time = 0 141 | 142 | if self.showAll: 143 | self.stream.writeln( 144 | '%s (%.3fs)' % (verbose_str, test_info.elapsed_time) 145 | ) 146 | elif self.dots: 147 | self.stream.write(short_str) 148 | self.callback = callback 149 | 150 | def startTest(self, test): 151 | """ 152 | Called before execute each test method. 153 | """ 154 | self.start_time = time.time() 155 | TestResult.startTest(self, test) 156 | 157 | if self.showAll: 158 | self.stream.write(' ' + self.getDescription(test)) 159 | self.stream.write(" ... ") 160 | 161 | def stopTest(self, test): 162 | """ 163 | Called after execute each test method. 164 | """ 165 | _TextTestResult.stopTest(self, test) 166 | self.stop_time = time.time() 167 | 168 | if self.callback and callable(self.callback): 169 | self.callback() 170 | self.callback = None 171 | 172 | def addSuccess(self, test): 173 | """ 174 | Called when a test executes successfully. 175 | """ 176 | self._prepare_callback( 177 | _TestInfo(self, test), self.successes, 'OK', '.' 178 | ) 179 | 180 | def addFailure(self, test, err): 181 | """ 182 | Called when a test method fails. 183 | """ 184 | testinfo = _TestInfo(self, test, _TestInfo.ERROR, err) 185 | self.errors.append(( 186 | testinfo, 187 | self._exc_info_to_string(err, test) 188 | )) 189 | self._prepare_callback(testinfo, [], 'FAIL', 'F') 190 | 191 | def addError(self, test, err): 192 | """ 193 | Called when a test method raises an error. 194 | """ 195 | testinfo = _TestInfo(self, test, _TestInfo.ERROR, err) 196 | self.errors.append(( 197 | testinfo, 198 | self._exc_info_to_string(err, test) 199 | )) 200 | self._prepare_callback(testinfo, [], 'ERROR', 'E') 201 | 202 | def addSkip(self, test, reason): 203 | """ 204 | Called when a test method was skipped. 205 | """ 206 | testinfo = _TestInfo(self, test, _TestInfo.SKIP, reason) 207 | self.skipped.append((testinfo, reason)) 208 | self._prepare_callback(testinfo, [], 'SKIP', 'S') 209 | 210 | def printErrorList(self, flavour, errors): 211 | """ 212 | Writes information about the FAIL or ERROR to the stream. 213 | """ 214 | for test_info, error in errors: 215 | self.stream.writeln(self.separator1) 216 | self.stream.writeln( 217 | '%s [%.3fs]: %s' % (flavour, test_info.elapsed_time, 218 | test_info.get_description()) 219 | ) 220 | self.stream.writeln(self.separator2) 221 | self.stream.writeln('%s' % test_info.get_error_info()) 222 | 223 | def _get_info_by_testcase(self, outsuffix): 224 | """ 225 | Organizes test results by TestCase module. This information is 226 | used during the report generation, where a XML report will be created 227 | for each TestCase. 228 | """ 229 | tests_by_testcase = {} 230 | 231 | for tests in (self.successes, self.failures, self.errors, self.skipped): 232 | for test_info in tests: 233 | if isinstance(test_info, tuple): 234 | # This is a skipped, error or a failure test case 235 | test_info = test_info[0] 236 | testcase_name = test_info.test_name 237 | if not testcase_name in tests_by_testcase: 238 | tests_by_testcase[testcase_name] = [] 239 | tests_by_testcase[testcase_name].append(test_info) 240 | 241 | return tests_by_testcase 242 | 243 | def _report_testsuite(suite_name, outsuffix, tests, xml_document): 244 | """ 245 | Appends the testsuite section to the XML document. 246 | """ 247 | testsuite = xml_document.createElement('testsuite') 248 | xml_document.appendChild(testsuite) 249 | 250 | testsuite.setAttribute('name', "%s-%s" % (suite_name, outsuffix)) 251 | testsuite.setAttribute('tests', str(len(tests))) 252 | 253 | testsuite.setAttribute( 254 | 'time', '%.3f' % sum(map(lambda e: e.elapsed_time, tests)) 255 | ) 256 | failures = filter(lambda e: e.outcome == _TestInfo.FAILURE, tests) 257 | testsuite.setAttribute('failures', str(len(list(failures)))) 258 | 259 | errors = filter(lambda e: e.outcome == _TestInfo.ERROR, tests) 260 | testsuite.setAttribute('errors', str(len(list(errors)))) 261 | 262 | return testsuite 263 | 264 | _report_testsuite = staticmethod(_report_testsuite) 265 | 266 | def _test_method_name(test_id): 267 | """ 268 | Returns the test method name. 269 | """ 270 | return test_id.split('.')[-1] 271 | 272 | _test_method_name = staticmethod(_test_method_name) 273 | 274 | def _report_testcase(suite_name, test_result, xml_testsuite, xml_document): 275 | """ 276 | Appends a testcase section to the XML document. 277 | """ 278 | testcase = xml_document.createElement('testcase') 279 | xml_testsuite.appendChild(testcase) 280 | 281 | testcase.setAttribute('classname', suite_name) 282 | testcase.setAttribute( 283 | 'name', _XMLTestResult._test_method_name(test_result.test_id) 284 | ) 285 | testcase.setAttribute('time', '%.3f' % test_result.elapsed_time) 286 | 287 | if (test_result.outcome != _TestInfo.SUCCESS): 288 | elem_name = ('failure', 'error', 'skipped')[test_result.outcome - 1] 289 | failure = xml_document.createElement(elem_name) 290 | testcase.appendChild(failure) 291 | if test_result.outcome != _TestInfo.SKIP: 292 | failure.setAttribute('type', test_result.err[0].__name__) 293 | failure.setAttribute('message', unicode(test_result.err[1])) # don't use str(), breaks on py2 294 | error_info = unicode(test_result.get_error_info()) 295 | failureText = xml_document.createCDATASection(error_info) 296 | failure.appendChild(failureText) 297 | else: 298 | failure.setAttribute('type', 'skip') 299 | failure.setAttribute('message', test_result.err) 300 | 301 | 302 | _report_testcase = staticmethod(_report_testcase) 303 | 304 | def _report_output(test_runner, xml_testsuite, xml_document): 305 | """ 306 | Appends the system-out and system-err sections to the XML document. 307 | """ 308 | systemout = xml_document.createElement('system-out') 309 | xml_testsuite.appendChild(systemout) 310 | 311 | systemout_text = xml_document.createCDATASection(sys.stdout.getvalue()) 312 | systemout.appendChild(systemout_text) 313 | 314 | systemerr = xml_document.createElement('system-err') 315 | xml_testsuite.appendChild(systemerr) 316 | 317 | systemerr_text = xml_document.createCDATASection(sys.stderr.getvalue()) 318 | systemerr.appendChild(systemerr_text) 319 | 320 | _report_output = staticmethod(_report_output) 321 | 322 | def generate_reports(self, test_runner): 323 | """ 324 | Generates the XML reports to a given XMLTestRunner object. 325 | """ 326 | from xml.dom.minidom import Document 327 | all_results = self._get_info_by_testcase(test_runner.outsuffix) 328 | 329 | if (isinstance(test_runner.output, str) and not 330 | os.path.exists(test_runner.output)): 331 | os.makedirs(test_runner.output) 332 | 333 | for suite, tests in all_results.items(): 334 | doc = Document() 335 | 336 | # Build the XML file 337 | testsuite = _XMLTestResult._report_testsuite( 338 | suite, test_runner.outsuffix, tests, doc 339 | ) 340 | for test in tests: 341 | _XMLTestResult._report_testcase(suite, test, testsuite, doc) 342 | _XMLTestResult._report_output(test_runner, testsuite, doc) 343 | xml_content = doc.toprettyxml(indent='\t') 344 | 345 | if type(test_runner.output) is str: 346 | report_file = codecs.open( 347 | '%s%sTEST-%s-%s.xml' % ( 348 | test_runner.output, os.sep, suite, 349 | test_runner.outsuffix 350 | ), 'w', 'utf-8' 351 | ) 352 | try: 353 | report_file.write(xml_content) 354 | finally: 355 | report_file.close() 356 | else: 357 | # Assume that test_runner.output is a stream 358 | test_runner.output.write(xml_content) 359 | 360 | 361 | class XMLTestRunner(TextTestRunner): 362 | """ 363 | A test runner class that outputs the results in JUnit like XML files. 364 | """ 365 | def __init__(self, output='.', outsuffix=None, stream=sys.stderr, 366 | descriptions=True, verbosity=1, elapsed_times=True): 367 | TextTestRunner.__init__(self, stream, descriptions, verbosity) 368 | self.verbosity = verbosity 369 | self.output = output 370 | if outsuffix: 371 | self.outsuffix = outsuffix 372 | else: 373 | self.outsuffix = time.strftime("%Y%m%d%H%M%S") 374 | self.elapsed_times = elapsed_times 375 | 376 | def _make_result(self): 377 | """ 378 | Creates a TestResult object which will be used to store 379 | information about the executed tests. 380 | """ 381 | return _XMLTestResult( 382 | self.stream, self.descriptions, self.verbosity, self.elapsed_times 383 | ) 384 | 385 | def _patch_standard_output(self): 386 | """ 387 | Replaces stdout and stderr streams with string-based streams 388 | in order to capture the tests' output. 389 | """ 390 | sys.stdout = _DelegateIO(sys.stdout) 391 | sys.stderr = _DelegateIO(sys.stderr) 392 | 393 | def _restore_standard_output(self): 394 | """ 395 | Restores stdout and stderr streams. 396 | """ 397 | sys.stdout = sys.stdout.delegate 398 | sys.stderr = sys.stderr.delegate 399 | 400 | def run(self, test): 401 | """ 402 | Runs the given test case or test suite. 403 | """ 404 | try: 405 | # Prepare the test execution 406 | self._patch_standard_output() 407 | result = self._make_result() 408 | 409 | # Print a nice header 410 | self.stream.writeln() 411 | self.stream.writeln('Running tests...') 412 | self.stream.writeln(result.separator2) 413 | 414 | # Execute tests 415 | start_time = time.time() 416 | test(result) 417 | stop_time = time.time() 418 | time_taken = stop_time - start_time 419 | 420 | # Print results 421 | result.printErrors() 422 | self.stream.writeln(result.separator2) 423 | run = result.testsRun 424 | self.stream.writeln("Ran %d test%s in %.3fs" % ( 425 | run, run != 1 and "s" or "", time_taken) 426 | ) 427 | self.stream.writeln() 428 | 429 | expectedFails = unexpectedSuccesses = skipped = 0 430 | try: 431 | results = map(len, (result.expectedFailures, 432 | result.unexpectedSuccesses, 433 | result.skipped)) 434 | except AttributeError: 435 | pass 436 | else: 437 | expectedFails, unexpectedSuccesses, skipped = results 438 | 439 | # Error traces 440 | infos = [] 441 | if not result.wasSuccessful(): 442 | self.stream.write("FAILED") 443 | failed, errored = map(len, (result.failures, result.errors)) 444 | if failed: 445 | infos.append("failures={0}".format(failed)) 446 | if errored: 447 | infos.append("errors={0}".format(errored)) 448 | else: 449 | self.stream.write("OK") 450 | 451 | if skipped: 452 | infos.append("skipped={0}".format(skipped)) 453 | if expectedFails: 454 | infos.append("expected failures={0}".format(expectedFails)) 455 | if unexpectedSuccesses: 456 | infos.append("unexpected successes={0}".format(unexpectedSuccesses)) 457 | 458 | if infos: 459 | self.stream.writeln(" ({0})".format(", ".join(infos))) 460 | else: 461 | self.stream.write("\n") 462 | 463 | # Generate reports 464 | self.stream.writeln() 465 | self.stream.writeln('Generating XML reports...') 466 | result.generate_reports(self) 467 | finally: 468 | self._restore_standard_output() 469 | 470 | return result 471 | -------------------------------------------------------------------------------- /Library/CommonLibrary.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | from Resource.Variables.variables import * 4 | from Core.Keywords import database as db 5 | from Core.Keywords import builtIn as bln 6 | from Core.Keywords import selenium as selm 7 | from Core.Keywords import requests 8 | 9 | 10 | class CommonLibrary(object): 11 | pass -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeRobotFramework(CRF) 2 | 不想填表格?文本模式编辑没有直接写代码的感觉?试试code版robotframework吧, 基于unittest编写测试用例, 完全支持robotframework内置关键字和扩展库关键字, 可同时生成HTML格式测试报告和JUnit XML格式报告, 可直接集成到Jenkins中执行并查看结果。 3 | 4 | ## 需要安装的模块 5 | ``` 6 | pip install -U unittest-xml-reporting html-testRunner requests selenium \ 7 | pymysql pymongo robotframework robotframework-seleniumlibrary==3.0.0b3 \ 8 | robotframework-requests robotframework-databaselibrary \ 9 | robotframework-ftplibrary robotframework-appiumlibrary \ 10 | robotframework-archivelibrary robotframework-difflibrary \ 11 | robotframework-mongodbLibrary 12 | ``` 13 | 14 | ## 可选安装的模块 15 | ``` 16 | pip install -U robotframework-selenium2library \ 17 | robotframework-extendedselenium2library robotframework-httplibrary \ 18 | robotframework-faker robotframework-ncclient robotremoteserver 19 | ``` 20 | 21 | ## 目录结构说明 22 | ``` 23 | ├─Core 框架核心库 24 | │ ├─Keywords 函数库 25 | │ └─Runner 运行库 26 | ├─Library 自定义库 27 | ├─Resource 资源 28 | │ ├─TestData 测试数据 29 | │ │ ├─Files 普通文件 30 | │ │ └─SQL SQL文件 31 | │ └─Variables 配置/变量 32 | ├─Results 测试结果 33 | └─TestCase 测试用例 34 | ``` 35 | 36 | ## 用例注释格式说明 37 | 编写用例时增加注释可以对测试用例进行必要的描述, 同时在生成测试报告时会获取注释内容以便在测试报告中显示测试标题、操作步骤和预期结果。 38 | ``` 39 | import unittest 40 | 41 | class TestSuite(unittest.TestCase): 42 | def test_case(self): 43 | """用例编号_用例标题 44 | 操作步骤: 45 | 1、 46 | 2、 47 | 3、 48 | ====== 49 | 预期结果: 50 | 1、 51 | 2、 52 | """ 53 | pass 54 | ``` 55 | >Ps: 用例编号_用例标题必须写在第一行, 换行编写操作步骤和预期结果, 操作步骤与预期结果之间用======分隔开, 至少包含6个等号。 56 | -------------------------------------------------------------------------------- /Resource/Variables/variables.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | # 公共变量 4 | -------------------------------------------------------------------------------- /Results/output.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Results/report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Report 5 | 6 | 7 | 8 | 11 | 12 | 13 |
14 |
15 |
16 |

Test Report

17 |

Start Time: 2017-11-06 20:39:33

18 |

Duration: 0:00:00

19 |

Status: Pass: 1

20 |
21 |
22 |
23 |
24 |

        

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 66 | 67 | 68 |
TestCaseClassNameExpectedTime  (Rerun)Status
U0001_用户名注册 (test_U0001)TestCase.UserSystem.UserGUI.UserGUI 41 | 预期结果: 42 | 注册成功 43 | 0.000000  (0) 46 | 47 | 48 | Pass 49 | 50 | 51 | 52 | 53 |
59 | Total Test Runned: 1 60 | 64 | Pass: 1 65 |
69 |
70 |
71 |
72 | 73 | 119 | 120 | -------------------------------------------------------------------------------- /RunTestSuites.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf8 3 | 4 | 5 | from unittest import TestSuite, TestLoader 6 | from Core.Runner.TestRunner import TestRunner 7 | from Core.Runner.xmlrunner import XMLTestRunner 8 | from Core.Runner.HtmlTestRunner import HTMLTestRunner 9 | # 导入测试用例 10 | from TestCase.UserSystem.UserGUI import UserGUI 11 | 12 | 13 | # 加载所有测试用例 14 | def test_suites(): 15 | test_loader = TestLoader() 16 | test_suite = TestSuite() 17 | # test_suite.addTest(UserGUI('test_U0001')) 18 | test_suite.addTests([ 19 | test_loader.loadTestsFromTestCase(UserGUI), 20 | ]) 21 | return test_suite 22 | 23 | # 执行测试 24 | def main(): 25 | # rerun 失败重试次数 26 | # tb_locals 日志中是否打印变量值 27 | runner = TestRunner(output='Results', verbosity=2, tb_locals=True, rerun=2) 28 | runner.run(test_suites()) 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /TestCase/UserSystem/UserGUI.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import unittest 4 | from Core.Keywords import selenium as selm 5 | from Core.Keywords import builtIn as bln 6 | from Core.Keywords import requests 7 | from Library.CommonLibrary import CommonLibrary 8 | from Resource.Variables.variables import * 9 | 10 | 11 | class UserGUI(unittest.TestCase): 12 | """用户模块自动化用例 GUI版""" 13 | # 导入自定义库 14 | @classmethod 15 | def _include_librarys(self): 16 | self.comlib = CommonLibrary() 17 | 18 | @classmethod 19 | def setUpClass(self): 20 | self._include_librarys() 21 | # self.driver用于用例执行失败后自动截图, 变量为固定格式, 请勿更改, 不需要失败截图可注释 22 | self.driver = selm 23 | selm.set_selenium_speed(0.5) 24 | 25 | def setUp(self): 26 | pass 27 | 28 | def tearDown(self): 29 | # 启用失败自动截图功能后, 请务必注释掉这里的关闭浏览器功能, 失败截图后会自动关闭浏览器 30 | # selm.close_browser() 31 | pass 32 | 33 | # @unittest.skip('已取消') 34 | def test_U0001(self): 35 | """U0001_用户名注册 36 | 操作步骤: 37 | 1、进入注册页面 38 | 2、输入6-16位账户名(非数字开头的英文、下划线和数字组合)、手机号(13、14、15、18、17开头)、登录密码(8-20位字符,至少含数字、大写字母、小写字母、符号中的2种)、验证码(6位)、选择推荐人,输入系统中存在的推荐人用户名或手机号 39 | 3、勾选‘我已阅读并同意《小牛在线服务协议》’ 40 | 4、点击【下一步】 41 | 5、点击【获取验证码】 42 | 6、输入6位验证码,点击【确认】 43 | ====== 44 | 预期结果: 45 | 注册成功 46 | """ 47 | pass 48 | 49 | --------------------------------------------------------------------------------