├── 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 | TestCase |
29 | ClassName |
30 | Expected |
31 | Time (Rerun) |
32 | Status |
33 |
34 |
35 |
36 | {% for eachTestCase, className,expected, status, errorType, errorMessage, times, rerun, screenshot in tests_results %}
37 |
38 | {{eachTestCase}} |
39 | {{className}} |
40 | {{expected}} |
41 | {{times}} ({{rerun}}) |
42 |
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 | |
61 |
62 | {% if "success" != status %}
63 |
64 |
65 | {{errorType}}
66 | ErrorMessage: {{errorMessage}}
67 | |
68 |
69 | {% endif %}
70 | {% endfor %}
71 |
72 |
73 | Total Test Runned: {{total_tests}}
74 | |
75 | |
76 | |
77 |
78 | {{headers.status}}
79 | |
80 |
81 |
82 |
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 | TestCase |
29 | ClassName |
30 | Expected |
31 | Time (Rerun) |
32 | Status |
33 |
34 |
35 |
36 |
37 |
38 | U0001_用户名注册 (test_U0001) |
39 | TestCase.UserSystem.UserGUI.UserGUI |
40 |
41 | 预期结果:
42 | 注册成功
43 | |
44 | 0.000000 (0) |
45 |
46 |
47 |
48 | Pass
49 |
50 |
51 |
52 |
53 | |
54 |
55 |
56 |
57 |
58 |
59 | Total Test Runned: 1
60 | |
61 | |
62 | |
63 |
64 | Pass: 1
65 | |
66 |
67 |
68 |
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 |
--------------------------------------------------------------------------------