Web UI自动化

Web UI自动化

1、规划,安排

  • 1.1 什么项目适合做自动化

    a. 项目周期要长,是否需要对这个项目进行长期维护(维优),是否需要长期进行迭代,交付。
    b. 项目团队的能力,团队大小

  • 1.2 什么时候开始做?

    a. 绝大部分的功能都基本已经稳定了,界面,需求没有太多变化了
    (初期的几个版本基本稳定下来的之后,所以初期的几个版本重心点都放在功能,接口,性能等方面)
    b. 哪些功能已经稳定了,先实现自动化脚本的开发
    (在版本间歇期开发)

1、搭建自动化测试环境

  • 框架:python+selenium+unittest框架

  • 步骤

    1、安装Python3.x 配置环境变量

    2、安装PyCharm

    3、安装测试库

  pip install selenium

​ 4、安装浏览 chrome,firefox,Ie....

​ 5、安装浏览器驱动

​ 备注:浏览器驱动与浏览器的版本一定要匹配

​ 把浏览器驱动chromedriver.exe放入到python的安装目录

2、编写脚本

2.1、确定测试范围:

Web自动化的覆盖率80-90%,哪些功能有实现了自动化的。

方维项目Web自动化

前端:注册,登录,首页,查询标的,实名认证,借款,投资,提现,充值,还款
后台:登录,首页,贷款管理,理财管理,会员管理,资金管理,

2.2、确定测试框架:

python+selenium+unittest框架

2.3、写web UI自动化基础脚本的思路,流程:

说明:我们其实都是通过调用selenium库中的webdriver类中的API函数来实现对页面元素进行操作,具体都是根据功能用例的操作步骤来实现自动化脚本的编写。并针对结果进行断言判断结果是否与预期结果一致。

1、导包
2、实例化一个Webdriver对象
3、加载页面/网址
4、对页面进行操作,定位元素,操作元素(根据功能用例中的操作步骤来的)
5、断言—检查结果(检查点要全面)

备注:Web UI自动化基本脚本最核心关键的是:页面元素的定位,操作,还有就是断言,预期结果一定要检查到位,全面。调试

#百度搜索
#用例:验证精确搜索,搜索selenium
#操作步骤:1. 输入要搜索的数据  2. 点击百度一下
from selenium import webdriver
import re

#1. 实例化一个Webdriver对象,打开浏览器
driver = webdriver.Chrome()

#2. 加载页面
driver.get("http://www.baidu.com")

#3. 对页面进行操作,定位元素,操作元素(根据功能用例中的操作步骤来的)
#说明:webdriver类,webdriver对象中提供了各种的API函数,我们都是通过调用这些API函数来实现对页面元素的定位
element1 = driver.find_element_by_id('kw')
element2 = driver.find_element_by_id('su')

element1.send_keys('selenium')
element2.click()

#4. 断言
#1. 跳转之后的页面title应该为:selenium_百度搜索
#2. 跳转之后的页面的body中应该包含selenium相关的信息
try:
    assert driver.title=='selenium_百度搜索','页面title与预期结果不一致!'
    assert 'selenium' in re.findall('',driver.page_source)[0],'页面的body中不存在selenium信息!'
    print('百度搜索-精确搜索selenium用例,测试通过!')
except AssertionError as e:
    print('百度搜索-精确搜索selenium用例,测试不通过! %s' %e)

3、元素定位

3.1、页面元素定位的8种基本方式:

原则:有ID尽量用id定位,尽量避免用name,class name定位,因为这两个可能在压面会有重复名字。

​ 如果没有id,优先选择使用xpath,css_selector,link_text进行定位。

​ 一般xpath,css_selector成功概率高。

  • id定位:

    driver.find_element_by_id()
    
  • link text定位:

    driver.find_element_by_link_text()
    
  • xpath定位:拷贝

    driver.find_element_by_xpath()
    
  • css selector选择器定位: 拷贝

    driver.find_element_by_css_selector()
    
  • name名字定位:

    driver.find_element_by_name()
    
  • class name名字定位:

    driver.find_element_by_class_name()
    
  • tag标签定位:

    driver.find_element_by_tag_name()
    
  • partial_link_text模糊定位:

    driver.find_element_by_partial_link_text()
    
#用例:验证正常登录用例
#功能用例的操作步骤:1. 输入用户名  2. 输入密码  3. 点击的登录  4. 点击取消
#功能用例的预期结果:1. 跳转到首页  2. 首页上显示对应昵称
import time

from selenium import webdriver

#1. 实例化一个webdriver对象
driver = webdriver.Chrome()
driver.implicitly_wait(10)

#2. 加载页面
driver.get('http://localhost/fw')

