闲逸笔记

keep it simple & stupid

Knitter 测试框架基础文档(草稿)

本文档假设您已有 Python 编程基础知识,以及 Selenium 基础知识。

安装条件

  • 目前支持 Python 2.7/3.4。

  • 目前只在 Windows 环境使用。Linux 理论上亦支持。

安装

pip install knitter

开始测试任务

  • 创建一个空的 Python package,这里起名为 "demoprj",做为我们测试项目。

    demoprj/
        __init__.py
    
  • 在 "demoprj" 中添加一个文件 conf.py, 用于管理配置信息。

    demoprj/
        __init__.py
        conf.py
    
  • 在 "demoprj" 中创建一个 package "page", 用于管理所有页面。

    demoprj/
        __init__.py
        conf.py
        page/
            __init__.py
    
  • 在 "demoprj" 中创建一个 package "testcase", 用于管理所有用例。

    demoprj/
        __init__.py
        conf.py
        page/
            __init__.py
        testcase/
            __init__.py
    
  • 在外部创建一个 run.py,用于运行我们的测试脚本

    demoprj/
        __init__.py
        conf.py
        page/
            __init__.py
        testcase/
            __init__.py
    run.py
    

设置配置文件 conf.py

  • 示例

    class MultiBrowsersDemo:
        BASE_URL = 'http://sleepycat.org/static/knitter/KnitterDemo.html'
        RESULT_PATH = "D:/WorkSpace/GitHub-Knitter/example/data"
        TESTING_BROWSERS = 'Chrome|Firefox'
        DRIVER_OF_CHROME = 'D:/WorkSpace/GitHub-Knitter/example/drivers/chromedriver_win32/chromedriver.exe'
    
  • 参数解释

    • BASE_URL

      • 启动浏览器时所打开的起始 URL
    • RESULT_PATH

      • 用于保存结果的目录。如果不设置,默认是 run.py 文件所在的目录。
    • TESTING_BROWSERS

      • 要在哪个浏览器上运行。值为 IE, Firefox, Chrome。如果多个,则用 | 隔开。如果设置了多个,则每个用例会在每个浏览器上各运行一次。
    • DRIVER_OF_CHROME

      • Chrome 浏览器驱动文件。
    • DRIVER_OF_IE

      • IE 浏览器驱动文件。

定位页面控件

  • 在 page/ 中创建一个文件 KnitterDemo.py, 用于表示 http://sleepycat.org/static/knitter/KnitterDemo.html 这个页面。

    # -*- coding: utf-8 -*-
    
    from knitter.webelement import WebElement
    from selenium.webdriver.common.by import By
    
    class Name:
        class Title(WebElement):
            (by, value) = (By.ID, 'title')
    
        class Name(WebElement):
            (by, value) = (By.ID, 'name')
    
    class SubmitButton(WebElement):
        (by, value) = (By.XPATH, '//button[@onclick="do_submit();"]')
    
  • 原则上,一个 Python 文件(如 KnitterDemo.py) 表示一个页面。一个 class 表示一个页面控件。可以使用嵌套 class 分类不同区域的控件。

  • 每个 class 页面控件必须继承类 WebElement。类 WebElement 包含了所有控件所用的操作方法,如 Click(), Set() 等等。

  • 类 WebElement 有三个属性 by, value, index。用于唯一的标志出当前的控件。index 的默认值为 0。

  • 一般情况下,只需指定 by 和 value 两个的值,即可定位当前控件。特殊情况可以使用 index。更特殊情况可以使用参数。如下:

    class AnswerOfQuestion(WebElement):
        def __init__(self, order=0):
            (self.by, self.value) = (By.XPATH, '//input[@focus-on="question%s"]' % order)
    
    SignIn.AnswerOfQuestion(2).Set("this is a test string.")
    

