什么是selenium?
一个用于Web应用程序测试的工具直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。测试与浏览器的兼容性,测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。测试系统功能,创建衰退测试检验软件功能和用户需求。
slenium前世
早期是直接使用 javascrip 注入技术与浏览器打交道,slenium RC启动一个server 将web元素api转换为javascript。在Selenium内核启动浏览器之后注入这段Javascript,由此才实现了Selenium的目的:自动化Web操作。这种Javascript注入技术的缺点是速度不理想,而且稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。
slenium现在
Selenium2.x 提出了WebDriver的概念之后与浏览器交互利用原生的API,直接操作浏览器页面里的元素。不同的浏览器厂商对Web元素的操作和呈现一些差异直接导致了Selenium WebDriver要分浏览器厂商不同,而提供不同的实现。Selenium3.0发布后,最大更新点就是干掉了对selenium rc的支持,后面就一直是webdriver协议,
WebDriver工作流程
1、通过WebDriver创建一个浏览器服务,remote server
2、脚本启动时会在新的线程中启动一个浏览器,并绑定特定的端口,没个浏览器有不同的端口段。
3、client 创建1个session,在该session中通过http请求向remote server发送restful的请求,remote server解析请求,完成相应操作并返回response。
4、分析response,继续执行脚本还是结束执行
command.py:Command类中定义了WebDriver的一些常用的常量。
remote\webdrvier.py:所有浏览器webdrvier的基类,其中包含了所有webdriver的api接口
remote\remote_connection.py:包含启动Remote WebDrvier server,执行client请求,self._commands是selenium的核心请求参数,根据对应的Command常量,发送不同的http请求。
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
编写以下代码能打开对应的浏览器就配置成功:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from selenium import webdriver driver = webdriver.Chrome() driver.get('http://www.imdsx.cn')
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from selenium import webdriver driver = webdriver.Firefox() driver.get('http://www.imdsx.cn')
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from selenium import webdriver driver = webdriver.Ie() driver.get('http://www.imdsx.cn')
selenium定位
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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定位
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
xpath(xpath 定位尽量少用层级定位,如果开发更改了页面的层级,所有定位都挂了,尽量属性定位为主,层级为辅助) //* 取当前页面的全部元素 //*[@id='i1'] id 进行定位 @代表引用属性 //*[@placeholder="请通过ID定位元素"] //input[@placeholder="请通过ID定位元素"] 通过 标签名进一步缩小范围 //select[4] 如果存在不唯一的情况 可以通过角标进行取值 xpath从1开始取 //select[@size="4" and @multiple="multiple"] 逻辑定位
当不确定时可以只用copy直接复制xpath地址(这个地址相当于层级定位最好少用)
css selector定位
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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自动化框架
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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.自动化测试用例和页面数据源进行分离。 怎么判断业务逻辑的成功 举例说明:假设是登录的业务逻辑,判断登录的特有元素消失 ,就代表登录成功了。 只有登录成功了才会出现的元素 判断一下他是否出现
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
# -*- 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" οnclick='javascript:showCase(0)'>总结</button> <button class="btn btn-default" οnclick='javascript:showCase(1)'>失败</button> <button class="btn btn-default" οnclick='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" οnfοcus='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 οnfοcus='this.blur();' οnclick="document.getElementById('div_%(tid)s').style.display = 'none' " > [x]</a> </div> %(script)s <a href="#" οnclick="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)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
# 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")
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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)))
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
''' 主要放页面的元素 ''' 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() #调试当前页面
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
''' 用到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()