#3. 根据功能用例的操作步骤,对页面元素进行定位并操作
#3.1 定位用户名输入框,输入用户名
driver.find_element_by_id('login-email-address').send_keys('jason')

#3.2 定位密码输入框,输入密码
driver.find_element_by_id('login-password').send_keys('zgp123456')

#3.3 定位登录按钮,点击登录按钮
driver.find_element_by_id('Iajax-login-submit').click()

#time.sleep(1)

#3.4 定位取消按钮,点击取消按钮
driver.find_element_by_xpath('//*[@id="fanwe_msg_box"]/table/tbody/tr/td[2]/div[3]/input[2]').click()

time.sleep(1)

#4. 断言,检查实际结果与预期结果是否一致
#4.1 考虑页面跳转是否正常,a. 检查页面title  b. 检查页面url  页面如果没有发生跳转,检查页面上有哪些新变化,去检查核心信息
page_element_info = driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div[1]/div/div[1]/span/span').text
print(page_element_info)
try:
    assert page_element_info=='jason','页面信息校验失败!'
    print('方维登录-正常登录用例,测试通过!')
except AssertionError as e:
    print('方维登录-正常登录用例,测试不通过! %s' %e)
finally:
    driver.close()
  • xpath定位:

一般直接通过f12拷贝xpath路径

//*[@id='login-email-address']
//*[@id="header"]/div[2]/div/ul/li[3]/a
//*[@class="ui-select-selected"]
driver.find_element_by_xpath('//*[@id="header"]/div[2]/div/ul/li[3]/div/a[3]').click()   # 点击申请贷款
#driver.find_element_by_link_text('申请借款').click()
driver.find_element_by_xpath('//*[@id="borrowlb"]/div/ul/li[2]/div[3]/a/img').click()     # 点击购房借款 (适用)
driver.find_element_by_xpath('//*[@id="J_save_deal_form"]/div[1]/div[5]/dl/dt').click()   # 点击借款用途
driver.find_element_by_xpath('//*[@class="ui-select-drop"]/a[2]').click()                 # 点击购房借款
driver.find_element_by_xpath('//*[@id="borrowtitle"]').send_keys('购房首付款')              # 输入借款标题
driver.find_element_by_xpath('//*[@id="J_save_deal_form"]/div[1]/div[7]/dl/dt').click()   # 点击有无抵押
driver.find_element_by_xpath('//*[@id="J_save_deal_form"]/div[1]/div[7]/dl/dd/a[2]').click()    #点击有
  • css Selector选择器:

一个标签元素的样式可以使用id样式,也可以使用class样式,id样式用#表示,class样式用.表示

driver.find_element_by_css_selector('#borrowamount').send_keys(5000)       # 输入借款金额
driver.find_element_by_css_selector('form#J_save_deal_form > div:nth-child(1) > div:nth-child(11) > dl > dt').click()   #点击还款周期
driver.find_element_by_css_selector('form#J_save_deal_form > div:nth-child(1) > div:nth-child(11) > dl > dd >a:nth-child(2)').click()   #点击按月还款driver.find_element_by_css_selector('#apr').send_keys(15)
driver.find_element_by_css_selector('form#J_save_deal_form > div:nth-child(1) > div:nth-child(18) > dl > dt').click()  #点击还款方式
driver.find_element_by_css_selector('form#J_save_deal_form > div:nth-child(1) > div:nth-child(18) > dl > dd >a:nth-child(3) ').click()

3.2、滚动条的处理

selenium并不是万能的,有时候页面上操作无法实现的,这时候就需要借助JS来完成了

当页面上的元素超过一屏后,想操作屏幕下方的元素,是不能直接定位到,会报元素不可见的。这时候需要借助滚动条来拖动屏幕,使被操作的元素显示在当前的屏幕上。滚动条是无法直接用定位工具来定位的。selenium里面也没有直接的方法去控制滚动条,这时候只能借助Js了,还好selenium提供了一个操作js的方法:execute_script(),可以直接执行js的脚本

一. 控制滚动条高度

1.1 滚动条回到顶部:

js="var q=document.getElementById('id').scrollTop=0"
driver.execute_script(js)

1.2 滚动条拉到底部

js="var q=document.documentElement.scrollTop=10000"
driver.execute_script(js)

1.3 滚动到指定位置

target = driver.find_element_by_id("id_keypair")
driver.execute_script("arguments[0].scrollIntoView();", target)

5、scrollTo函数

--scrollHeight 获取对象的滚动高度。

--scrollLeft 设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离。

--scrollTop 设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离。

--scrollWidth 获取对象的滚动宽度。

  • *#滚动到底部*
js = "window.scrollTo(0,document.body.scrollHeight)"

driver.execute_script(js)
  • *#滚动到顶部*
