前言:
從前面五篇,對(duì)selenium大概也有了個(gè)理解,如何定位元素,如何操作元素,如何切換句柄。這時(shí)候就要做個(gè)小demo實(shí)戰(zhàn)下了!
功能主要有:
1-PO模式,設(shè)計(jì)page、testcase、testsuit
2-打印log文件
3-生成測(cè)試報(bào)告
4-壓縮測(cè)試報(bào)告,發(fā)送到郵箱
一、項(xiàng)目結(jié)構(gòu)
二、pages講解
pages是對(duì)要寫自動(dòng)化的頁(yè)面進(jìn)行分離,抽取出來(lái)定位元素,執(zhí)行方法。以login為例子
打開頁(yè)面--->進(jìn)入登錄頁(yè)面--->選擇賬號(hào)登錄---->輸入框輸入用戶名密碼--->點(diǎn)擊登錄
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # @Time : 2019-08-05 14:44
4 # @Author : zhangxue
5 # @File : LoginPage.py
6 # @Desc : 登錄頁(yè)面
7
8 import time
9 from selenium.webdriver.common.by import By
10 from config.url import *
11 from config.config import *
12 from selenium import webdriver
13 from pages.BasePage import BasePage
14
15 class LoginPage(BasePage):
16 '''用戶登錄頁(yè)面'''
17 # 定位器,通過(guò)元素屬性定位元素對(duì)象
18 login_loc = (By.XPATH, "http://a[@class='header_link console']") #首頁(yè)控制臺(tái)鏈接
19 account_loc = (By.XPATH, "http://div[@class='ant-tabs-nav ant-tabs-nav-animated']//div[2]//span") #賬號(hào)登錄tab選項(xiàng)
20 username_loc = (By.XPATH, "http://input[@id='username_or_email']") #用戶名/郵箱/手機(jī)號(hào)input
21 password_loc = (By.XPATH, "http://input[@id='password']")
22 submit_loc = (By.XPATH, "http://div[@class='ant-tabs-tabpane ant-tabs-tabpane-active']//button[@class='ant-btn login-submit-btn ant-btn-primary ant-btn-lg']//span") #登錄按鈕
23
24 login_url = baseurl_console + "login" # 登錄鏈接
25 console_dashboardUrl = baseurl_console + "dashboard" # 登錄狀態(tài)的url
26
27 """打開官網(wǎng)首頁(yè)"""
28 def openPage(self):
29 self.open(baseurl_guanwang, title_guanwang)
30
31 """進(jìn)入登錄頁(yè)面"""
32 def click_console(self):
33 self.find_element(*self.login_loc).click()
34 assert self.assert_page_title(title_console), u"title不正確! %s" % title_console
35 assert self.assert_page_url(self.login_url), "url地址不對(duì)! %s" % self.login_url
36
37 """選擇賬號(hào)登錄tab"""
38 def click_accountLogin(self):
39 self.find_element(*self.account_loc).click()
40
41 # 輸入用戶名
42 def input_username(self, username):
43 self.find_element(*self.username_loc).clear()
44 self.find_element(*self.username_loc).click()
45 self.find_element(*self.username_loc).send_keys(username)
46
47 # 輸入密碼
48 def input_password(self, password):
49 self.find_element(*self.password_loc).clear()
50 self.find_element(*self.password_loc).click()
51 self.find_element(*self.password_loc).send_keys(password)
52
53 # 點(diǎn)擊登錄
54 def click_submit(self):
55 self.find_element(*self.submit_loc).click()
56
57 #統(tǒng)一登錄入口,方便其他地方調(diào)用
58 def user_login(self, username, password):
59 self.openPage()
60 self.click_console()
61 time.sleep(3)
62
63 nowhandle = self.driver.current_window_handle # 在這里得到當(dāng)前窗口句柄
64 allhandles = self.driver.window_handles # 獲取所有窗口句柄
65 for handle in allhandles: # 在所有窗口中查找彈出窗口
66 if handle != nowhandle:
67 self.driver.switch_to.window(handle) # 這兩步是在彈出窗口中進(jìn)行的操作,證明我們確實(shí)進(jìn)入了
68
69 self.click_accountLogin()
70 time.sleep(1)
71 self.input_username(username)
72 self.input_password(password)
73 time.sleep(1)
74 self.click_submit()
75
76 # 登錄成功后比對(duì)url和title
77 assert self.assert_page_title(title_console), u"title不正確! %s" % title_console
78 assert self.assert_page_url(self.console_dashboardUrl), "url地址不對(duì)! %s" % self.console_dashboardUrl
79
80 time.sleep(2)
81
82 self.driver.get_screenshot_as_file(basedir + '/report/screen/登錄.png')
83
84
85
86 # if __name__ == "__main__":
87 # driver = webdriver.Chrome()
88 # login = LoginPage(driver)
89 # login.openPage()
90 # login.click_console()
91 # login.click_accountLogin()
三、testcase講解
testcase就是對(duì)pages對(duì)應(yīng)的方法組裝,進(jìn)行case設(shè)計(jì),因?yàn)閘ogin頁(yè)面沒(méi)寫太多case,就以一個(gè)為例子
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # @Time : 2019-08-05 15:31
4 # @Author : zhangxue
5 # @File : test_login.py
6 # @Desc : 登錄校驗(yàn)
7
8 import unittest
9 import time
10 from selenium import webdriver
11 from config.url import *
12 from lib.publicMethod.Log import logger
13 from pages.LoginPage import *
14 from config.config import *
15
16 class CaseLogin(unittest.TestCase):
17 """控制臺(tái)登錄"""
18
19 @classmethod
20 def setUpClass(cls):
21 logger.info("##########登錄測(cè)試 開始##############")
22 cls.driver = webdriver.Chrome()
23 cls.username = username
24 cls.password = password
25 cls.loginPage = LoginPage(cls.driver)
26
27 # 用例執(zhí)行體
28 def test_login(self):
29 self.loginPage.user_login(self.username, self.password)
30
31
32 @classmethod
33 def tearDownClass(cls):
34 cls.driver.quit()
35 logger.info("##########登錄測(cè)試 結(jié)束##############")
36
37
38 if __name__ == "__main__":
39 unittest.main()
四、testsuite講解
testsuite是對(duì)testcase進(jìn)行組裝,統(tǒng)一的一個(gè)執(zhí)行入口。testsuite有多種組合方法,可以詳細(xì)的去了解下,代碼的注釋中有幾種的講解
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import unittest
5 import os
6
7 from lib.publicMethod.HTMLTestRunner_PY3 import HTMLTestRunner
8 import time
9 from lib.publicMethod import Send_email
10
11 if __name__ == '__main__':
12 # 生成報(bào)告文件的參數(shù)
13 basedir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14 print(basedir)
15 # discover這個(gè)方法可以遞歸的去識(shí)別路徑下所有符合pattern的文件,將這些文件加入套件
16 suite = unittest.defaultTestLoader.discover(basedir+'/test/testcase/', pattern='*.py')
17
18 # 1、用TestSuite的addTests()方法,將測(cè)試用例組裝成測(cè)試套件,不用TestLoader時(shí),傳入的是case里面的方法名。用TestLoader時(shí)傳入類的類名
19 # addTest傳入單個(gè)的Testcase方法,addTests傳入Testcase方法數(shù)組
20 # suite.addTest(TestMathFunc('test_add'))
21 # tests = [TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_divide')]
22 # suite.addTests(tests)
23
24 # 2、使用addTests+TestLoader傳入測(cè)試用例,但是TestLoader無(wú)法對(duì)case排序
25
26 # 2.1 loadTestsFromName傳入‘模塊名.TestCase名’(文件名.里面的類名)
27 # suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
28 # # 2.2 loadTestsFromNames就是傳入列表即有多個(gè)testcase時(shí),依次傳入文件
29 # suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc']))
30 # 2.3 loadTestsFromTestCase傳入TestCase名,(testcae中文件里面的類名)
31 # suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
32 # suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TwoMathFun))
33 # suite.addTests(unittest.TestLoader().loadTestsFromTestCase(Add))
34
35 # suite.addTest(unittest.TestLoader().loadTestsFromTestCase(UntitledTestCase))
36 # 運(yùn)行suite,這樣寫是將結(jié)果輸出到控制臺(tái)
37 # verbosity參數(shù)可以控制輸出的錯(cuò)誤報(bào)告的詳細(xì)程度,默認(rèn)是1,
38 # 如果設(shè)為0,則不輸出每一用例的執(zhí)行結(jié)果,即沒(méi)有上面的結(jié)果中的第1行;
39 # 如果設(shè)為2,則輸出詳細(xì)的執(zhí)行結(jié)果
40 # runner = unittest.TextTestRunner(verbosity=2)
41 # runner.run(suite)
42
43 #運(yùn)行suite,并將結(jié)果輸出到Html報(bào)告里
44 #生成報(bào)告文件的參數(shù)
45 report_title = 'facepp-UI自動(dòng)化測(cè)試'
46 desc = '餅圖統(tǒng)計(jì)測(cè)試執(zhí)行情況'
47 report_file = basedir+'/report/faceppUI.html'
48 with open(report_file, 'wb') as report:
49 runner = HTMLTestRunner(stream=report, title=report_title, description=desc)
50 runner.run(suite)
51
52
53 # 發(fā)送報(bào)告到郵箱
54 time.sleep(1)
55 Send_email.cr_zip('TestReport.zip',basedir + '/report/')
56 Send_email.send_mail_report("facepp-UI自動(dòng)化測(cè)試!!!")
五、生成html報(bào)告
忽略我的報(bào)錯(cuò),因?yàn)槲椰F(xiàn)在的電腦上沒(méi)有安裝瀏覽器的驅(qū)動(dòng)
大概就是這個(gè)樣子,餅圖會(huì)將通過(guò)的失敗的錯(cuò)誤的。下面的表格會(huì)展示出所有的case,詳情里會(huì)將錯(cuò)誤信息展示出來(lái)
六、發(fā)送到郵箱
執(zhí)行成功后,會(huì)發(fā)送到郵箱一封郵件,發(fā)送人、收件人都是可以設(shè)置的。郵件內(nèi)容為測(cè)試結(jié)果,html報(bào)告打包后的壓縮文件,可以下載后,打開查看html報(bào)告
1 # 發(fā)送報(bào)告到郵箱
2 time.sleep(1)
3 Send_email.cr_zip('TestReport.zip',basedir + '/report/')
4 Send_email.send_mail_report("facepp-UI自動(dòng)化測(cè)試!!!")
七、生成日志
日志格式如上,年月日時(shí)分秒,然后info、error等信息
使用時(shí),引入logger類,然后logger.info("##########登錄測(cè)試 開始##############")即可
八、生成html報(bào)告源碼|發(fā)送到郵箱郵件源碼 |日志源碼
?
生成html報(bào)告