编写测试用例

  • 在 testcase/ 中创建一个文件 validations.py,表示一个测试用例模块。本模块中放置所有与验证相关的用例(一个用例对应一个函数)。运行时,可以运行整个模块,亦可运行其中单个用例。

    # -*- coding: utf-8 -*-
    
    from demoprj.page import KnitterDemo
    
    def TestCase001_NormalInputTest():
        #### Name ###
        KnitterDemo.Name.Title.Select("Mr.")
        KnitterDemo.Name.Name.Set("Henry.Wang")
    
        ### Gender ###
        KnitterDemo.Gender.Male.Click()
    
        ### Hobbies ###
        KnitterDemo.Hobby.Music.Click()
        KnitterDemo.Hobby.Travel.Click()
    
        ###### Result ######
        KnitterDemo.SubmitButton.Click()
    
        KnitterDemo.Result.VerifyInnerHTMLContains("Henry.Wang")
        KnitterDemo.Result.VerifyAttribute("innerHTML", "Music", method="contain")
    
  • 所有的元素控件都继承于 WebElement,也就继承了其 Click(), Select() 等所有的操作方法。

  • 每一个控件在操作前都会先确定元素是否存在。如果无法找到会多次尝试寻找(因为考虑到页面加载速度过慢的情况)。一定时间(一般三分钟)内找不到则报错,退出当前用例的运行。

  • 常用的操作方法:

    • Click()

      • 点击当前控件。
    • Set(value)

      • 设置输入内容。用于 input, textarea 等。(相当于 clear() + send_keys(value))。
    • Select(value)

      • 选择内容。用于 select, ul。
    • SelectByOrder(order)

      • 按顺序选择。
    • SelectByPartial(value)

      • 按部分文本内容选择。选第一个符合条件的。
    • TypeIn(value)

      • 输入内容。如果里面以前有内容,则不会清除前面的内容。(相当于直接 send_keys(value))。用于 input, textarea 等。
    • WaitForAttribute(name, value, method="equal")

      • 等待控件某个属性(name)的值(value)符合条件(method)。如:

        # 等待直到 ReminderDate 的 ng-model 属性的值等于 hello
        NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', method='equal') 
        
        # 等待直到 ReminderDate 的 ng-model 属性的值包含 hello
        NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', method='contain')
        
        # 等待直到 ReminderDate 的 ng-model 属性的值不包含 hello
        NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', method='not contain')
        
        # 等待直到 ReminderDate 的 ng-model 属性的值被包含于 hello
        NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', method='in')
        
        # 等待直到 ReminderDate 的 ng-model 属性的值不等于 hello
        NewClaim.Dates.ReminderDate.WaitForAttribute('ng-model', 'hello', method='not equal')
        
    • WaitForAppearing()

      • 等待更长时间(一般是两分钟)以等待控件出现。如果最后仍未出现亦不报错。走下一步。
    • IsExist()

      • 控件存在,则返回 True;否则,返回 False。
    • IsEnabled()

      • 控件 enabled,则返回 True;否则,返回 False。
    • IsAttribute(name, value, method="contain")

      • 控件某个属性(name)的值(value)是否符合条件(method)。参考 WaitForAttribute()。
    • VerifyInnerHTMLContains(contain_content)

      • 验证控件的 innerHTML 是否包含 contain_content。失败则将当前用例标为失败,并退出当前用例的运行。
    • VerifyAttribute(name, value, method='equal')

      • 验证控件某个属性(name)的值(value)是否符合条件(method)。参考 WaitForAttribute()。

运行测试

  • 在 run.py 中导入 conf 和 testcase

    # -*- coding: utf-8 -*-
    
    from knitter import executer
    from demoprj import conf, testcase
    
    executer.run(conf.MultiBrowsersDemo, testcase.validations.TestCase001_NormalInputTest)
    
  • 注意:在运行单个用例时,不要在用例名后加括号()。因为导入的是函数对象,而不是函数的返回值。

  • 如果要运行多个用例,可以添加多个参数:

    executer.run(conf.MultiBrowsersDemo, testcase.validations.TestCase001_NormalInputTest,
                                         testcase.validations.TestCase002_Data_In_Excel)
    
  • 如果要运行整个 validations 模块,则:

    executer.run(conf.MultiBrowsersDemo, testcase.validations)
    
  • 如果要运行多个模块,同上。

  • 运行结果将保存在 run.py 同级目录中,新建一个 result/ 目录。如果在 conf.py 中配置了 RESULT_PATH,则保存到相应的位置。

详细示例代码