js = "window.scrollTo(0,0)"

driver.execute_script(js)

3.3、select下来列表定位:**


方法一:先点击父元素,弹出下来列表之后,然后在点击子元素

driver.find_element_by_xpath('//*[@id="adv-setting-4"]/select').click()
driver.find_element_by_xpath('//*[@id="adv-setting-4"]/select/option[3]').click()

方法二:先定位父元素,再定位子元素

select_element = driver.find_element_by_xpath('//*[@id="adv-setting-4"]/select')
select_element.find_element_by_xpath('//*[@id="adv-setting-4"]/select/option[3]').click()

方法三:使用selector类

#1. 实例化一个Select对象
select = Select(driver.find_element_by_xpath('//*[@id="adv-setting-4"]/select'))
#select.select_by_value("stf=1564107520.617,1564712320.617|stftype=1")
#select.select_by_index(2)
select.select_by_visible_text('最近一周')

3.4、动态id

注意:以后一旦碰到id带有数字的,一般这种ID可能是动态变化的。

对于动态ID元素定位的方式不能使用ID来定位,可以使用以下几种方式:

1、xpath路径的方式来定位,但是这个xpath路径不能是使用跟这个动态ID有关联的路径。比如:

//*[@id="img_out_306500039"]                #不可以
//*[@id="qlogin_list"]/a/span[4]            #xpath路径从上一级开始进行逐级搜索,这个是可以的。
//*[@class="img_out"]                       #保证class是唯一的,这个也可以的

2、通过css selector选择器来定位,但css selector不能使用跟这个动态ID相关的,ID样式

​ 通过样式,不能使用跟这个动态ID相关的,ID样式

​ 通过属性,不能使用动态ID属性

