什麼是selenium?javascript
一個用於Web應用程序測試的工具直接運行在瀏覽器中,就像真正的用戶在操做同樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。測試與瀏覽器的兼容性,測試你的應用程序看是否可以很好得工做在不一樣瀏覽器和操做系統之上。測試系統功能,建立衰退測試檢驗軟件功能和用戶需求。php
slenium前世css
早期是直接使用 javascrip 注入技術與瀏覽器打交道,slenium RC啓動一個server 將web元素api轉換爲javascript。在Selenium內核啓動瀏覽器以後注入這段Javascript,由此才實現了Selenium的目的:自動化Web操做。這種Javascript注入技術的缺點是速度不理想,並且穩定性大大依賴於Selenium內核對API翻譯成的Javascript質量高低。html
slenium如今前端
Selenium2.x 提出了WebDriver的概念以後與瀏覽器交互利用原生的API,直接操做瀏覽器頁面裏的元素。不一樣的瀏覽器廠商對Web元素的操做和呈現一些差別直接致使了Selenium WebDriver要分瀏覽器廠商不一樣,而提供不一樣的實現。Selenium3.0發佈後,最大更新點就是幹掉了對selenium rc的支持,後面就一直是webdriver協議,java
WebDriver工做流程python
一、經過WebDriver建立一個瀏覽器服務,remote server
二、腳本啓動時會在新的線程中啓動一個瀏覽器,並綁定特定的端口,沒個瀏覽器有不一樣的端口段。
三、client 建立1個session,在該session中經過http請求向remote server發送restful的請求,remote server解析請求,完成相應操做並返回response。
四、分析response,繼續執行腳本仍是結束執行
command.py:Command類中定義了WebDriver的一些經常使用的常量。git
remote\webdrvier.py:全部瀏覽器webdrvier的基類,其中包含了全部webdriver的api接口github
remote\remote_connection.py:包含啓動Remote WebDrvier server,執行client請求,self._commands是selenium的核心請求參數,根據對應的Command常量,發送不一樣的http請求。web
selenium 中的等待方式:time(固定等待),implicitly_wait(隱式等待),WebDriverWait(顯示等待)
webdriver client的原理
當測試腳本啓動Chrome的時候,selenium-webdriver 會首先在新線程中啓動Chrome瀏覽器。啓動後selenium-webdriver會將Chrome綁定到特定的端口,綁定完成後該chrome實例便做爲webdriver的remote server存在;客戶端(也就是測試腳本)建立1個會話,在該session中經過http請求向remote server發送請求,remote server解析請求,完成相應操做並返回response;客戶端接受response,並分析其返回值以決定是轉到第3步仍是結束腳本;
webdriver是按照server – client的經典設計模式設計的。
server端就是remote server,能夠是任意的瀏覽器。當咱們的腳本啓動瀏覽器後,該瀏覽器就是remote server,它的職責就是等待client(腳本)發送請求並作出相應;
client端簡單說來就是咱們的測試代碼,咱們測試代碼中的一些行爲,好比打開瀏覽器,轉跳到特定的url等操做是以http請求的方式發送給被測試瀏覽器,也就是remote server;remote server接受請求,並執行相應操做,並在response中返回執行狀態、返回值等信息;
selenium的搭建
1.在python中安裝好selenium包 : pip install selenium
2.在配置好瀏覽器的驅動程序根據http://www.imdsx.cn/index.php/2017/08/02/drvier/ 驅動對照表下載Chrome對驅動,並添加在PATH環境變量中,(或者直接把驅動放在pytho的安裝目錄下)
Firefox驅動下載地址爲:https://github.com/mozilla/geckodriver/releases/ ,
IE瀏覽器驅動下載地址爲:http://selenium-release.storage.googleapis.com/index.html
編寫如下代碼能打開對應的瀏覽器就配置成功:
from selenium import webdriver driver = webdriver.Chrome() driver.get('http://www.imdsx.cn')
from selenium import webdriver driver = webdriver.Firefox() driver.get('http://www.imdsx.cn')
from selenium import webdriver driver = webdriver.Ie() driver.get('http://www.imdsx.cn')
selenium定位
import selenium #引用selenium包 from selenium import webdriver#引用包的服務 driver = webdriver.Chrome()#建立瀏覽器 當作咱們的服務端 driver.get('xxxxxxxxxxxxxxx/')#打開對應測試網站,這裏的url 必須帶有http # 8種單數定位方式 # id進行定位 # driver.find_element_by_id('i1').send_keys('123123')#send_keys向文本框輸入數字 # class 定位方式 # driver.find_element_by_class_name('classname').send_keys('123123') # name定位方式 # driver.find_element_by_name('name').send_keys('123123') # 文案定位 # driver.find_element_by_link_text('新建標籤頁面').click() #.click()表示點擊操做 # 文案包含定位方式 js = 'window.scrollTo(0,0);' # 將滾動條調製最上方 driver.execute_script(js)# 執行寫好的js # import time # # time.sleep(2)#界面若是反應比較慢能夠加載等等時間 # driver.find_element_by_partial_link_text('新建標籤').click() # 標籤名定位 最不經常使用的 # driver.find_element_by_tag_name('input').send_keys('1111') # xpath 定位 # driver.find_element_by_xpath('//*[@id="i1"]').send_keys('2222') # css id 定位 # driver.find_element_by_css_selector('#i1').send_keys('2222') # # css name 定位 # driver.find_element_by_css_selector('name').send_keys()
xpath定位
xpath(xpath 定位儘可能少用層級定位,若是開發更改了頁面的層級,全部定位都掛了,儘可能屬性定位爲主,層級爲輔助) //* 取當前頁面的所有元素 //*[@id='i1'] id 進行定位 @表明引用屬性 //*[@placeholder="請經過ID定位元素"] //input[@placeholder="請經過ID定位元素"] 經過 標籤名進一步縮小範圍 //select[4] 若是存在不惟一的狀況 能夠經過角標進行取值 xpath從1開始取 //select[@size="4" and @multiple="multiple"] 邏輯定位
當不肯定時能夠只用copy直接複製xpath地址(這個地址至關於層級定位最好少用)
css selector定位
css selector 1.支持ID,class 定位 (# 表明id) ( . 表示class) 不是說不能用class 若是class屬性在頁面中惟一,那麼是能夠等同於id來使用 2.屬性定位 [placeholder = "請經過ID定位元素"] 屬性定位 3.標籤組合定位 input[placeholder = "請經過ID定位元素"] 縮小範圍 先定位input 在定位屬性 4.多屬性組合定位 Css Selector 的多屬性組合選擇過濾 沒有and 只須要多個[] 鏈接 就能夠 select[size = "4"][multiple = "multiple"] 多屬性確立惟一 5.層級關係定位 經過 > 來區分層級的界定 select>option[value='3'] 6.模糊匹配 ^= 匹配元素屬性以什麼開頭 input[value ^= "登"] $= 匹配屬性以什麼結尾 input[value$="錄"] *= 匹配屬性包含什麼值 input [value *= "錄"]
作UI自動化前須要瞭解什麼
1.何時作UI自動化:當項目已經穩定不在大面積修改的狀況下,就能夠接自動化了。
2.作UI自動化須要瞭解什麼知識:前端HTML,CSS,定位方式
簡單的UI自動化框架
1.bin 目錄下放程序入口 2.lib 目錄下放一些配置文件 3.log 目錄下放日誌 (HTMLTestRunne :生產網頁的報告頁面, logger 生成日誌 path 配置地址 pyse 定位頁元素使用的框架 tool 生成錯誤圖片) 4.pages 目錄下放頁面元素、 5.report 目錄下放文件運行報告 (picture)下放錯誤截圖 6.test_case 目錄下放用例 框架能實現: 1、基於顯示等待 解決不穩定的狀況 (time:固定等待 , implicitly_wait: 隱式等待,webDriveWait:顯示等待) 2.解決維護麻煩 PO(page object)思想(以一個頁面當作一個類,每個頁面的功能點當作一個函數) 3.自動化測試用例和頁面數據源進行分離。 怎麼判斷業務邏輯的成功 舉例說明:假設是登陸的業務邏輯,判斷登陸的特有元素消失 ,就表明登陸成功了。 只有登陸成功了纔會出現的元素 判斷一下他是否出現
from lib.logger import logger from lib.path import WEBCASEPATH,REPORTPATH from lib.HTMLTestRunner import HTMLTestRunner import unittest from lib.tool import Tool class Main(object): def run(self): Tool().clear_picture() suite = unittest.TestSuite() cases = unittest.defaultTestLoader.discover(WEBCASEPATH) print(cases) for case in cases: print(case) suite.addTest(case) f = open(REPORTPATH,'wb') runner = HTMLTestRunner(f,verbosity=1,title=u'測試報告', description=u'用例執行狀況:') runner.run(suite) f.flush() f.close() if __name__ == '__main__': Main().run()
# -*- coding: utf-8 -*- """ A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance. The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == '__main__': HTMLTestRunner.main() For more customization options, instantiates a HTMLTestRunner object. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. # output to a file fp = file('my_report.html', 'wb') runner = HTMLTestRunner.HTMLTestRunner( stream=fp, title='My unit test', description='This demonstrates the report output by HTMLTestRunner.' ) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">' # run the test runner.run(my_test_suite) ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # URL: http://tungwaiyip.info/software/HTMLTestRunner.html __author__ = "Wai Yip Tung" __version__ = "0.8.3" """ Change History Version 0.8.3 * Use Bootstrap (Zhang Xin) * Change to Chinese (Zhang Xin) Version 0.8.2 * Show output inline instead of popup window (Viorel Lupu). Version in 0.8.1 * Validated XHTML (Wolfgang Borgert). * Added description of test classes and test cases. Version in 0.8.0 * Define Template_mixin class for customization. * Workaround a IE 6 bug that it does not treat <script> block as CDATA. Version in 0.7.1 * Back port to Python 2.3 (Frank Horowitz). * Fix missing scroll bars in detail log (Podi). """ # TODO: color stderr # TODO: simplify javascript using ,ore than 1 class in the class attribute? import datetime import io import sys import time import unittest from xml.sax import saxutils # ------------------------------------------------------------------------ # The redirectors below are used to capture output during testing. Output # sent to sys.stdout and sys.stderr are automatically captured. However # in some cases sys.stdout is already cached before HTMLTestRunner is # invoked (e.g. calling logging.basicConfig). In order to capture those # output, use the redirectors for the cached stream. # # e.g. # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector) # >>> class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) def writelines(self, lines): self.fp.writelines(lines) def flush(self): self.fp.flush() stdout_redirector = OutputRedirector(sys.stdout) stderr_redirector = OutputRedirector(sys.stderr) # ---------------------------------------------------------------------- # Template class Template_mixin(object): """ Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ |<html> | | <head> | | | | STYLESHEET | | +----------------+ | | | | | | +----------------+ | | | | </head> | | | | <body> | | | | HEADING | | +----------------+ | | | | | | +----------------+ | | | | REPORT | | +----------------+ | | | | | | +----------------+ | | | | ENDING | | +----------------+ | | | | | | +----------------+ | | | | </body> | |</html> | +------------------------+ """ STATUS = { 0: u'經過', 1: u'失敗', 2: u'錯誤', } DEFAULT_TITLE = 'Unit Test Report' DEFAULT_DESCRIPTION = '' # ------------------------------------------------------------------------ # HTML Template HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>%(title)s</title> <meta name="generator" content="%(generator)s"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> %(stylesheet)s <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <script language="javascript" type="text/javascript"><!-- output_list = Array(); /* level - 0:Summary; 1:Failed; 2:All */ function showCase(level) { trs = document.getElementsByTagName("tr"); for (var i = 0; i < trs.length; i++) { tr = trs[i]; id = tr.id; if (id.substr(0,2) == 'ft') { if (level < 1) { tr.className = 'hiddenRow'; } else { tr.className = ''; } } if (id.substr(0,2) == 'pt') { if (level > 1) { tr.className = ''; } else { tr.className = 'hiddenRow'; } } } } function showClassDetail(cid, count) { var id_list = Array(count); var toHide = 1; for (var i = 0; i < count; i++) { tid0 = 't' + cid.substr(1) + '.' + (i+1); tid = 'f' + tid0; tr = document.getElementById(tid); if (!tr) { tid = 'p' + tid0; tr = document.getElementById(tid); } id_list[i] = tid; if (tr.className) { toHide = 0; } } for (var i = 0; i < count; i++) { tid = id_list[i]; if (toHide) { if(document.getElementById('div_'+tid) == null){ document.getElementById(tid).className = 'hiddenRow'; }else{ document.getElementById('div_'+tid).style.display = 'none' document.getElementById(tid).className = 'hiddenRow'; } } else { document.getElementById(tid).className = ''; } } } function showTestDetail(div_id){ var details_div = document.getElementById(div_id) var displayState = details_div.style.display // alert(displayState) if (displayState != 'block' ) { displayState = 'block' details_div.style.display = 'block' } else { details_div.style.display = 'none' } } function html_escape(s) { s = s.replace(/&/g,'&'); s = s.replace(/</g,'<'); s = s.replace(/>/g,'>'); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open("", //url name, "resizable,scrollbars,status,width=800,height=450"); d = w.document; d.write("<pre>"); d.write(html_escape(output_list[id])); d.write("\n"); d.write("<a href='javascript:window.close()'>close</a>\n"); d.write("</pre>\n"); d.close(); } */ --></script> <div id="div_base"> %(heading)s %(report)s %(ending)s </div> </body> </html> """ # variables: (title, generator, stylesheet, heading, report, ending) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a <link> for external style sheet, e.g. # <link rel="stylesheet" href="$url" type="text/css"> STYLESHEET_TMPL = """ <style type="text/css" media="screen"> body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; } table { font-size: 100%; } pre { white-space: pre-wrap;word-wrap: break-word; } /* -- heading ---------------------------------------------------------------------- */ h1 { font-size: 16pt; color: gray; } .heading { margin-top: 0ex; margin-bottom: 1ex; } .heading .attribute { margin-top: 1ex; margin-bottom: 0; } .heading .description { margin-top: 2ex; margin-bottom: 3ex; } /* -- css div popup ------------------------------------------------------------------------ */ a.popup_link { } a.popup_link:hover { color: red; } .popup_window { display: none; position: relative; left: 0px; top: 0px; /*border: solid #627173 1px; */ padding: 10px; background-color: #E6E6D6; font-family: "Lucida Console", "Courier New", Courier, monospace; text-align: left; font-size: 8pt; /* width: 500px;*/ } } /* -- report ------------------------------------------------------------------------ */ #show_detail_line { margin-top: 3ex; margin-bottom: 1ex; } #result_table { width: 99%; } #header_row { font-weight: bold; color: white; background-color: #777; } #total_row { font-weight: bold; } .passClass { background-color: #74A474; } .failClass { background-color: #FDD283; } .errorClass { background-color: #FF6600; } .passCase { color: #6c6; } .failCase { color: #FF6600; font-weight: bold; } .errorCase { color: #c00; font-weight: bold; } .hiddenRow { display: none; } .testcase { margin-left: 2em; } /* -- ending ---------------------------------------------------------------------- */ #ending { } #div_base { position:absolute; top:0%; left:5%; right:5%; width: auto; height: auto; margin: -15px 0 0 0; } </style> """ # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = """<div class='page-header' style='margin-top: 15px;'> <h1>%(title)s</h1> %(parameters)s </div> <p class='description'>%(description)s</p> """ # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p> """ # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = u""" <div class="btn-group btn-group-sm"> <button class="btn btn-default" onclick='javascript:showCase(0)'>總結</button> <button class="btn btn-default" onclick='javascript:showCase(1)'>失敗</button> <button class="btn btn-default" onclick='javascript:showCase(2)'>所有</button> </div> <p></p> <table id='result_table' class="table table-bordered"> <colgroup> <col align='left' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> </colgroup> <tr id='header_row'> <td>測試套件/測試用例</td> <td>總數</td> <td>經過</td> <td>失敗</td> <td>錯誤</td> <td>查看</td> </tr> %(test_list)s <tr id='total_row'> <td>總計</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td> </td> </tr> </table> """ # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = u""" <tr class='%(style)s'> <td>%(desc)s</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">詳情</a></td> </tr> """ # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'> <!--css div popup start--> <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" > %(status)s</a> <div id='div_%(tid)s' class="popup_window"> <div style='text-align: right; color:red;cursor:pointer;height: 200px;position: relative;'> <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " > [x]</a> </div> %(script)s <a href="#" onclick="window.open('picture/%(png)s','','WIDTH=1034,height=619 TOP=0 left=200')"><IMG SRC=picture/%(png)s style="width: 390px;height: 200px;position: absolute;top: 10px;left: 135px"></a> </div> <!--css div popup end--> </td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'>%(status)s</td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s """ # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = """<div id='ending'> </div>""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.stdout0 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('.') def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('E') def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('F') from lib.logger import logger class HTMLTestRunner(Template_mixin): """ """ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): "Run the given test case or test suite." result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) # print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n, t, o, e in result_list: cls = t.__class__ if not cls in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n, t, o, e,)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). Override this to add custom attributes. """ startTime = str(self.startTime)[:19] if hasattr(self, 'self.stopTime'): duration = str(self.stopTime - self.startTime) else: self.stopTime = datetime.datetime.now() duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append(u'經過 %s' % result.success_count) if result.failure_count: status.append(u'失敗 %s' % result.failure_count) if result.error_count: status.append(u'錯誤 %s' % result.error_count) if status: status = ' '.join(status) else: status = 'none' return [ (u'開始時間', startTime), (u'運行時長', duration), (u'狀態', status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = 'HTMLTestRunner %s' % __version__ stylesheet = self._generate_stylesheet() heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() output = self.HTML_TMPL % dict( title=saxutils.escape(self.title), generator=generator, stylesheet=stylesheet, heading=heading, report=report, ending=ending, ) self.stream.write(output.encode('utf8')) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( name=saxutils.escape(name), value=saxutils.escape(value), ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title=saxutils.escape(self.title), parameters=''.join(a_lines), description=saxutils.escape(self.description), ) return heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n, t, o, e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 else: ne += 1 # format class description if cls.__module__ == "__main__": name = cls.__name__ else: name = "%s.%s" % (cls.__module__, cls.__name__) doc = cls.__doc__ and cls.__doc__.split("\n")[0] or "" desc = doc and '%s: %s' % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict( style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', desc=desc, count=np + nf + ne, Pass=np, fail=nf, error=ne, cid='c%s' % (cid + 1), ) rows.append(row) from lib.tool import Tool pnglist = Tool().error_picture() logger.debug(pnglist) if len(pnglist) > 0: Rewrite_results = [] for cls_p in cls_results: name = cls_p[1].id().split('.')[-1] for png in pnglist: png_name = png[0].split('.')[0] if png_name == name: tmp = cls_p + png Rewrite_results.append(tmp) break else: tmp = cls_p + ('',) Rewrite_results.append(tmp) for tid, (n, t, o, e, png) in enumerate(Rewrite_results): self._generate_report_test(rows, cid, tid, n, t, o, e, png) else: for tid, (n, t, o, e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list=''.join(rows), count=str(result.success_count + result.failure_count + result.error_count), Pass=str(result.success_count), fail=str(result.failure_count), error=str(result.error_count), ) return report def _generate_report_test(self, rows, cid, tid, n, t, o, e, png=''): # e.g. 'pt1.1', 'ft1.1', etc has_output = bool(o or e) tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL # o and e should be byte string because they are collected from stdout and stderr? if isinstance(o, str): # TODO: some problem with 'string_escape': it escape \n and mess up formating # uo = unicode(o.encode('string_escape')) uo = o else: uo = o if isinstance(e, str): # TODO: some problem with 'string_escape': it escape \n and mess up formating # ue = unicode(e.encode('string_escape')) ue = e else: ue = e if png: script = '' else: script = self.REPORT_TEST_OUTPUT_TMPL % dict( id=tid, output=saxutils.escape(uo + ue), ) row = tmpl % dict( tid=tid, Class=(n == 0 and 'hiddenRow' or 'none'), style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), desc=desc, script=script, png=png, status=self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL ############################################################################## # Facilities for running tests from the command line ############################################################################## # Note: Reuse unittest.TestProgram to launch test. In the future we may # build our own launcher to support more specific command line # parameters like test title, CSS, etc. class TestProgram(unittest.TestProgram): """ A variation of the unittest.TestProgram. Please refer to the base class for command line parameters. """ def runTests(self): # Pick HTMLTestRunner as the default test runner. # base class's testRunner parameter is not useful because it means # we have to instantiate HTMLTestRunner before we know self.verbosity. if self.testRunner is None: self.testRunner = HTMLTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None)
from logging import handlers import logging from lib.path import WEBLOGPATH class Logger(object): __instance = None def __new__(cls, *args, **kwargs): if not Logger.__instance: Logger.__instance = object.__new__(cls, *args) return Logger.__instance def __init__(self): # 格式化log的模板 self.formater = logging.Formatter( '[%(asctime)s] [%(levelname)s] [%(filename)s:%(funcName)s:%(lineno)d] %(message)s') # 聲明一個log對象 self.logger = logging.getLogger('log') # 設置全局log級別 self.logger.setLevel(logging.DEBUG) # 文件log self.filelogger = handlers.RotatingFileHandler(WEBLOGPATH, maxBytes=5242880, backupCount=3 ) # 屏幕log self.console = logging.StreamHandler() # 對屏幕設置級別 self.console.setLevel(logging.DEBUG) self.filelogger.setFormatter(self.formater) self.console.setFormatter(self.formater) self.logger.addHandler(self.filelogger) self.logger.addHandler(self.console) def log(self): return self.logger logger = Logger().log()
import os BASEPATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 報告地址 REPORTPATH = BASEPATH + os.path.sep + 'report' + os.path.sep + 'report.html' LOGPATH = BASEPATH + os.path.sep + 'log' + os.path.sep WEBLOGPATH = LOGPATH + 'server.log' # webcase path WEBCASEPATH = BASEPATH + os.path.sep + 'test_case' WEBPICTUREPATH = BASEPATH + os.path.sep + 'report' + os.path.sep + 'picture' + os.path.sep
# coding=utf-8 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select class Pyse(object): ''' Pyse framework for the main class, the original selenium provided by the method of the two packaging, making it easier to use. ''' def __init__(self, browser='ff'): ''' Run class initialization method, the default is proper to drive the Firefox browser. Of course, you can also pass parameter for other browser, Chrome browser for the "Chrome", the Internet Explorer browser for "internet explorer" or "ie". ''' if browser == "firefox" or browser == "ff": driver = webdriver.Firefox() elif browser == "chrome": option = webdriver.ChromeOptions() option.add_argument("--start-maximized") driver = webdriver.Chrome(chrome_options=option,) elif browser == "internet explorer" or browser == "ie": driver = webdriver.Ie() elif browser == "opera": driver = webdriver.Opera() elif browser == "phantomjs": driver = webdriver.PhantomJS() elif browser == 'edge': driver = webdriver.Edge() try: self.driver = driver except Exception: raise NameError( "Not found %s browser,You can enter 'ie', 'ff', 'opera', 'phantomjs', 'edge' or 'chrome'." % browser) def element_wait(self, css, secs=5): ''' Waiting for an element to display. Usage: driver.element_wait("css=>#el",10) ''' if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") by = css.split("=>")[0] value = css.split("=>")[1] if by == "id": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.ID, value))) elif by == "name": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.NAME, value))) elif by == "class": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.CLASS_NAME, value))) elif by == "link_text": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.LINK_TEXT, value))) elif by == "xpath": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.XPATH, value))) elif by == "css": WebDriverWait(self.driver, secs, 1).until(EC.presence_of_element_located((By.CSS_SELECTOR, value))) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") def get_element(self, css): ''' Judge element positioning way, and returns the element. ''' if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") by = css.split("=>")[0] value = css.split("=>")[1] if by == "id": element = self.driver.find_element_by_id(value) elif by == "name": element = self.driver.find_element_by_name(value) elif by == "class": element = self.driver.find_element_by_class_name(value) elif by == "link_text": element = self.driver.find_element_by_link_text(value) elif by == "xpath": element = self.driver.find_element_by_xpath(value) elif by == "css": element = self.driver.find_element_by_css_selector(value) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") return element def open(self, url): ''' open url. Usage: driver.open("https://www.baidu.com") ''' self.driver.get(url) def max_window(self): ''' Set browser window maximized. Usage: driver.max_window() ''' self.driver.maximize_window() def set_window(self, wide, high): ''' Set browser window wide and high. Usage: driver.set_window(wide,high) ''' self.driver.set_window_size(wide, high) def type(self, css, text): ''' Operation input box. Usage: driver.type("css=>#el","selenium") ''' self.element_wait(css) el = self.get_element(css) el.send_keys(text) def clear(self, css): ''' Clear the contents of the input box. Usage: driver.clear("css=>#el") ''' self.element_wait(css) el = self.get_element(css) el.clear() def click(self, css): ''' It can click any text / image can be clicked Connection, check box, radio buttons, and even drop-down box etc.. Usage: driver.click("css=>#el") ''' self.element_wait(css) el = self.get_element(css) el.click() def right_click(self, css): ''' Right click element. Usage: driver.right_click("css=>#el") ''' self.element_wait(css) el = self.get_element(css) ActionChains(self.driver).context_click(el).perform() def move_to_element(self, css): ''' Mouse over the element. Usage: driver.move_to_element("css=>#el") ''' self.element_wait(css) el = self.get_element(css) ActionChains(self.driver).move_to_element(el).perform() def double_click(self, css): ''' Double click element. Usage: driver.double_click("css=>#el") ''' self.element_wait(css) el = self.get_element(css) ActionChains(self.driver).double_click(el).perform() def drag_and_drop(self, el_css, ta_css): ''' Drags an element a certain distance and then drops it. Usage: driver.drag_and_drop("css=>#el","css=>#ta") ''' self.element_wait(el_css) element = self.get_element(el_css) self.element_wait(ta_css) target = self.get_element(ta_css) ActionChains(driver).drag_and_drop(element, target).perform() def click_text(self, text): ''' Click the element by the link text Usage: driver.click_text("新聞") ''' self.driver.find_element_by_partial_link_text(text).click() def close(self): ''' Simulates the user clicking the "close" button in the titlebar of a popup window or tab. Usage: driver.close() ''' self.driver.close() def quit(self): ''' Quit the driver and close all the windows. Usage: driver.quit() ''' self.driver.quit() def submit(self, css): ''' Submit the specified form. Usage: driver.submit("css=>#el") ''' self.element_wait(css) el = self.get_element(css) el.submit() def F5(self): ''' Refresh the current page. Usage: driver.F5() ''' self.driver.refresh() def js(self, script): ''' Execute JavaScript scripts. Usage: driver.js("window.scrollTo(200,1000);") ''' self.driver.execute_script(script) def get_attribute(self, css, attribute): ''' Gets the value of an element attribute. Usage: driver.get_attribute("css=>#el","type") ''' el = self.get_element(css) return el.get_attribute(attribute) def get_text(self, css): ''' Get element text information. Usage: driver.get_text("css=>#el") ''' self.element_wait(css) el = self.get_element(css) return el.text def get_display(self, css): ''' Gets the element to display,The return result is true or false. Usage: driver.get_display("css=>#el") ''' self.element_wait(css) el = self.get_element(css) return el.is_displayed() def get_title(self): ''' Get window title. Usage: driver.get_title() ''' return self.driver.title def get_url(self): ''' Get the URL address of the current page. Usage: driver.get_url() ''' return self.driver.current_url def get_windows_img(self, file_path): ''' Get the current window screenshot. Usage: driver.get_windows_img() ''' self.driver.get_screenshot_as_file(file_path) def wait(self, secs): ''' Implicitly wait.All elements on the page. Usage: driver.wait(10) ''' self.driver.implicitly_wait(secs) def accept_alert(self): ''' Accept warning box. Usage: driver.accept_alert() ''' self.driver.switch_to.alert.accept() def dismiss_alert(self): ''' Dismisses the alert available. Usage: driver.dismiss_alert() ''' self.driver.switch_to.alert.dismiss() def switch_to_frame(self, css): ''' Switch to the specified frame. Usage: driver.switch_to_frame("css=>#el") ''' self.element_wait(css) iframe_el = self.get_element(css) self.driver.switch_to.frame(iframe_el) def switch_to_frame_out(self): ''' Returns the current form machine form at the next higher level. Corresponding relationship with switch_to_frame () method. Usage: driver.switch_to_frame_out() ''' self.driver.switch_to.default_content() def open_new_window(self, css): ''' Open the new window and switch the handle to the newly opened window. Usage: driver.open_new_window() ''' original_windows = self.driver.current_window_handle all_handles = self.driver.window_handles for handle in all_handles: if handle != original_windows: self.driver.switch_to.window(handle) # def _save_png(self, name): # self.get_windows_img(name) def wait_and_save_exception(self, css, name): try: self.element_wait(css, secs=5) return True except Exception as e: from lib.path import WEBPICTUREPATH self.get_windows_img(WEBPICTUREPATH + name + '.jpg') return False def wait_and_exception(self, css): try: self.element_wait(css, secs=10) return True except Exception as e: return False def select_by_value(self, css, value): self.element_wait(css) el = self.get_element(css) Select(el).select_by_value(value) if __name__ == '__main__': driver = Pyse("chrome")
import os from lib.path import WEBPICTUREPATH class Tool(object): def __init__(self): self.filelist = os.listdir(WEBPICTUREPATH) def error_picture(self): picture = [] for item in self.filelist: if item.endswith('.jpg'): picture.append((item,)) return picture def clear_picture(self): list(map(os.remove, map(lambda file: WEBPICTUREPATH + file, self.filelist)))
''' 主要放頁面的元素 ''' from lib.pyse import Pyse class Base(object): def __init__(self): self.pyse = Pyse('chrome') def open(self): self.pyse.open('http://zbox.imdsx.cn/user-login-Lw==.html1') #1.第一步打開瀏覽器,輸入網址 def quit(self): self.pyse.quit() class LoginPage(Base): #一個頁面一個類,每一個功能點定義爲函數 def send_username(self): css = "css=>#account" self.pyse.type(css,"admin") #定義密碼和登錄按鈕 def send_passwd(self): css = "css=>input[name=""password]" self.pyse.type(css,"houyafan123") def login(self): css = 'css=>#submit' self.pyse.click(css) def check_login(self): css = 'css=>a[href="/user-logout.html"]' flag = self.pyse.wait_and_save_exception(css,'test_a_lpgin') #校驗元素是否存在 return flag if __name__== '__main__': page = LoginPage() page.open() page.send_username() page.send_passwd() page.login() #調試當前頁面
''' 用到page,unittest 函數 當前頁面寫用例, ''' from pages.basepage import LoginPage import unittest class UiTestter(unittest.TestCase): @classmethod def setUpClass(cls): ''' def setUp(self) 當前類下每一條用例運行時候都要先執行一下 def tearDown(self) 當前類下每一條用例運行結束要執行一次 def setUpClass(cls)當前類下全部用例以前運行一次 def tearDownClass(cls)當前類下全部用例以後運行一次 ''' cls.page = LoginPage() cls.page.open() def test_a_lpgin(self): self.page.send_username() self.page.send_passwd() self.page.login() self.assertTrue(self.page.check_login()) @classmethod def tearDownClass(cls): cls.page.quit()