Permalink [http://sleepycat.org/blog/39]


评论区

 

小样   2018-01-04 10:18  在此条评论下回复 ▼ 

能不能支持自动发送测试报告

Henry    Blog Author  2018-01-15 13:51

抱歉,目前不支持。

 

 

WXL   2017-03-29 16:07  在此条评论下回复 ▼ 

你好, 我又有疑问麻烦你了,如下代码
loginpage.Logout.Logout.Click()
loginpage.Logout.Confirm.Click()

'''
if loginpage.Logout.Logout.IsExist():
loginpage.Logout.Logout.Click()
loginpage.Logout.Confirm.Click()
'''
直接运行就会报错,改成运行注释掉的带if的内容就正常,但是if仅仅是判断存在的呀。麻烦解疑,我已经研究好几天了,也没看出为什么,报错内容如下:
2017-03-29 16:02:57 Step: Element [Logout]: Click()
2017-03-29 16:02:58 Step: Error!
<class 'selenium.common.exceptions.WebDriverException'>
Message: unknown error: Element <div class="quit" onclick="logout()">...</div> is not clickable at point (1266, 29). Other element would receive the click: <div id="cover" class="coverCanvas ease-cover-hidden" style="display: block;"></div>
(Session info: chrome=57.0.2987.98)
(Driver info: chromedriver=2.28.455520 (cc17746adff54984afff480136733114c6b3704b),platform=Windows NT 10.0.14393 x86_64)

Henry    Blog Author  2017-03-30 22:29

这个错误与 .IsExist() 无关,应该是点击 Logout 出现问题。请查看下是不是前面代码导致后面的 Click() 失败。

 

 

WXL   2017-03-24 17:09  在此条评论下回复 ▼ 

那我如果是必须登录系统之后,才能进行一系列的操作的,就不太方便使用这个框架了吗?如果相关联的写在一起,那我就得每次都重新调用登录的方法是吗?我试着改了env.RESTART_BROWSER的默认值为FALSE好像也没有办法实现不重新打开浏览器呢。

Henry    Blog Author  2017-03-28 14:01

根据以前的经验是每个用例都重新登陆的。毕竟对自动化测试来说,只是多花一点运行时间而已,影响不大,但好处很多。所以框架里没有做新用例不重新登陆的处理。

 

WXL   2017-03-24 17:09  在此条评论下回复 ▼ 

那我如果是必须登录系统之后,才能进行一系列的操作的,就不太方便使用这个框架了吗?如果相关联的写在一起,那我就得每次都重新调用登录的方法是吗?我试着改了env.RESTART_BROWSER的默认值为FALSE好像也没有办法实现不重新打开浏览器呢。

 

 

WXL   2017-03-22 17:45  在此条评论下回复 ▼ 

你好,我想知道一个这样的场景,就是两个用例,一个登录系统一个退出系统;根据你这个方法,我不知道如何实现登录后浏览器不关闭直接方便测试退出系统呢?之前unittest可以使用setUpClass、tearDownClass代替即可初始化及清理方法即可,我想知道这套方法需要改哪里呢? 非常感谢

Henry    Blog Author  2017-03-23 14:50

这个框架在设计方面,是强制每个测试用例要关闭浏览器、清空环境并重新启动浏览器的。这样做的好处非常多: 便于调试单个失败的用例,便于定位问题,避免出现长时间运行的不稳定情况等等。尽量做到每个测试用例能独立运行,或者,把相关联的手工测试用例放到一个自动化用例中运行。

 

 

lin   2016-09-20 14:29  在此条评论下回复 ▼ 

遇到个问题,有个上传文件的控件是input标签的,一般都说input的上传文件的标签可以直接send_keys输入文件路径就好了。
但是,我在page/里用class定位了这个input,在testcase里 xxx.Set("C:\\bin\\test.bin")却运行fail,不知道要怎么解决?
log:
2016-09-20 14:11:41 Step: Element [Upgrade_upload_file]: Set [C:\\bin\\test.bin].
2016-09-20 14:11:41 Step: Error!
<class 'selenium.common.exceptions.InvalidElementStateException'>
Message: invalid element state: Element must be user-editable in order to clear it.
(Session info: chrome=49.0.2623.110)
(Driver info: chromedriver=2.9.248315,platform=Windows NT 6.1 SP1 x86_64)

lin   2016-09-20 23:29

请忽略,仔细看下发现这个input不能用clear,所以把set函数原样复制一份改名成SetnoClear,去掉clear就可以用了

 

 

 

reca   2016-08-31 13:32  在此条评论下回复 ▼ 

1.在调试过程中能打印错误信息吗?
2.在用例执行失败后能给出失败原因吗?

lin   2016-09-03 23:57

执行失败的话,knitter框架会自动截图,存在测试报告文件夹底下。
至于失败的原因代码错误的话,日志里会输出异常提示,如果是业务上的失败原因,这个应该是在写用例的过程中自己需要考虑的吧,比如说多添加一些异常出现时候的关键变量打印日志之类。框架里封装的函数已经有很多很清晰的日志输出了,学习他这样的风格我觉得就很棒啦! 再次感谢大神写的这个框架!

Henry    Blog Author  2016-09-05 14:47

@lin, 谢谢! :)
@reca,1) 调试或执行过程日志是实时打印在 result\index.html 中的。2) 如果用例执行失败,日志中会打印出失败信息,然后对当前浏览器截图,然后退出并执行下一个用例。所有结果都可以用浏览器打开 result\index.html 实在查看。
参考:https://github.com/hww712/knitter/tree/master/example/result

 

 

lin   2016-08-21 15:20  在此条评论下回复 ▼ 

求助,页面按钮点击后有alert对话框,这个应该怎么写?

Henry    Blog Author  2016-08-21 18:59

如果只用 selenium,大致的用法是这样的:
browser = webdriver.Firefox()
alert = browser.switch_to_alert()
alert.accept()
browser.switch_to_default_content()

如果用这个 knitter 框架,则参考如下代码:
from knitter.webelement import WebBrowser
WebBrowser.AlertAccept()

 

 

grace   2016-06-02 14:08  在此条评论下回复 ▼ 

cof里的路径不支持相对路径吗?

grace   2016-06-02 14:18

请忽略 ,可以支持,是我自己把相对路径写错了

 

发表评论

top bottom