driver.find_element_by_css_selector(#img_out_306500039) #使用了动态ID的样式,不可以
#driver.find_element_by_css_selector(span.img_out)  #使用了class样式,可以,但是class要唯一
driver.find_element_by_css_selector(div[id="qlogin_list"]/a/span[4][class="img_out"])

3、先定位父元素,再定位子元素

parent_element = driver.find_element_by_xpath('//*[@id="qlogin_list"]/a[1]')  #定位父元素
#parent_element.find_element_by_css_selector(span.img_out)                    #再定位子元素

3.5、多窗口处理

1、如何判断是否为一个窗口?通过获取窗口的句柄,看是否有多个句柄,是否有新的句柄,如果有新的句柄,表示有一个新的窗口

#多窗口的处理#如何判断是否为新的窗口
#获取窗口的句柄(一个窗口对应一个句柄,句柄是指向这个窗口)
handles = driver.window_handles             #返回的是一个列表对象  
print(handles)
结果:
[
'CDwindow-4482B00295E5D9F37BD79461F3055B2A', 
'CDwindow-DB98CA0F85C348ED9282D2D4F870B3B2'
]

如何跳转到新的窗口

driver.switch_to.window(handles[1])

3.6、鼠标悬停

在做自动化测试的时候,经常会遇到这种情况,某个页面元素,你必须要把鼠标移动到上面才能显示出元素。那么这种情况,我们怎么处理呢?,selenium给我们提供了一个类来处理这类事件——ActionChains

ActionChains可以对需要模拟鼠标操作才能进行的情况,比如单击、双击、点击鼠标右键、拖拽等等进行操作。ActionChains方法列表:

move_to_element(to_element) —— 鼠标移动到某个元素(鼠标悬停)
context_click(on_element=None) —— 点击鼠标右键
double_click(on_element=None) ——双击鼠标左键
drag_and_drop(source, target) ——拖拽到某个元素然后松开
drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开
perform() —— 执行链中的所有动作
ActionChains(driver).move_to_element(driver.find_element_by_xpath('//*[@id="header"]/div[2]/div/ul/li[3]/a')).perform()                     # 我要借款
# 点击申请贷款
driver.find_element_by_xpath('//*[@id="header"]/div[2]/div/ul/li[3]/div/a[3]').click() 

3.7、内嵌网页的处理(iframe帧的处理)

需要先跳转到内嵌网页中,然后在去定位元素

driver.switch_to.frame(0) #1. frame名字或id  2. frame的索引第几个 3. 先定位这个frame元素
driver.find_element_by_xpath('/html/body').send_keys('购房借款')   #输入借款描述
先定位到iframe,然后跳转
element = driver.find_element_by_xpath('//*[@id="J_save_deal_form"]/div[1]/div[35]/div/div/div[2]/iframe')
    driver.switch_to.frame(element)

跳入iframe处理完成之后,一定要跳出iframe

driver.switch_to.parent_frame()     #跳到上一级的iframe中driver.switch_to.default_content()  #跳到最外层

3.8、对话框的处理

JavaScript 有三种弹窗 Alert (只有确定按钮), Confirmation (确定,取消等按钮), Prompt (有输入对话框)。

1.accept() 相当于点击弹出框的确定按钮:driver.switchTo().alert.accept();
2.dismiss() 相当于点击弹出框的取消按钮:driver.switchTo().alert.dismiss();
3.SendKeys(String input)针对于prompt情况的输入:driver.switchTo().alert.sendKeys("可以输入");
4.getText()获取弹出框文本内容:driver.switchTo().alert.getText();

4、各种元素定位异常的原因

1、页面加载延迟导致元素定位失败

说明:以后注意,凡是出现有点击页面跳转的情况,小心因为页面延迟导致元素定位失败的情况。

解决办法:

1、睡觉 强制等待——不好,如果脚本中出现很多的sleep(1),会导致整个脚本的执行效率。

因为:可能页面在1s之内早就已经出现了,但是由于用的是sleep(1),它会一直等,知道1s结束。

time.sleep(1)

2、隐式等待:

原理:如果在前面配置了隐式等待,那么只要在脚本执行过程中,出现有页面跳转的情况,都会延迟等待页面出现,如果页面一旦出现可以定位到元素了,脚本会继续往下执行,而不会等到10s。如果在10s之内还定位的元素没有出现,那么会报超时异常。

隐式等待是等页面加载,而不是元素加载!!!(隐式等待就是针对页面的,显式等待是针对元素的。)

#1. 实例化一个webdriver对象
driver = webdriver.Chrome()
driver.implicitly_wait(10)              #设定隐式等待10s

3、显示等待:

用于等待某个元素出现,然后再继续执行后续代码。显式等待是等元素加载!!!

这里会用到一个类 WebDriverWait类

原理:总共设定时间是10s,在10s钟之内,每隔0.5s会去找一次元素,循环找,如果在10s之内元素还没有出现,则抛出异常NoSuchElementException,如果找到了,则返回找到的这个元素。然后再去执行下一步的操作。

element = WebDriverWait(driver,10).until(lambda x: x.find_element_by_xpath('//*[@id="J_save_deal_form"]/div[1]/div[7]/dl/dd/a[2]'))
element.click()#点击有

2、换其他定位方式,比如css,xpath等

3、考虑是否有iframe内嵌网页的问题

4、考虑是否有多窗口问题

5、考虑是否因为滚动条位置不对,元素在页面上看不见

6、考虑是否为动态ID

5、自动化工程维护管理与优化

1、unittest框架

unittest框架,Pyunit框架是一个单元测试框架,基于Python,Junit,TestNg样式单元测试框架,它基础Java;自动化中利用unittest框架用来管理自动化用例,加载用例,执行用例。

1.1、Unittest 核心组件
  • test fixture(测试固件)

包含一个Setup()方法/函数,tearDown()方法/函数,用例执行之前都会先执行Setup()方法/函数,主要是完成一些准备初始化的工作,比如创建临时的数据库,文件和目录,用例数据读取,浏览器的打开等,用例执行完成之后,会执行tearDown()方法/函数,完成一些清理回收的工作,比如数据库断开,关闭浏览器

(1)比如说在这个测试用例中需要访问数据库,那么可以在setUp()中建立数据库连接以及进行一些初始化,在tearDown()中清除在数据库中产生的数据,然后关闭连接。注意tearDown的过程很重要,要为以后的TestCase留下一个干净的环境。

  • test case(测试用例):

什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),以及测试后环境的还原(tearDown),还有包括用例方法,每个用例方法都必须要以test开头

  • test suite(测试套件):

多个测试用例的集合就是 suite,一个 suite 可以包含多个 测试用例,也可以嵌套 suite。可以通过addTest()方法手动增加Test Case,也可通过TestLoader自动添加Test Case,TestLoader在添加用例时,会没有顺序。

  • test runner(运行器):

用来执行测试套件中测试用例的,最终执行完成之后会生成一个测试结果。

  • TestLoader(加载器):用来加载用例,把用例加载到测试套件中
  • Test Result(测试结果):包括运行了多少测试用例,成功了多少,失败了多少等信息。
1.2、执行原理
img
1.3、unittest常用的断言方法
assertEqual(a, b)   判断a==b
assertNotEqual(a, b)    判断a!=b
assertTrue(x)   bool(x) is True
assertFalse(x)  bool(x) is False
assertIs(a, b)  a is b
assertIsNot(a, b)   a is not b
assertIsNone(x)     x is None
assertIsNotNone(x)  x is not None
assertIn(a, b)  a in b
assertNotIn(a, b)   a not in b
assertIsInstance(a, b)  isinstance(a, b)                #判断对象的,对象相对判断
assertNotIsInstance(a, b)   not isinstance(a, b)        #对象不相等的判断