1 # -*- coding: utf-8 -*-
2 """
3 A TestRunner for use with the Python unit testing framework. It
4 generates a HTML report to show the result at a glance.
5
6 The simplest way to use this is to invoke its main method. E.g.
7
8 import unittest
9 import HTMLTestRunner
10
11 ... define your tests ...
12
13 if __name__ == '__main__':
14 HTMLTestRunner.main()
15
16
17 For more customization options, instantiates a HTMLTestRunner object.
18 HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
19
20 # output to a file
21 fp = file('my_report.html', 'wb')
22 runner = HTMLTestRunner.HTMLTestRunner(
23 stream=fp,
24 title='My unit test',
25 description='This demonstrates the report output by HTMLTestRunner.'
26 )
27
28 # Use an external stylesheet.
29 # See the Template_mixin class for more customizable options
30 runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
31
32 # run the test
33 runner.run(my_test_suite)
34
35
36 ------------------------------------------------------------------------
37 Copyright (c) 2004-2007, Wai Yip Tung
38 All rights reserved.
39
40 Redistribution and use in source and binary forms, with or without
41 modification, are permitted provided that the following conditions are
42 met:
43
44 * Redistributions of source code must retain the above copyright notice,
45 this list of conditions and the following disclaimer.
46 * Redistributions in binary form must reproduce the above copyright
47 notice, this list of conditions and the following disclaimer in the
48 documentation and/or other materials provided with the distribution.
49 * Neither the name Wai Yip Tung nor the names of its contributors may be
50 used to endorse or promote products derived from this software without
51 specific prior written permission.
52
53 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
54 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
55 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
56 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
57 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
58 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
59 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
60 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
61 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
62 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
63 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 """
65
66 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
67
68 __author__ = "Wai Yip Tung"
69 __version__ = "0.9.1"
70
71 """
72 Change History
73 Version 0.9.1
74 * 用Echarts添加執(zhí)行情況統(tǒng)計(jì)圖 (灰藍(lán))
75
76 Version 0.9.0
77 * 改成Python 3.x (灰藍(lán))
78
79 Version 0.8.3
80 * 使用 Bootstrap稍加美化 (灰藍(lán))
81 * 改為中文 (灰藍(lán))
82
83 Version 0.8.2
84 * Show output inline instead of popup window (Viorel Lupu).
85
86 Version in 0.8.1
87 * Validated XHTML (Wolfgang Borgert).
88 * Added description of test classes and test cases.
89
90 Version in 0.8.0
91 * Define Template_mixin class for customization.
92 * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
93
94 Version in 0.7.1
95 * Back port to Python 2.3 (Frank Horowitz).
96 * Fix missing scroll bars in detail log (Podi).
97 """
98
99 # TODO: color stderr
100 # TODO: simplify javascript using ,ore than 1 class in the class attribute?
101
102 import datetime
103 import sys
104 import io
105 import time
106 import unittest
107 from xml.sax import saxutils
108
109
110 # ------------------------------------------------------------------------
111 # The redirectors below are used to capture output during testing. Output
112 # sent to sys.stdout and sys.stderr are automatically captured. However
113 # in some cases sys.stdout is already cached before HTMLTestRunner is
114 # invoked (e.g. calling logging.basicConfig). In order to capture those
115 # output, use the redirectors for the cached stream.
116 #
117 # e.g.
118 # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
119 # >>>
120
121 class OutputRedirector(object):
122 """ Wrapper to redirect stdout or stderr """
123 def __init__(self, fp):
124 self.fp = fp
125
126 def write(self, s):
127 self.fp.write(s)
128
129 def writelines(self, lines):
130 self.fp.writelines(lines)
131
132 def flush(self):
133 self.fp.flush()
134
135 stdout_redirector = OutputRedirector(sys.stdout)
136 stderr_redirector = OutputRedirector(sys.stderr)
137
138
139 # ----------------------------------------------------------------------
140 # Template
141
142
143 class Template_mixin(object):
144 """
145 Define a HTML template for report customerization and generation.
146
147 Overall structure of an HTML report
148
149 HTML
150 +------------------------+
151 |<html> |
152 | <head> |
153 | |
154 | STYLESHEET |
155 | +----------------+ |
156 | | | |
157 | +----------------+ |
158 | |
159 | </head> |
160 | |
161 | <body> |
162 | |
163 | HEADING |
164 | +----------------+ |
165 | | | |
166 | +----------------+ |
167 | |
168 | REPORT |
169 | +----------------+ |
170 | | | |
171 | +----------------+ |
172 | |
173 | ENDING |
174 | +----------------+ |
175 | | | |
176 | +----------------+ |
177 | |
178 | </body> |
179 |</html> |
180 +------------------------+
181 """
182
183 STATUS = {
184 0: u'通過(guò)',
185 1: u'失敗',
186 2: u'錯(cuò)誤',
187 }
188
189 DEFAULT_TITLE = 'Unit Test Report'
190 DEFAULT_DESCRIPTION = ''
191
192 # ------------------------------------------------------------------------
193 # HTML Template
194
195 HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
196 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
197 <html xmlns="http://www.w3.org/1999/xhtml">
198 <head>
199 <title>%(title)s</title>
200 <meta name="generator" content="%(generator)s"/>
201 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
202
203 <link rel="stylesheet">
204 <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
205 <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->
206
207 %(stylesheet)s
208
209 </head>
210 <body>
211 <script language="javascript" type="text/javascript"><!--
212 output_list = Array();
213
214 /* level - 0:Summary; 1:Failed; 2:All */
215 function showCase(level) {
216 trs = document.getElementsByTagName("tr");
217 for (var i = 0; i < trs.length; i++) {
218 tr = trs[i];
219 id = tr.id;
220 if (id.substr(0,2) == 'ft') {
221 if (level < 1) {
222 tr.className = 'hiddenRow';
223 }
224 else {
225 tr.className = '';
226 }
227 }
228 if (id.substr(0,2) == 'pt') {
229 if (level > 1) {
230 tr.className = '';
231 }
232 else {
233 tr.className = 'hiddenRow';
234 }
235 }
236 }
237 }
238
239
240 function showClassDetail(cid, count) {
241 var id_list = Array(count);
242 var toHide = 1;
243 for (var i = 0; i < count; i++) {
244 tid0 = 't' + cid.substr(1) + '.' + (i+1);
245 tid = 'f' + tid0;
246 tr = document.getElementById(tid);
247 if (!tr) {
248 tid = 'p' + tid0;
249 tr = document.getElementById(tid);
250 }
251 id_list[i] = tid;
252 if (tr.className) {
253 toHide = 0;
254 }
255 }
256 for (var i = 0; i < count; i++) {
257 tid = id_list[i];
258 if (toHide) {
259 document.getElementById('div_'+tid).style.display = 'none'
260 document.getElementById(tid).className = 'hiddenRow';
261 }
262 else {
263 document.getElementById(tid).className = '';
264 }
265 }
266 }
267
268
269 function showTestDetail(div_id){
270 var details_div = document.getElementById(div_id)
271 var displayState = details_div.style.display
272 // alert(displayState)
273 if (displayState != 'block' ) {
274 displayState = 'block'
275 details_div.style.display = 'block'
276 }
277 else {
278 details_div.style.display = 'none'
279 }
280 }
281
282
283 function html_escape(s) {
284 s = s.replace(/&/g,'&');
285 s = s.replace(/</g,'<');
286 s = s.replace(/>/g,'>');
287 return s;
288 }
289
290 /* obsoleted by detail in <div>
291 function showOutput(id, name) {
292 var w = window.open("", //url
293 name,
294 "resizable,scrollbars,status,width=800,height=450");
295 d = w.document;
296 d.write("<pre>");
297 d.write(html_escape(output_list[id]));
298 d.write("\n");
299 d.write("<a href='javascript:window.close()'>close</a>\n");
300 d.write("</pre>\n");
301 d.close();
302 }
303 */
304 --></script>
305
306 <div id="div_base">
307 %(heading)s
308 %(report)s
309 %(ending)s
310 %(chart_script)s
311 </div>
312 </body>
313 </html>
314 """ # variables: (title, generator, stylesheet, heading, report, ending, chart_script)
315
316 ECHARTS_SCRIPT = """
317 <script type="text/javascript">
318 // 基于準(zhǔn)備好的dom,初始化echarts實(shí)例
319 var myChart = echarts.init(document.getElementById('chart'));
320
321 // 指定圖表的配置項(xiàng)和數(shù)據(jù)
322 var option = {
323 title : {
324 text: '測(cè)試執(zhí)行情況',
325 x:'center'
326 },
327 tooltip : {
328 trigger: 'item',
329 formatter: "{a} <br/> : {c} (n5n3t3z%%)"
330 },
331 color: ['#95b75d', 'grey', '#b64645'],
332 legend: {
333 orient: 'vertical',
334 left: 'left',
335 data: ['通過(guò)','失敗','錯(cuò)誤']
336 },
337 series : [
338 {
339 name: '測(cè)試執(zhí)行情況',
340 type: 'pie',
341 radius : '60%%',
342 center: ['50%%', '60%%'],
343 data:[
344 {value:%(Pass)s, name:'通過(guò)'},
345 {value:%(fail)s, name:'失敗'},
346 {value:%(error)s, name:'錯(cuò)誤'}
347 ],
348 itemStyle: {
349 emphasis: {
350 shadowBlur: 10,
351 shadowOffsetX: 0,
352 shadowColor: 'rgba(0, 0, 0, 0.5)'
353 }
354 }
355 }
356 ]
357 };
358
359 // 使用剛指定的配置項(xiàng)和數(shù)據(jù)顯示圖表。
360 myChart.setOption(option);
361 </script>
362 """ # variables: (Pass, fail, error)
363
364 # ------------------------------------------------------------------------
365 # Stylesheet
366 #
367 # alternatively use a <link> for external style sheet, e.g.
368 # <link rel="stylesheet" href="$url" type="text/css">
369
370 STYLESHEET_TMPL = """
371 <style type="text/css" media="screen">
372 body { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; }
373 table { font-size: 100%; }
374 pre { white-space: pre-wrap;word-wrap: break-word; }
375
376 /* -- heading ---------------------------------------------------------------------- */
377 h1 {
378 font-size: 16pt;
379 color: gray;
380 }
381 .heading {
382 margin-top: 0ex;
383 margin-bottom: 1ex;
384 }
385
386 .heading .attribute {
387 margin-top: 1ex;
388 margin-bottom: 0;
389 }
390
391 .heading .description {
392 margin-top: 2ex;
393 margin-bottom: 3ex;
394 }
395
396 /* -- css div popup ------------------------------------------------------------------------ */
397 a.popup_link {
398 }
399
400 a.popup_link:hover {
401 color: red;
402 }
403
404 .popup_window {
405 display: none;
406 position: relative;
407 left: 0px;
408 top: 0px;
409 /*border: solid #627173 1px; */
410 padding: 10px;
411 /*background-color: #E6E6D6; */
412 font-family: "Lucida Console", "Courier New", Courier, monospace;
413 text-align: left;
414 font-size: 8pt;
415 /* width: 500px;*/
416 }
417
418 }
419 /* -- report ------------------------------------------------------------------------ */
420 #show_detail_line {
421 margin-top: 3ex;
422 margin-bottom: 1ex;
423 }
424 #result_table {
425 width: 99%;
426 }
427 #header_row {
428 font-weight: bold;
429 color: #303641;
430 background-color: #ebebeb;
431 }
432 #total_row { font-weight: bold; }
433 .passClass { background-color: #bdedbc; }
434 .failClass { background-color: #ffefa4; }
435 .errorClass { background-color: #ffc9c9; }
436 .passCase { color: #6c6; }
437 .failCase { color: #FF6600; font-weight: bold; }
438 .errorCase { color: #c00; font-weight: bold; }
439 .hiddenRow { display: none; }
440 .testcase { margin-left: 2em; }
441
442
443 /* -- ending ---------------------------------------------------------------------- */
444 #ending {
445 }
446
447 #div_base {
448 position:absolute;
449 top:0%;
450 left:5%;
451 right:5%;
452 width: auto;
453 height: auto;
454 margin: -15px 0 0 0;
455 }
456 </style>
457 """
458
459 # ------------------------------------------------------------------------
460 # Heading
461 #
462
463 HEADING_TMPL = """
464 <div class='page-header'>
465 <h1>%(title)s</h1>
466 %(parameters)s
467 </div>
468 <div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div>
469 <div id="chart" style="width:50%%;height:400px;float:left;"></div>
470 """ # variables: (title, parameters, description)
471
472 HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
473 """ # variables: (name, value)
474
475 # ------------------------------------------------------------------------
476 # Report
477 #
478
479 REPORT_TMPL = u"""
480 <div class="btn-group btn-group-sm">
481 <button class="btn btn-default" onclick='javascript:showCase(0)'>總結(jié)</button>
482 <button class="btn btn-default" onclick='javascript:showCase(1)'>失敗</button>
483 <button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button>
484 </div>
485 <p></p>
486 <table id='result_table' class="table table-bordered">
487 <colgroup>
488 <col align='left' />
489 <col align='right' />
490 <col align='right' />
491 <col align='right' />
492 <col align='right' />
493 <col align='right' />
494 </colgroup>
495 <tr id='header_row'>
496 <td>測(cè)試套件/測(cè)試用例</td>
497 <td>總數(shù)</td>
498 <td>通過(guò)</td>
499 <td>失敗</td>
500 <td>錯(cuò)誤</td>
501 <td>查看</td>
502 </tr>
503 %(test_list)s
504 <tr id='total_row'>
505 <td>總計(jì)</td>
506 <td>%(count)s</td>
507 <td>%(Pass)s</td>
508 <td>%(fail)s</td>
509 <td>%(error)s</td>
510 <td> </td>
511 </tr>
512 </table>
513 """ # variables: (test_list, count, Pass, fail, error)
514
515 REPORT_CLASS_TMPL = u"""
516 <tr class='%(style)s'>
517 <td>%(desc)s</td>
518 <td>%(count)s</td>
519 <td>%(Pass)s</td>
520 <td>%(fail)s</td>
521 <td>%(error)s</td>
522 <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">詳情</a></td>
523 </tr>
524 """ # variables: (style, desc, count, Pass, fail, error, cid)
525
526 REPORT_TEST_WITH_OUTPUT_TMPL = r"""
527 <tr id='%(tid)s' class='%(Class)s'>
528 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
529 <td colspan='5' align='center'>
530
531 <!--css div popup start-->
532 <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
533 %(status)s</a>
534
535 <div id='div_%(tid)s' class="popup_window">
536 <pre>%(script)s</pre>
537 </div>
538 <!--css div popup end-->
539
540 </td>
541 </tr>
542 """ # variables: (tid, Class, style, desc, status)
543
544 REPORT_TEST_NO_OUTPUT_TMPL = r"""
545 <tr id='%(tid)s' class='%(Class)s'>
546 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
547 <td colspan='5' align='center'>%(status)s</td>
548 </tr>
549 """ # variables: (tid, Class, style, desc, status)
550
551 REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s""" # variables: (id, output)
552
553 # ------------------------------------------------------------------------
554 # ENDING
555 #
556
557 ENDING_TMPL = """<div id='ending'> </div>"""
558
559 # -------------------- The end of the Template class -------------------
560
561
562 TestResult = unittest.TestResult
563
564
565 class _TestResult(TestResult):
566 # note: _TestResult is a pure representation of results.
567 # It lacks the output and reporting ability compares to unittest._TextTestResult.
568
569 def __init__(self, verbosity=1):
570 TestResult.__init__(self)
571 self.stdout0 = None
572 self.stderr0 = None
573 self.success_count = 0
574 self.failure_count = 0
575 self.error_count = 0
576 self.verbosity = verbosity
577
578 # result is a list of result in 4 tuple
579 # (
580 # result code (0: success; 1: fail; 2: error),
581 # TestCase object,
582 # Test output (byte string),
583 # stack trace,
584 # )
585 self.result = []
586 self.subtestlist = []
587
588 def startTest(self, test):
589 TestResult.startTest(self, test)
590 # just one buffer for both stdout and stderr
591 self.outputBuffer = io.StringIO()
592 stdout_redirector.fp = self.outputBuffer
593 stderr_redirector.fp = self.outputBuffer
594 self.stdout0 = sys.stdout
595 self.stderr0 = sys.stderr
596 sys.stdout = stdout_redirector
597 sys.stderr = stderr_redirector
598
599 def complete_output(self):
600 """
601 Disconnect output redirection and return buffer.
602 Safe to call multiple times.
603 """
604 if self.stdout0:
605 sys.stdout = self.stdout0
606 sys.stderr = self.stderr0
607 self.stdout0 = None
608 self.stderr0 = None
609 return self.outputBuffer.getvalue()
610
611 def stopTest(self, test):
612 # Usually one of addSuccess, addError or addFailure would have been called.
613 # But there are some path in unittest that would bypass this.
614 # We must disconnect stdout in stopTest(), which is guaranteed to be called.
615 self.complete_output()
616
617 def addSuccess(self, test):
618 if test not in self.subtestlist:
619 self.success_count += 1
620 TestResult.addSuccess(self, test)
621 output = self.complete_output()
622 self.result.append((0, test, output, ''))
623 if self.verbosity > 1:
624 sys.stderr.write('ok ')
625 sys.stderr.write(str(test))
626 sys.stderr.write('\n')
627 else:
628 sys.stderr.write('.')
629
630 def addError(self, test, err):
631 self.error_count += 1
632 TestResult.addError(self, test, err)
633 _, _exc_str = self.errors[-1]
634 output = self.complete_output()
635 self.result.append((2, test, output, _exc_str))
636 if self.verbosity > 1:
637 sys.stderr.write('E ')
638 sys.stderr.write(str(test))
639 sys.stderr.write('\n')
640 else:
641 sys.stderr.write('E')
642
643 def addFailure(self, test, err):
644 self.failure_count += 1
645 TestResult.addFailure(self, test, err)
646 _, _exc_str = self.failures[-1]
647 output = self.complete_output()
648 self.result.append((1, test, output, _exc_str))
649 if self.verbosity > 1:
650 sys.stderr.write('F ')
651 sys.stderr.write(str(test))
652 sys.stderr.write('\n')
653 else:
654 sys.stderr.write('F')
655
656 def addSubTest(self, test, subtest, err):
657 if err is not None:
658 if getattr(self, 'failfast', False):
659 self.stop()
660 if issubclass(err[0], test.failureException):
661 self.failure_count += 1
662 errors = self.failures
663 errors.append((subtest, self._exc_info_to_string(err, subtest)))
664 output = self.complete_output()
665 self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
666 self._exc_info_to_string(err, subtest)))
667 if self.verbosity > 1:
668 sys.stderr.write('F ')
669 sys.stderr.write(str(subtest))
670 sys.stderr.write('\n')
671 else:
672 sys.stderr.write('F')
673 else:
674 self.error_count += 1
675 errors = self.errors
676 errors.append((subtest, self._exc_info_to_string(err, subtest)))
677 output = self.complete_output()
678 self.result.append(
679 (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
680 if self.verbosity > 1:
681 sys.stderr.write('E ')
682 sys.stderr.write(str(subtest))
683 sys.stderr.write('\n')
684 else:
685 sys.stderr.write('E')
686 self._mirrorOutput = True
687 else:
688 self.subtestlist.append(subtest)
689 self.subtestlist.append(test)
690 self.success_count += 1
691 output = self.complete_output()
692 self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
693 if self.verbosity > 1:
694 sys.stderr.write('ok ')
695 sys.stderr.write(str(subtest))
696 sys.stderr.write('\n')
697 else:
698 sys.stderr.write('.')
699
700
701 class HTMLTestRunner(Template_mixin):
702
703 def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
704 self.stream = stream
705 self.verbosity = verbosity
706 if title is None:
707 self.title = self.DEFAULT_TITLE
708 else:
709 self.title = title
710 if description is None:
711 self.description = self.DEFAULT_DESCRIPTION
712 else:
713 self.description = description
714
715 self.startTime = datetime.datetime.now()
716
717 def run(self, test):
718 "Run the given test case or test suite."
719 result = _TestResult(self.verbosity)
720 test(result)
721 self.stopTime = datetime.datetime.now()
722 self.generateReport(test, result)
723 print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
724 return result
725
726 def sortResult(self, result_list):
727 # unittest does not seems to run in any particular order.
728 # Here at least we want to group them together by class.
729 rmap = {}
730 classes = []
731 for n,t,o,e in result_list:
732 cls = t.__class__
733 if cls not in rmap:
734 rmap[cls] = []
735 classes.append(cls)
736 rmap[cls].append((n,t,o,e))
737 r = [(cls, rmap[cls]) for cls in classes]
738 return r
739
740 def getReportAttributes(self, result):
741 """
742 Return report attributes as a list of (name, value).
743 Override this to add custom attributes.
744 """
745 startTime = str(self.startTime)[:19]
746 duration = str(self.stopTime - self.startTime)
747 status = []
748 if result.success_count: status.append(u'通過(guò) %s' % result.success_count)
749 if result.failure_count: status.append(u'失敗 %s' % result.failure_count)
750 if result.error_count: status.append(u'錯(cuò)誤 %s' % result.error_count )
751 if status:
752 status = ' '.join(status)
753 else:
754 status = 'none'
755 return [
756 (u'開始時(shí)間', startTime),
757 (u'運(yùn)行時(shí)長(zhǎng)', duration),
758 (u'狀態(tài)', status),
759 ]
760
761 def generateReport(self, test, result):
762 report_attrs = self.getReportAttributes(result)
763 generator = 'HTMLTestRunner %s' % __version__
764 stylesheet = self._generate_stylesheet()
765 heading = self._generate_heading(report_attrs)
766 report = self._generate_report(result)
767 ending = self._generate_ending()
768 chart = self._generate_chart(result)
769 output = self.HTML_TMPL % dict(
770 title = saxutils.escape(self.title),
771 generator = generator,
772 stylesheet = stylesheet,
773 heading = heading,
774 report = report,
775 ending = ending,
776 chart_script = chart
777 )
778 self.stream.write(output.encode('utf8'))
779
780 def _generate_stylesheet(self):
781 return self.STYLESHEET_TMPL
782
783 def _generate_heading(self, report_attrs):
784 a_lines = []
785 for name, value in report_attrs:
786 line = self.HEADING_ATTRIBUTE_TMPL % dict(
787 name = saxutils.escape(name),
788 value = saxutils.escape(value),
789 )
790 a_lines.append(line)
791 heading = self.HEADING_TMPL % dict(
792 title = saxutils.escape(self.title),
793 parameters = ''.join(a_lines),
794 description = saxutils.escape(self.description),
795 )
796 return heading
797
798 def _generate_report(self, result):
799 rows = []
800 sortedResult = self.sortResult(result.result)
801 for cid, (cls, cls_results) in enumerate(sortedResult):
802 # subtotal for a class
803 np = nf = ne = 0
804 for n,t,o,e in cls_results:
805 if n == 0: np += 1
806 elif n == 1: nf += 1
807 else: ne += 1
808
809 # format class description
810 if cls.__module__ == "__main__":
811 name = cls.__name__
812 else:
813 name = "%s.%s" % (cls.__module__, cls.__name__)
814 doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
815 desc = doc and '%s: %s' % (name, doc) or name
816
817 row = self.REPORT_CLASS_TMPL % dict(
818 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
819 desc = desc,
820 count = np+nf+ne,
821 Pass = np,
822 fail = nf,
823 error = ne,
824 cid = 'c%s' % (cid+1),
825 )
826 rows.append(row)
827
828 for tid, (n,t,o,e) in enumerate(cls_results):
829 self._generate_report_test(rows, cid, tid, n, t, o, e)
830
831 report = self.REPORT_TMPL % dict(
832 test_list = ''.join(rows),
833 count = str(result.success_count+result.failure_count+result.error_count),
834 Pass = str(result.success_count),
835 fail = str(result.failure_count),
836 error = str(result.error_count),
837 )
838 return report
839
840 def _generate_chart(self, result):
841 chart = self.ECHARTS_SCRIPT % dict(
842 Pass=str(result.success_count),
843 fail=str(result.failure_count),
844 error=str(result.error_count),
845 )
846 return chart
847
848 def _generate_report_test(self, rows, cid, tid, n, t, o, e):
849 # e.g. 'pt1.1', 'ft1.1', etc
850 has_output = bool(o or e)
851 tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
852 name = t.id().split('.')[-1]
853 doc = t.shortDescription() or ""
854 desc = doc and ('%s: %s' % (name, doc)) or name
855 tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
856
857 script = self.REPORT_TEST_OUTPUT_TMPL % dict(
858 id=tid,
859 output=saxutils.escape(o+e),
860 )
861
862 row = tmpl % dict(
863 tid=tid,
864 Class=(n == 0 and 'hiddenRow' or 'none'),
865 style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none')),
866 desc=desc,
867 script=script,
868 status=self.STATUS[n],
869 )
870 rows.append(row)
871 if not has_output:
872 return
873
874 def _generate_ending(self):
875 return self.ENDING_TMPL
876
877
878 ##############################################################################
879 # Facilities for running tests from the command line
880 ##############################################################################
881
882 # Note: Reuse unittest.TestProgram to launch test. In the future we may
883 # build our own launcher to support more specific command line
884 # parameters like test title, CSS, etc.
885 class TestProgram(unittest.TestProgram):
886 """
887 A variation of the unittest.TestProgram. Please refer to the base
888 class for command line parameters.
889 """
890 def runTests(self):
891 # Pick HTMLTestRunner as the default test runner.
892 # base class's testRunner parameter is not useful because it means
893 # we have to instantiate HTMLTestRunner before we know self.verbosity.
894 if self.testRunner is None:
895 self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
896 unittest.TestProgram.runTests(self)
897
898 main = TestProgram
899
900 ##############################################################################
901 # Executing this module from the command line
902 ##############################################################################
903
904 if __name__ == "__main__":
905 main(module=None)
發(fā)送到郵箱郵件文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-611799.html


1 # -*- coding: utf-8 -*-
2 import smtplib
3 import zipfile
4 from email.mime.text import MIMEText #發(fā)送純文本信息
5 from email.mime.multipart import MIMEMultipart #發(fā)送帶附件的信息
6 from email.header import Header #導(dǎo)入配置庫(kù)
7 from config.config import basedir
8 import sys
9 from config import config
10 import os
11
12 # 1、壓縮文件
13 def cr_zip(outputName, inputPath):
14 # 將inputPath路徑下的文件壓縮成名字為outputName的文件,放到outputpath目錄下
15
16 outputpath = inputPath + outputName
17 filelist = []
18 isfp = os.path.basename(inputPath)
19 if isfp:
20 print('%s is not path' % inputPath)
21 sys.exit(0)
22 else:
23 for root, subdirs, files in os.walk(inputPath):
24 for file in files:
25 filelist.append(os.path.join(root, file))
26
27 # 參數(shù)1,壓縮后的文件夾路徑加名字(如果只加name的話,會(huì)壓縮到調(diào)用這個(gè)方法的時(shí)候的文件路徑下);
28 # 參數(shù)2,'r' ----- 打開一個(gè)存在的只讀ZIP文件'w' ----- 清空并打開一個(gè)只寫的zip文件,或創(chuàng)建一個(gè)只寫的ZIP文件'a' ----- 表示打開一個(gè)文件,并添加內(nèi)容
29 # 參數(shù)3,壓縮格式 ,可選的壓縮格式只有2個(gè):ZIP_STORE、ZIP_DEFLATED。ZIP_STORE是默認(rèn)的,表示不壓縮。ZIP_DEFLATED表示壓縮
30 zf = zipfile.ZipFile(outputpath, 'w', zipfile.ZIP_DEFLATED)
31 for f in filelist:
32 zf.write(f)
33 zf.close()
34
35 # 2、發(fā)送郵件
36 def send_mail_report(title):
37 """1、獲取測(cè)試報(bào)告郵件服務(wù)器、發(fā)件人、收件人、發(fā)件人賬號(hào)密碼等信息"""
38 sender = config.sender #發(fā)件人
39 receiver = config.receiver #收件人
40 #第三方SMTP服務(wù)
41 server = config.server #設(shè)置服務(wù)器
42 username = config.emailusername #用戶名
43 password = config.emailpassword #口令
44 port = config.smtp_server_port
45
46 """2、獲取最新測(cè)試報(bào)告"""
47 reportPath=config.basedir+"/report/"
48 newReport = ""
49 for root, subdirs, files in os.walk(reportPath):
50 for file in files:
51 if os.path.splitext(file)[1] == ".html": # 判斷該目錄下的文件擴(kuò)展名是否為html
52 newReport=file
53
54 """2.1調(diào)用cr_zip()方法,將測(cè)試報(bào)告壓縮一下。"""
55 cr_zip('TestReport.zip', basedir + '/report/')
56
57 """3、生成郵件的內(nèi)容"""
58 msg = MIMEMultipart() #MIMEMultipart(),創(chuàng)建一個(gè)帶附件的實(shí)例
59 msg["subject"] = title #"""郵件需要三個(gè)頭部信息: From, To, 和 Subject"""
60 msg["from"] = Header(config.sender,'utf-8')
61 msg["to"] = Header(",".join(config.receiver),'utf-8')
62 with open(os.path.join(reportPath,newReport), 'rb') as f:
63 mailbody = f.read()
64 html = MIMEText(mailbody, _subtype='html', _charset='utf-8')
65 msg.attach(html)
66
67 """4、將測(cè)試報(bào)告壓縮文件添加到郵件附件"""
68
69 att = MIMEText(open(basedir + '/report/' + 'TestReport.zip', 'rb').read(), 'base64', 'utf-8')
70 att["Content-Type"] = 'application/octet-stream' #application/octet-stream : 二進(jìn)制流數(shù)據(jù)(如常見(jiàn)的文件下載)
71 att.add_header("Content-Disposition", "attachment", filename="TestReport.zip") #filename為附件名
72 msg.attach(att)
73
74 """5、發(fā)送郵件"""
75 """5、發(fā)送郵件"""
76 try:
77 s = smtplib.SMTP(server, port) # port=25 為 SMTP 端口號(hào)
78 s.ehlo() # 向Gamil發(fā)送SMTP 'ehlo' 命令
79 s.starttls()
80 """s.set_debuglevel(1)認(rèn)證"""
81 s.login(username, password)
82 """發(fā)送郵件"""
83 s.sendmail(sender, receiver, msg.as_string())
84 s.quit()
85 print("郵件發(fā)送成功")
86 except smtplib.SMTPException as e:
87 print(e)
88 print("Error :無(wú)法發(fā)送郵件")
89
90
91 if __name__ =='__main__':
92 print(basedir+'/report/')
93 send_mail_report("接口測(cè)試報(bào)告")
日志文件文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-611799.html


1 # coding=utf-8
2 import logging
3 import time
4 import os
5 from config.config import basedir
6 # proj_path = os.path.dirname(os.path.dirname(__file__))
7
8
9 # os.path.join(a,b)注:第一個(gè)絕對(duì)路徑之前的參數(shù)將被忽略
10
11 log_path = basedir +"/log/"
12 logname = os.path.join(log_path, '{0}.log'.format(time.strftime('%Y-%m-%d--%H_%M_%S')))
13
14 class Log:
15 def __printconsole(self, level, message):
16 """創(chuàng)建一個(gè)logger"""
17 logger = logging.getLogger()
18 logger.setLevel(logging.DEBUG)
19 """創(chuàng)建一個(gè)handler,用于寫入日志文件"""
20 fh = logging.FileHandler(logname, 'a', encoding='utf-8')
21 fh.setLevel(logging.DEBUG)
22 """再創(chuàng)建一個(gè)handler,用于輸出到控制臺(tái)"""
23 ch = logging.StreamHandler()
24 ch.setLevel(logging.DEBUG)
25 """定義handler的輸出格式"""
26 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
27 fh.setFormatter(formatter)
28 ch.setFormatter(formatter)
29 """給logger添加handler"""
30 logger.addHandler(fh)
31 logger.addHandler(ch)
32 """記錄一條日志"""
33 if level == 'info':
34 logger.info(message)
35 elif level == 'debug':
36 logger.debug(message)
37 elif level == 'warning':
38 logger.warning(message)
39 elif level == 'error':
40 logger.error(message)
41 logger.removeHandler(ch)
42 logger.removeHandler(fh)
43 """關(guān)閉打開的文件"""
44 fh.close()
45
46 def debug(self, message):
47 self.__printconsole('debug', message)
48
49 def info(self, message):
50 self.__printconsole('info', message)
51
52 def warning(self, message):
53 self.__printconsole('warning', message)
54
55 def error(self, message):
56 self.__printconsole('error', message)
57
58 logger = Log()
到了這里,關(guān)于python+selenium-【六】-完整的項(xiàng)目結(jié)構(gòu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!