1.3、如何使用unittest框架写用例:

1、导包

import unittest

2、定义一个类,继承unittest.TestCase基类

class FwLogin(unittest.TestCase):

3、重写/覆盖TestCase中的setUp(),tearDown()方法,在这个两个方法中去搭建测试用例的环境,清除恢复数据

class FwLogin(unittest.TestCase):
    def setUp(self):
        '''
        用例执行之前都会先执行Setup()方法/函数,主要是完成一些准备初始化的工作
        :return:
        '''
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get('http://localhost/fw')

    def tearDown(self):
        '''
        用例执行完成之后,会执行**tearDown()**方法/函数,完成一些销毁的工作。
        :return:
        '''
        self.driver.quit()

4、实现用例方法,每个用例方法必须以test开头

class FwLogin(unittest.TestCase):
    def setUp(self):
        '''
        用例执行之前都会先执行Setup()方法/函数,主要是完成一些准备初始化的工作
        :return:
        '''
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get('http://localhost/fw')

    def test_fw_normal_login(self):
        '''
        用例方法
        :return:
        '''
        #1. 定位用户名输入框,输入用户名
        self.driver.find_element_by_xpath('//*[@id="login-email-address"]').send_keys('jason')

        #2. 定位密码输入框,输入密码
        self.driver.find_element_by_id('login-password').send_keys('zgp123456')

        #3. 定位登录按钮,点击登录按钮
        self.driver.find_element_by_id('Iajax-login-submit').click()

        #4. 定位取消按钮,点击取消按钮
        self.driver.find_element_by_xpath('//*[@id="fanwe_msg_box"]/table/tbody/tr/td[2]/div[3]/input[2]').click()

        #5. 断言
        # 4. 断言,检查实际结果与预期结果是否一致
        # 4.1 考虑页面跳转是否正常,a. 检查页面title  b. 检查页面url  页面如果没有发生跳转,检查页面上有哪些新变化,去检查核心信息
        page_element_info = self.driver.find_element_by_xpath(
            '/html/body/div[2]/div/div[2]/div[1]/div/div[1]/span/span').text

        try:
            assert page_element_info == 'jason', '页面信息校验失败!'
            print('方维登录-正常登录用例,测试通过!')
        except AssertionError as e:
            print('方维登录-正常登录用例,测试不通过! %s' % e)

    def tearDown(self):
        '''
        用例执行完成之后,会执行**tearDown()**方法/函数,完成一些销毁的工作。
        :return:
        '''
        self.driver.quit()

5、利用TestLoader加载用例到测试套件中

import unittest

from test_case.fw_login import FwLogin
from test_case.fw_register import FwRegister

if __name__ == '__main__':
    #加载用例
    #方法1:
    #1. 创建一个测试套件
    suite = unittest.TestSuite()

    #2. 加载用例到测试套件中
    suite.addTest(FwLogin('test_fw_normal_login'))
    suite.addTest(FwLogin('test_fw_empty_user_login'))
    suite.addTest(FwRegister('test_fw_normal_register'))

    #3. 创建一个执行器
    runner = unittest.TextTestRunner()

    #4. 执行用例
    runner.run(suite)
#方法2:
# 测试套件
suite = unittest.TestSuite()
# 测试用例加载器
loader = unittest.TestLoader()
# 把测试用例加载到测试套件中
suite.addTests(loader.loadTestsFromTestCase(FwLogin))
suite.addTests(loader.loadTestsFromTestCase(FwRegister))

#3. 创建一个执行器
runner = unittest.TextTestRunner()

#4. 执行用例
runner.run(suite)
#方法3:
suite = unittest.defaultTestLoader.discover('D:\\selenium\\1947_Web_Fw_Project\\test_case\\',pattern='fw*.py')
#3. 创建一个执行器
runner = unittest.TextTestRunner()

#4. 执行用例
runner.run(suite)

6、利用TextTestRunner执行器去执行测试套件中的用例

#3. 创建一个执行器
runner = unittest.TextTestRunner()
#4. 执行用例
runner.run(suite)
#1. 导包
import unittest
from selenium import webdriver

#2. 定义一个类,继承unittest.TestCase
#只有继承了unittest.TestCase才成为了用例,不继承只是一个普通的类
class FwLogin(unittest.TestCase):
    def setUp(self):
        '''
        用例执行之前都会先执行Setup()方法/函数,主要是完成一些准备初始化的工作
        :return:
        '''
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get('http://localhost/fw')

    def test_fw_normal_login(self):
        '''
        用例方法
        :return:
        '''
        #1. 定位用户名输入框,输入用户名
        self.driver.find_element_by_xpath('//*[@id="login-email-address"]').send_keys('jason')

        #2. 定位密码输入框,输入密码
        self.driver.find_element_by_id('login-password').send_keys('zgp123456')

        #3. 定位登录按钮,点击登录按钮
        self.driver.find_element_by_id('Iajax-login-submit').click()

        #4. 定位取消按钮,点击取消按钮
        self.driver.find_element_by_xpath('//*[@id="fanwe_msg_box"]/table/tbody/tr/td[2]/div[3]/input[2]').click()

        #5. 断言
        # 4. 断言,检查实际结果与预期结果是否一致
        # 4.1 考虑页面跳转是否正常,a. 检查页面title  b. 检查页面url  页面如果没有发生跳转,检查页面上有哪些新变化,去检查核心信息
        page_element_info = self.driver.find_element_by_xpath(
            '/html/body/div[2]/div/div[2]/div[1]/div/div[1]/span/span').text

        try:
            assert page_element_info == 'jason', '页面信息校验失败!'
            print('方维登录-正常登录用例,测试通过!')
        except AssertionError as e:
            print('方维登录-正常登录用例,测试不通过! %s' % e)

    def test_fw_empty_user_login(self):
        # 1. 定位用户名输入框,清空用户名文本框
        self.driver.find_element_by_xpath('//*[@id="login-email-address"]').clear()

        # 2. 定位密码输入框,输入密码
        self.driver.find_element_by_id('login-password').send_keys('zgp123456')

        # 3. 定位登录按钮,点击登录按钮
        self.driver.find_element_by_id('Iajax-login-submit').click()

        # 5. 断言
        assert_text = self.driver.find_element_by_xpath('//*[@id="fanwe_error_box"]/table/tbody/tr/td[2]/div[2]').text
        try:
            self.assertEqual('Email格式错误,请重新输入或者昵称格式错误,请重新输入',assert_text,'信息不一致!')
            print('方维登录-用户名为空用例执行成功!')
        except AssertionError as e:
            print('方维登录-用户名为空用例执行失败! %s' %e)

    def tearDown(self):
        '''
        用例执行完成之后,会执行**tearDown()**方法/函数,完成一些销毁的工作。
        :return:
        '''
        self.driver.quit()

if __name__=='__main__':
    unittest.main()

2、数据驱动,参数化

用户数据提取到Excel表格中,进行统一化管理,比如:就拿登录模块为例,那用户数据就是'用户名',‘密码’,‘断言文本’ ,把这些数据提取到Excel表格中,实现数据与脚本的分离,然后封装一个读取Excel文件数据的函数实现数据的读取,并利用DDT(Data Driver Test)模型讲数据引用到脚本中去实现参数化。

对于Excel表格数据的读取我们都是调用公司封装好的模块中的API函数来实现的。

对于Excel表格数据的读取需要封装一个模块实现数据的读取,这里需要用到xlrd,xlwt两个库,通过调用xlrd库中的API来实现对数据的读取,具体代码如下:

# coding:utf-8
import xlrd
class ExcelUtil():

    def __init__(self, excelPath, sheetName):
        '''
        构造方法
        :param excelPath:
        :param sheetName:
        '''
        self.data = xlrd.open_workbook(excelPath)
        self.table = self.data.sheet_by_name(sheetName)
        # 获取第一行作为key值
        self.keys = self.table.row_values(0)
        # 获取总行数
        self.rowNum = self.table.nrows
        # 获取总列数
        self.colNum = self.table.ncols

    def dict_data(self):
        if self.rowNum <= 1:
            print("总行数小于1")
        else:
            r = []
            j=1
            for i in range(self.rowNum-1):
                s = {}
                # 从第二行取对应values值
                values = self.table.row_values(j)
                for x in range(self.colNum):
                    s[self.keys[x]] = values[x]
                r.append(s)
                j+=1
            return r

if __name__ == "__main__":
    # filepath = "D:\\test\\web-project\\5ke\\testdata.xlsx"
    filepath = "D:\\selenium\\1947_Web_Fw_Project\\data\\测试数据.xls"
    sheetName = "登录"
    data = ExcelUtil(filepath, sheetName)
    print(data.dict_data())

引入DDT(Data Driver Test)模型,利用DDT来实现数据的驱动

1.安装ddt

pip install ddt

2.导入ddt

3.在用例类上引用ddt

@ddt.ddt
class FwLogin(unittest.TestCase):

4.在测试用例函数上去引用测试数据,这样在测试用例函数里面就可以使用引用过来的用户数据了。

@ddt.data(*testdata)
def test_fw_normal_login(self,data):

完整的代码:

#1. 导包
import unittest
from selenium import webdriver
from common.readExcel import ExcelUtil
import ddt

#1. 读取excel表格的测试数据
testdata = ExcelUtil("D:\\selenium\\1947_Web_Fw_Project\\data\\测试数据.xls",'登录').dict_data()
#[{'case_name':'正常登录','username':'jason','password':'zgp123456','assert_text':'jason'},{},{},{},{}]

#2. 定义一个类,继承unittest.TestCase
#只有继承了unittest.TestCase才成为了用例,不继承只是一个普通的类
@ddt.ddt
class FwLogin(unittest.TestCase):
    def setUp(self):
        '''
        用例执行之前都会先执行Setup()方法/函数,主要是完成一些准备初始化的工作
        :return:
        '''
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get('http://localhost/fw')

    @ddt.data(*testdata)
    def test_fw_normal_login(self,data):

        '''
        用例方法
        :return:
        '''
        # print(data)
        #1. 定位用户名输入框,输入用户名
        self.driver.find_element_by_xpath('//*[@id="login-email-address"]').send_keys(data['username'])

        #2. 定位密码输入框,输入密码
        self.driver.find_element_by_id('login-password').send_keys(data['password'])

        #3. 定位登录按钮,点击登录按钮
        self.driver.find_element_by_id('Iajax-login-submit').click()

        if (data['case_name']=='正常登录'):
            #4. 定位取消按钮,点击取消按钮
            self.driver.find_element_by_xpath('//*[@id="fanwe_msg_box"]/table/tbody/tr/td[2]/div[3]/input[2]').click()

        #5. 断言
        # 4. 断言,检查实际结果与预期结果是否一致
        # 4.1 考虑页面跳转是否正常,a. 检查页面title  b. 检查页面url  页面如果没有发生跳转,检查页面上有哪些新变化,去检查核心信息
        if (data['case_name']=='正常登录'):
            page_element_info = self.driver.find_element_by_xpath(
                '/html/body/div[2]/div/div[2]/div[1]/div/div[1]/span/span').text
            #print(data['assert_text'])
            #print(page_element_info)
            #try:
            assert page_element_info == data['assert_text'], '页面信息校验失败!'
        else:
            assert_info = self.driver.find_element_by_xpath('//*[@id="fanwe_error_box"]/table/tbody/tr/td[2]/div[2]').text
            self.assertEqual(assert_info,data['assert_text'],'信息不一致,断言错误!')

    def tearDown(self):
        '''
        用例执行完成之后,会执行**tearDown()**方法/函数,完成一些销毁的工作。
        :return:
        '''
        self.driver.quit()

if __name__=='__main__':
    unittest.main()

3、模块的封装

主要是针对一些核心的常用的功能业务模块尽心封装,方便调用,比如:登录模块,打开浏览器的操作,针对元素定位做二次封装处理,数据库的操作的封装,Excel表格数据的读取的封装等等

Excel表格操作的封装:

# coding:utf-8
import xlrd
class ExcelUtil():

    def __init__(self, excelPath, sheetName):
        '''
        构造方法
        :param excelPath:
        :param sheetName:
        '''
        self.data = xlrd.open_workbook(excelPath)
        self.table = self.data.sheet_by_name(sheetName)
        # 获取第一行作为key值
        self.keys = self.table.row_values(0)
        # 获取总行数
        self.rowNum = self.table.nrows
        # 获取总列数
        self.colNum = self.table.ncols

    def dict_data(self):
        if self.rowNum <= 1:
            print("总行数小于1")
        else:
            r = []
            j=1
            for i in range(self.rowNum-1):
                s = {}
                # 从第二行取对应values值
                values = self.table.row_values(j)
                for x in range(self.colNum):
                    s[self.keys[x]] = values[x]
                r.append(s)
                j+=1
            return r

if __name__ == "__main__":
    # filepath = "D:\\test\\web-project\\5ke\\testdata.xlsx"
    filepath = "D:\\selenium\\1947_Web_Fw_Project\\data\\测试数据.xls"
    sheetName = "登录"
    data = ExcelUtil(filepath, sheetName)
    print(data.dict_data())

数据库的封装:

'''
封装:
    1. 连接数据库
    2. 增,删,查,改的操作。
'''
import pymysql.cursors

def connect_db(host,username,pwd,db_name,charset='utf8'):
    '''
    功能:连接数据库
    :param host:            主机ip
    :param username:        账号
    :param pwd:             密码
    :param db_name:         数据库名字
    :param charset:         编码方式
    :return:
    '''
    try:
        con = pymysql.connect(
            host=host,              # 数据库服务器的ip地址
            user=username,          # 数据库账号
            password=pwd,           # 数据库密码
            db=db_name,             # 数据库名称
            charset=charset,        # 编码方式
            cursorclass=pymysql.cursors.DictCursor
        )
    except pymysql.err.Error as e:                                      #捕获异常
        print("数据库连接失败:%s" %e)
    return con  # 连接对象返回出去

def close_db(connect):
    '''
    功能:关闭数据库的连接
    :param connect:
    :return:
    '''
    connect.close()

def select(connect,sql,params=None):
    '''
    功能:查询操作
    :param connect:
    :param sql:
    :param params:
    :return:
    '''
    try:
        #1. 创建游标
        crs = connect.cursor()
        #2. 执行sql语句
        crs.execute(sql,params)                 #执行sql语句可能会失败
        #3. 提交
        connect.commit()
        #4. 提取数据
        result = crs.fetchall()
        return result                           #正常返回 查询的数据
    except pymysql.err.Error as e:
        print('执行查询操作失败 %s' %e)
        return False                            #如果出现异常,返回false
    finally:                                    #finally里面的语句一定会执行。
        #5. 关游标
        crs.close()

def insert(connect,sql,params):
    try:
        # 1. 创建游标
        crs = connect.cursor()
        # 2. 执行sql语句
        crs.execute(sql, params)
        # 3. 提交
        connect.commit()
        return True
    except pymysql.err.Error as e:
        print('执行增加数据操作失败 %s' %e)
        return False
    finally:
        # 5. 关游标
        crs.close()

def delete(connect,sql,params):
    try:
        # 1. 创建游标
        crs = connect.cursor()
        # 2. 执行sql语句
        crs.execute(sql, params)
        # 3. 提交
        connect.commit()
        return True
    except pymysql.err.Error as e:
        print('执行删除数据操作失败 %s' % e)
        return False
    finally:
        # 5. 关游标
        crs.close()

def update(connect,sql,params):
    try:
        # 1. 创建游标
        crs = connect.cursor()
        # 2. 执行sql语句
        crs.execute(sql, params)
        # 3. 提交
        connect.commit()
        return True
    except pymysql.err.Error as e:
        print('执行修改数据操作失败 %s' % e)
        return False
    finally:
        # 5. 关游标
        crs.close()

打开浏览器的封装:

def _open_browser(url,mode):
    '''
    功能:打开浏览器,加载页面
    :param url:
    :param mode:
    :return:
    '''
    if mode in ['Chrome','chrome']:
        driver = webdriver.Chrome()                 #打开浏览器
    elif mode in ['Firefox','ff','firefox']:
        driver = webdriver.Firefox()
    elif mode in ['Ie','ie']:
        driver = webdriver.Ie()

    driver.implicitly_wait(10)                      #设置隐式等待延迟10s
    driver.maximize_window()                        #窗口最大化

    driver.get(url)                                 #加载页面
    return driver

登录模块的封装:

#1. 登录
def _login(driver,username,password):
    # 3.1 定位用户名输入框,输入用户名
    driver.find_element_by_id('login-email-address').send_keys(username)

    # 3.2 定位密码输入框,输入密码
    driver.find_element_by_id('login-password').send_keys(password)

    # 3.3 定位登录按钮,点击登录按钮
    driver.find_element_by_id('Iajax-login-submit').click()

    # 3.4 定位取消按钮,点击取消按钮
    driver.find_element_by_xpath('//*[@id="fanwe_msg_box"]/table/tbody/tr/td[2]/div[3]/input[2]').click()

4、自动化测试报告

HTMLReport是一个单元测试测试运行器,可以将测试结果保存在 Html 文件中,用于人性化的结果显示。
仅支持Python 3.x

1、导包
import HTMLReport
suite = unittest.defaultTestLoader.discover('D:\\selenium\\1947_Web_Fw_Project\\test_case\\',pattern='fw*.py')

#3. 创建一个执行器
# runner = unittest.TextTestRunner()
runner = HTMLReport.TestRunner(
                    #report_file_name='test',  # 报告文件名,如果未赋值,将采用“test+时间戳”
                    output_path='report',  # 保存文件夹名,默认“report”
                    title='测试报告',  # 报告标题,默认“测试报告”
                    description='无测试描述',  # 报告描述,默认“测试描述”
                    thread_count=1,  # 并发线程数量(无序执行测试),默认数量 1
                    thread_start_wait=3,  # 各线程启动延迟,默认 0 s
                    sequential_execution=False,  # 是否按照套件添加(addTests)顺序执行,
                    # 会等待一个addTests执行完成,再执行下一个,默认 False
                    # 如果用例中存在 tearDownClass ,建议设置为True,
                    # 否则 tearDownClass 将会在所有用例线程执行完后才会执行。
                    # lang='en'
                    lang='cn'  # 支持中文与英文,默认中文
                )
#4. 执行用例
runner.run(suite)

你可能感兴趣的:(Web UI自动化)