web自动化测试笔记(上、下部分一起补上了)

2020-05-15  吴水荣 

自动化测试,包括Web自动化、移动端自动化、接口自动化等,目前暂时先把web自动化的笔记放在这里,而且只放到PO模式的笔记,这些笔记很多知识点都是有实例的,不想就这样子丢了,还是先保存好。后面还有一些,过两天补上:


一、什么样的项目需要做自动化测试:
1、需求变动不频繁
2、项目周期长
3、项目需要回归测试

二、什么阶段开始:
功能测试完毕(手工测试)

三、web自动化测试所属分类:
1、黑盒测试(功能)-----自动化属于这个
2、白盒测试(单元)
3、灰盒测试(接口)

web自动化测试用例:跟着功能测试的用例后面加上“是否自动化”


四、安装selenium包

1、****很重要的:pip包管理工具

按Ctrl+r跑出cmd的运行窗口,然后输入pip install selenium  就会安装最新版本的selenium
安装完后可以输入pip show selenium来查看版本等信息
可以输入 pip uninstall selenium来卸载

扩展:
pip install selenium==版本号来指定安装的版本
在==号后面输入一个错误的版本号按回车,会提示所有可以安装的版本号
==号后面抄一个正确的版本就可以了

pip是python中的包管理工具(可以安装、卸载、查看python工具)-------需要先安装好python3的运行环境
必须连网才能使用Pip
pip list:查看通过pip工具安装的插件或工具


2、可以通过pycharm来安装
推荐原因:安装到当前工程环境里
操作:File--setting--Project:当前工程名称---Project Interpreter--点击+号安装相应包和版本
提示:如果使用pip install 插件名  安装过后,打开pycharm,导包操作时,提示找不到此包,那就
说明使用pip install 默认安装 的路径和当前工程所有的环境路径不是同一个环境,进行以上处理即可
安装浏览器驱动
firefox  48以上版本   需要selenium 3.x+firefox驱动(geckodriver)
下载地址:https://github.com/mozilla/geckodriver/releases/
里面有各个版本的驱动,有相对应的firefox版本

驱动和浏览器版本是对应的,下载哪一个要去官网查看
google
http://chromedriver.storage.googleapis.com/index.html


应用:
将浏览器驱动放到指定文件夹
将浏览器驱动所在目录添加到系统path环境变量中

科普path:
如果在cmd里面输入命令时提示不是内部命令,一般都是path这里没设置好
说明:指定系统搜索的目录
dos命令默认搜索顺序:1、检测是否为内部命令    2、检测是否为当前目录下可执行文件
3、检测path环境变量指定的目录
提示:
1、如果 以上搜索目录都检测不到输入的命令或或执行文件,系统会抛出不是内部或外部命令。。。
2、在web环境中,如果不将浏览器驱动添加到path中,selenium在运行的时候会提示浏览器驱动有误


# 第一个selenium案例:
# 需求:打开百度首页并停留3秒后关闭浏览器驱动
#1、导包
from selenium import webdriver
from time import sleep
#获取浏览器驱动
driver=webdriver.Chrome()
#打开url
driver.get("http://baidu.com")
#暂停3秒
sleep(3)
#关闭浏览器驱动
driver.quit();


五、元素定位
工具:
Firefox        Firebug(F12获取)
谷歌    F12

元素定位依赖于:1、标签名 2、属性 3、层级 4、路径
定位方式:
元素属性
1、id
2、name
3、class_name
标签
4、tag_name
a标签 (可点击文本)
5、link_text(定位超连接 a标签)
6、partial_link_text(同上,可模糊)

7、XPath(基于元素路径)
8、CSS(元素选择器)


1、id定位
element=driver.find_element_by_id(id)

例子:
from selenium import webdriver
from time import sleep; #如果直接import time会把time的所有方法都导进来,不好
                        #快速导包:输入sleep后,按ctrl+alt+空格,然后选中想要的方法后回车,就直接导包成功了
driver=webdriver.Firefox();
#如果输入的是本地文件的url,例如E:\aaa\bbb\ccc\index.html
#里面有\反斜杆,其在python中是转义字符。此时可以写成url=r"E:\aaa\bbb\ccc\index.html"  r是装饰的字符串,如果字符串中有转义字符,不进行转义使用
#也可以不用r,直接写2个\\      E:\\aaa\\bbb\\ccc\\index.html
#使用本地浏览器模式 前缀必须添加file:///
#url="file:///E:/aaa/bbb/ccc/index.html"
url='http://www.baidu.com'
driver.get(url);
driver.maximize_window();
driver.find_element_by_class_name('engine-key-wrapper').send_keys('python');
driver.find_element_by_id('search-submit').click();
sleep(3)
driver.quit();


2、name定位
element=driver.find_element_by_name(name)  #name是用引号括起来的

3、class_name定位
element=driver.find_element_by_class_name(classname)

4、tag_name定位    标签即是元素  例如input  div a 等      很少用
element=driver.find_element_by_tag_name(tag_name)

如果找到多个同样的的标签,默认返回第一个

5、link_text定位和partial_link_text定位        封装的时候一般使用后者
都是定位a标签,但是partial_link_text可以模糊定位,即不用全部匹配,前者需要
element=driver.find_element_by_link_text(text)        # 引号内部的文字如果不是全部匹配则会报错
element=driver.find_element_by_partial_link_text('123').click();  #只有是包含123的字符的就可以click了。但是最好使用能代表唯一性的词,如果有多个,还是返回第一个


7、XPath     XML Path

element=driver.find_element_by_xpath(xpath)

定位策略(方式)
1)路径     看7.1路径定位
2)利用元素属性     比如//input[@id='password']    这是相对路径定位时,可能会找到多个input,此时考虑结合属性来定位,需要[@]
3)属性加上逻辑           比如//input[@class='login' and @name='user-test']         当使用一个属性class找到的还不是唯一时,需要使用and 来多加一个属性来找
4)层级与属性结合       比如://p[@id='login-T2']/input[@name='user' and @type='text']    当使用2个属性都找不到唯一时,给它加上父层级路径       第一次要//,第二级就只要/
                                              一般使用子级的属性找不到唯一,就加上父级的属性,然后把子级的属性去掉:
                                              //p[@id='login-T2']/input

7.1 路径定位(绝对 、相对路径)
绝对路径:从最外层元素到指定元素之间所有经过元素层级的路径
1)绝对路径以/html根节点开始,使用/来隔元素层级:
/html/body/div/fieldset/p[1]/input
2)绝对路径对页面结构要求比较严格,不建议使用

相对路径:匹配任意层级的元素,不限制元素的位置
1)相对路径以//开始
2)格式://input       或者  //*

7.2  XPath延申
//*[text()='xxx']           文本内容是xxx的元素
driver.find_element_by_xpath("//*[text()='开课/合作']").click();

//*[contains(@attribute,'xxx')]      属性中含有xxx的元素        移动端全是这样子写,重要
driver.find_element_by_xpath("//*[contains(@id,'keyword')]").send_keys('python');


//*[starts-with(@attribute,'xxx')]      属性以xxx开头的元素
driver.find_element_by_xpath("//*[starts-with(@id,'js_sea')]").click();



8、CSS定位方法
element=driver.find_element_by_css_selector(css_selector)
8.1、常用策略
1) id选择器        #id
2)class选择器   .class
3)元素选择器    input       直接写属性名称
4)属性选择器    [name='passwordA']
5)层级选择器   p>input      或者 p input          两者的区别:p一定是input的直接上级,后者则不一定是直接上级,不管中间隔多少级
                          p#idname>input       id名称为idname的p下面的input


driver.find_element_by_css_selector('.header-index-text p').click();      层级与元素选择器、class选择器结合

使用CSS 元素选择器 定位span(这个是任意标签名)标签获取文本值
下面就是打印出class=mnav的标签的文本              
ps=driver.find_element_by_css_selector('.mnav').text;
print('文本值是:',ps)       #文本值是: 抗击肺炎


8.2  CSS 延申
input[type^='p']   type属性以p字母开头的元素
input[type$='d']   type属性以d字母结束的元素
input[type*='w']    type属性包含d字母的元素


8.3 定位一组元素
dirver.find_elements_by_id

#以下是定位到一组以mnav为class名的元素列表,打印其长度为7
elements=driver.find_elements_by_css_selector('.mnav')
print(len(elements));
#那么要对其中一个进行操作,需要以下标的方式
elements[1].click();

#通过遍历来操作,假设是文本框,全部输入同一个文字信息
for el in elements:
    el.send_keys('我是同一个文字信息');


8.4  find_element方法封装
 # 需求:使用driver_element方法来定位
 driver.find_element(By.ID,'kw').send_keys('python');
 driver.find_element(By.CSS_SELECTOR,'#su').click();

By类:需要导包位置   输入By之后,按ctrl+alt+空格可以快速导包
 from selenium.webdriver.common.by import By

六、元素操作方法
1、目的:
1)需要让脚本模拟用户级指定元素插入值
2)模拟人为删除元素的内容
3)模拟点击操作

2、方法
1) click()    单击元素
2)send_keys(value)  模拟输入
3)clear() 清除文本

3、案例
需求:输入文本后清空、修改再提交
driver.find_element_by_css_selector('#kw').send_keys("python");
sleep(3);
driver.find_element_by_css_selector('#kw').clear();
driver.find_element_by_css_selector('#kw').send_keys("java");
sleep(3)
driver.find_element_by_css_selector('#su').click();

七、浏览器操作方法
最大化  maximize_window()
设置大小 set_window_size(width,height)
窗口位置  set _window_position(x,y)
后退   back()
前进   forward()
刷新 F5  refresh()      
关闭当前窗口 close()
关闭由driver启动的所有窗口  quit()
获取页面的title  title
获取当前的url  current_url
提示:最后两个,没有括号,应用场景:一般为判读上步操作是否成功

实例:

from selenium import webdriver;
from time import sleep;
driver=webdriver.Firefox();
# driver._is_remote = False
url='http://www.baidu.com';
driver.get(url);
#将浏览器最大化
driver.maximize_window();
sleep(5)
# 设置固定大小300、200
# driver.set_window_size(300,200);
# sleep(2)
# 移动浏览器窗口位置 x:320   y:150
# driver.set_window_position(320,150)
# sleep(2)

# 最大化
# driver.maximize_window();
# 点击访问另一个访问,演示后退功能
driver.find_element_by_name('tj_trnews').click();
# driver.find_element_by_css_selector('').click();
sleep(2)
driver.back();
sleep(2)
# 然后演示前进功能
#driver.forward();
#sleep(3);
driver.find_element_by_css_selector('#kw').send_keys('python');
sleep(3);
#刷新
driver.refresh();
#获取当前页面title
title=driver.title;
print('当前页面的title是:',title);
sleep(3)
#获取当前页面url
current_url=driver.current_url;
print('当前页面的url是:',current_url);
sleep(3);
driver.find_element_by_partial_link_text('关于百度').click();
sleep(3);
driver.close();
sleep(3);
driver.quit();


八、获取元素信息
1、元素的文本
2、元素的属性值    作用:检测属性值的正确与否、  利用属性值判断是否我们要的元素
3、判断元素是否可见状态

常用方法
1、size  返回元素大小
2、text  获取元素的文本          size和text是属性,调用时无括号:xxx.size
3、get_attribute('xxx') 获取属性值,传递的参数为元素的属性名
4、is_displayed()    判断元素是否可见
5、is_enabled()    判断元素是否可用
6、is_selected()     判断元素是否选中,用来检查复选框或单选按钮是否被选中


实例:
from selenium import webdriver;
from time import sleep;
driver=webdriver.Firefox();
# driver._is_remote = False
url='http://www.baidu.com';
driver.get(url);
#将浏览器最大化
driver.maximize_window();
#获取输入框的大小
# 获取页面上某个超连接的文本内容
# 获取页面上第一个超链接的地址
# 判断页面中的某标签是否可见
# 判断页面中的某按钮是否可用
# 判断页面f中的复选框是否为选中的状态
size=driver.find_element_by_css_selector('#kw').size;
print('输入框的大小是:',size);
texts=driver.find_element_by_css_selector('#virus-2020').text;
print('页面中id为virus-2020的元素的文字为:',texts);
att=driver.find_element_by_css_selector('a').get_attribute('text')
print('页面中id是kw的属性是:',att);
display=driver.find_element_by_css_selector('#su').is_displayed();
print('搜索按钮是否可见:',display);#结果是True或者False
enabled=driver.find_element_by_css_selector('#su').is_enabled();
print('搜索按钮是否可用:',enabled);#结果是True或者False
# selected=driver.find_element_by_css_selector('xxx').is_selected();
# print('xxx是否被选中',selected);
driver.quit();



结果是:
输入框的大小是: {'width': 548.0, 'height': 44.0}
页面中id为virus-2020的元素的文字为: 抗击肺炎
页面中id是kw的属性是: 百度首页
搜索按钮是否可见: True
搜索按钮是否可用: True

九、键盘和鼠标操作
鼠标:点击、右击、双击、悬停、拖拽
1、鼠标操作的方法
说明:在Selenium中将操作鼠标的方法封闭在ActionChains 类中
实例化对象:action=ActionChains(driver)
方法:
1) context_click(element)  右击
2)   double_click(element)   双击
3)drag_and_drop(source,target) 拖动
4)  move_to_element(element)  悬停
5)  perform()   执行                 ------>此方法用来执行以上所有鼠标操作

提示:selenium框架中虽然提供了右击鼠标的方法,但是没有提供选择右击菜单方法,可以通过发送快捷键的方式解决(但是chrome浏览器不支持这种方法)


实例:
from selenium import webdriver;
from time import sleep;

from selenium.webdriver import ActionChains

driver=webdriver.Chrome();
# driver._is_remote = False
url='http://www.baidu.com';
driver.get(url);
#将浏览器最大化
driver.maximize_window();
#首先实例化并获取ActionChains类           先输入ActionChains然后按ctrl+alt+空格键导包from selenium.webdriver import ActionChains
action=ActionChains(driver);
#在文本框里右击,  预期:出现“粘贴”功能
# action.context_click(driver.find_element_by_css_selector('#kw')).perform();
# sleep(5)


#先手动复制一个文本,然后使用发送右键快捷键的方式粘贴一个内容
serchtext=driver.find_element_by_css_selector('#kw');
action.context_click(serchtext).perform();
serchtext.send_keys('p');#p是右键菜单粘粘的快键键
#在文本框里输入文字后双击   预期:全选中
ele=driver.find_element_by_css_selector('#kw');
action.context_click(ele).perform();
#移动到超连接后悬停     预期:超连接文字变色
action.move_to_element(driver.find_element_by_css_selector('#virus-2020')).perform();
sleep(5);
action.double_click(driver.find_element_by_css_selector('#kw').send_keys('python')).perform();
sleep(5)
driver.find_element_by_css_selector('#virus-2020').click();
sleep(5)

#拖拽
sourse=driver.find_element_by_css_selector('#div1');#源元素;假设是一个红色盒子
target=driver.find_element_by_css_selector('#div2');#目标元素
action.drag_and_drop(sourse,target).perform();
#拖拽扩展
action.drag_and_drop_by_offset(sourse,xoffset=360,yoffset=180);#拖动往x轴360个像素,y轴180个像素

driver.quit();


2、键盘操作
#Selenium中把键盘按钮都封装在keys类中

1)删除键(BackSpace)      send_keys(Keys.BACK_SPACE)
2)空格键(Space)    send_keys(Keys.SPACE)
3)制表键(Tab)        send_keys(Keys.TAB)
4)回退键(Esc)        send_keys(Keys.ESCAPE)
5)  回车键(Enter)        send_keys(Keys.ENTER)
6)全选(Ctrl+A)        send_keys(Keys.CONTROL,'a')
7)复制(Ctrl+C)        send_keys(Keys.CONTROL,'c')

实例:
from selenium import webdriver;
from time import sleep;
from selenium.webdriver.common.keys import Keys;
driver=webdriver.Chrome();
url='http://www.baidu.com'
driver.get(url);
"""
需求
输入框里输入python1
删除1
全选 复制 python
粘贴
"""
serchtext=driver.find_element_by_css_selector('#kw');
serchtext.send_keys('python1');
sleep(3);
serchtext.send_keys(Keys.BACK_SPACE);
sleep(3)
serchtext.send_keys(Keys.CONTROL,'a');
serchtext.send_keys(Keys.CONTROL,'c')
sleep(3)
serchtext.send_keys(Keys.CONTROL,'v')
sleep(3)
driver.find_element_by_css_selector('#su').click();
sleep(5)
driver.quit();

十一、元素等待
由于电脑配置或网络原因,在查找元素时,元素代码未在第一时间内被 加载出来,而抛出未找到元素异常
概念:元素在第一次未找到时,元素等待设置的时长被激活,如果在设置的有效时长内找到元素,继续执行代码,如果超出设置的时间未找到元素,抛出未找到元素异常
分类:
隐式等待:定位元素时,如果能定位到元素则直接返回元素,不触发等待;如果不能定位到该元素,则间隔一段时间后再去定位元素;如果在达到最大时长还没找到指定元素,则抛出不存在的异常 NoSuchElementException
现实方式:
driver.implicitly_wait(timeout)            单位是秒。实际工作中一般设置为30秒,但是如果是新浪网实种网页,直接设置80秒
实例:
from selenium import webdriver;
from time import sleep;
from selenium.webdriver.common.keys import Keys;
driver=webdriver.Chrome();
#设置隐式等待10秒。
# 特色:1、设置在全局中,针对所有元素有效 2、一般情况下为前置必写代码(1)获取浏览器驱动对象;2)最大化浏览器;3)设置等待)
driver.implicitly_wait(10);
url='http://www.baidu.com'
driver.get(url);
"""
需求
隐式等待使用
给一个错误的Id,不能知道,如果直接抛出异常,说明等待失败,如果在设置的时间外抛出异常则说明等待设置成功
"""
serchtext=driver.find_element_by_css_selector('#kw');
serchtext.send_keys('python1');
sleep(3);
driver.find_element_by_css_selector('#su').click();
sleep(5)
driver.quit();


显式等待:
定位元素时,如果能定位到元素则直接返回元素,不触发等待;如果不能定位到该元素,则间隔一段时间后再去定位元素;如果在达到最大时长还没找到指定元素,则抛出异常 TimeoutException
在Selenium中把显式等待的相关方法封装在WebDriverWait 类中

现实方式:
1、导包  等待类
from selenium.webdriver.support.wait import WebDriverWait;
2、实例化
WebDriverWait(driver,timeout=30,poll_frequency=0.5)
1)driver:浏览器驱动对象
2)timeout:超时时长,单位:秒
3)poll_frequency:检测间隔时间:默认为0.5秒,可以改为任意秒
3、调用方法
until(method):直到。。。时
1)method:函数名称,该函数用来实现对元素的定位
2)一般使用匿名函数来实现:lanbda x:x.find_element_by_id('kw')
4、element=WebDriverWait(driver,10,1).until(lambda x:x.find_element_by_id('kw'))
#x:x为driver,它是webDriverWait类将传入的driver赋值给类self._driver,until方法调用了self._driver;
提示:WebDriverWait(driver,timeout=30,poll_frequency=0.5).until(lambda x:x.find_element_by_id('kws'))返回一个元素
实例:
from selenium import webdriver;
from time import sleep;
from selenium.webdriver.common.keys import Keys;
driver=webdriver.Chrome();
#导显式等待的包
from selenium.webdriver.support.wait import WebDriverWait;
url='http://www.baidu.com'
driver.get(url);
#实例化WebDriverWait()并调用
#注意:调用until方法返回的一定是一个元素
el=WebDriverWait(driver,timeout=30,poll_frequency=0.5).until(lambda x:x.find_element_by_id('kws'));
el.send_keys('python');#注意:此时el还不是元素,只有代码运行起来的时候才是元素,所以写el.不会弹出send_keys();
sleep(3)
"""
需求
显式等待使用
给一个错误的Id,不能知道,如果直接抛出异常,说明等待失败,如果在设置的时间外抛出异常则说明等待设置成功
"""

driver.find_element_by_css_selector('#su').click();
sleep(5)
driver.quit();

显式等待和隐式等待的区别:
1、显式等待:针对单个元素生效
2、隐式等待:针对全局元素生效

十二、扩展:上传文件

from selenium import webdriver;
from time import sleep;
from selenium.webdriver.common.keys import Keys;
driver=webdriver.Chrome();
url='http://www.baidu.com'
driver.get(url);
"""
需求:点击上传文件按钮,可以选择文件上传的
错误思路:
假设【浏览】按钮的name是upfile
driver.find_element_by_css_selector("[name='upfile']").click()
这种思路,点开浏览按钮后弹出的窗口不是浏览器了,无法操作上传文件
"""
#正确的实现方式:使用send_keys('文件路径及文件名')
driver.find_element_by_css_selector("[name='upfile']").send_keys('D:\hello123.txt');#这样子就能上传成功了
sleep(5)
driver.quit();



十三、使用CSS定位下拉框  select标签   option
实例:
方式一:
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='https://mail.163.com/register/index.htm?from=163mail';
driver.get(url);
driver.maximize_window();
driver.implicitly_wait(30);
"""
需求:163邮箱注册页面,下拉选择框默认选中163.com
暂停2秒
1、定位 126.com 然后暂停2秒
2、定位yeah.net
"""
driver.find_element_by_css_selector('#username').send_keys('wushuirong_123');
driver.find_element_by_css_selector('.domain').click();
sleep(2);
driver.find_element_by_css_selector("[data-value='126.com']").click();
sleep(2);
driver.find_element_by_css_selector('.domain').click();
driver.find_element_by_css_selector("[data-value='yeah.net']").click();
sleep(2)
driver.quit();

方式二、使用Select方法
步骤:
1、导包 Select类  from selenium.webdriver.support.select import Select;
2、获取Select对象
     匿名:Select(element).select_by_index()   #通过下标
     实名:select=Select(element)
             调用:select.select_by_index();
注意:
1、Select类是通过select标签来控制其下的option元素
2、element:只能是select标签
3、实例化select时,需要的参数为select标签元素
4、调用Select类下面的方法,是通过索引、value值或显示文本去控制,而不需要click()事件。

element:标签对应的元素,通过元素定位方式获取,例如:
driver.find_element_by_id('selectA')

操作方式:
1、select_by_index(index)              根据option索引来定位,从0开始
2、select_by_value(value)            根据option属性value值定位
3、select_by_visible_text(text)       根据option显示文本来定位

实例:
from selenium import webdriver;
from time import sleep;
from selenium.webdriver.support.select import Select;
driver=webdriver.Chrome();
url='暂时没有实例连接';
driver.get(url);
driver.maximize_window();
driver.implicitly_wait(30);
"""
假设代码是这样子的:

   北京
   上海
   广州
   重庆

需求是:
暂停2秒
1、定位 上海
2、暂停2秒
3、定位广州
"""
sleep(2)
#通过下标形式访问
Select(driver.find_element_by_css_selector('#selectA')).select_by_index(1);
sleep(2)
Select(driver.find_element_by_css_selector('#selectA')).select_by_index(2);
sleep(2)
#通过value值形式访问
Select(driver.find_element_by_css_selector('#selectA')).select_by_value('sh');
sleep(2)
Select(driver.find_element_by_css_selector('#selectA')).select_by_value('gz');
#通过文本值形式访问
Select(driver.find_element_by_css_selector('#selectA')).select_by_visible_text('上海');
sleep(2)
Select(driver.find_element_by_css_selector('#selectA')).select_by_visible_text('广州');
driver.quit();

十四、弹出框
1、alert      警告
2、confirm  确认
3、prompt  提示

弹出框如果不关闭会使得后面的操作不生效
处理方法:Selenium中对处理弹出框的操作,有专用的处理方法,并且处理的方法是同一个,即alert\confirm\prompt均使用以下方法

1、获取弹出框对象:
alert=driver.switch_to.alert
2、调用
alert.text       #返回alert/confirm/prompt中的文字信息
alert.accept()   #接受对话框选选
alert.dismiss()  # 取消圣诞框选项

提示:无论以上哪个对话框,都可以使用取消,同意,因为调用的是后台的事件,跟页面显示的按钮数量无关。
注意:
1、driver.switch.to.alert   方法适合以上三种对话框,调用时没有括号。
2、获取文本的方法,调用时没有括号,如alert.text
3、在项目中不是所有的小窗口都是以上三种对话框
实例:
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='F:/python/hmtlfile/alert.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、点击alert按钮
2、关闭警告框
3、输入用户名admin
"""
# driver.find_element_by_css_selector("[value='alert警告框']").click();
#driver.find_element_by_css_selector("[value='confirm确认框']").click();
driver.find_element_by_css_selector("[value='prompt提示框']").click();
sleep(3)
#必须切换到弹出框并将其关闭
alert=driver.switch_to.alert;
print('警告框文本是:',alert.text);  
alert.accept();
#alert.dismiss();  #取消
alert.
driver.find_element_by_css_selector('.user').send_keys('admin');
sleep(3);
driver.quit();

十五、滚动条操作
1、在HTML页面中,由于前端技术框架的原因,页面元素为动态显示,元素根据滚动条的下拉而被加载
2、页面注册同意条款,需要滚动条到最底层,才能点击同意

实现方式:
selenium中并没有直接提供操作滚动条的方法,但是可以通过js脚本来达到,例如:
1、设置JavaScript脚本控制滚动条
js='window.scrollTo(0,1000)';
2、然后设用js方法
driver.execute_script(js);

实例:
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='F:/python/hmtlfile/alert.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、打开网页,停留2秒
2、拉到最底下
"""
js='window.scrollTo(0,1000)';   #这个0是左边距,即水平滚动条,1000是上边距,即垂直滚动条,一般设到1000像素都会拉到最底下了,如果页面特别长则需要设得大一点,10000都行
driver.execute_script(js);
sleep(3)
driver.quit();


十六、frame切换、多窗口切换(特别重要,每个项目都要用到)
1、frame切换
frame:HTML页面中的一种框架,主要作用是在当前页面中指定区域显示另一个页面的元素:
形式一:[了解]

    
     

形式二:


2、切换方法
1)drivier.switch_to.frame(frame_reference)            ---->切换到指定frame的方法
    frame_reference:   可以为frame框架的name、id或者定位到frame元素
2)driver.switch_to.default_content();    -->切换回默认页面

在frame中操作其他页面,必须先回到默认页面,才能进行下一步操作

实例:
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='F:/python/hmtlfile/iframe.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、在主页面输入用户名
2、在frame1页面中输入用户名
3、在frame2页面中输入用户名
"""
#在主页面输入用户名
driver.find_element_by_css_selector(".names").send_keys('admin');
sleep(2)
#切换到第一个frame并输入用户名
driver.switch_to.frame('frame1');          #这个参数可以是name\id,也可以是iframe元素,例如:
#driver.switch_to.frame(driver.find_element_by_css_selector("[name='frame2']"));
driver.find_element_by_css_selector('.user').send_keys('我是frame1');
sleep(2)
#需要返回主页面才能切换到另一个frame
driver.switch_to.default_content();
#切换到第二个frame
driver.switch_to.frame('frame2');
driver.find_element_by_css_selector('#username').send_keys('我是frame2');
sleep(3)
driver.quit();

2、多窗口切换
在Selenium中封装了获取当前窗口句柄、获取所有窗口句柄和切换到指定句柄窗口的方法:
句柄:handle,窗口的唯一识别码
方法:
1)driver.current_window_handle   --->获取当前窗口句柄
2)driver.window_handles        --->获取 所有窗口句柄
3)driver.switch_to.window(handle)  --->切换指定句柄窗口

步骤:
1、获取当前窗口句柄
2、点击连接,启动另一个窗口
3、获取当前所有窗口句
4、遍历所有窗口句柄
        5、判断当前遍历的窗口句柄不等于当前窗口句柄
        6、切换
7、返回主窗口
8、再次进入别的窗口

实例
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='F:/python/hmtlfile/windows.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、在主页面输入用户名
2、在新窗口页面1中输入用户名
3、在新窗口页面2中输入用户名
"""
#获取当前窗口句柄     --->目的:判断只要不是当前主窗口句柄,就一定是新开的窗口句柄
curren_handle=driver.current_window_handle;
#在主页面输入用户名
driver.find_element_by_css_selector(".names").send_keys('admin');
sleep(2)
driver.find_element_by_partial_link_text('弹窗页面').click();
#获取所有窗口句柄     这句话的位置不能变
handles=driver.window_handles;
#判断  不是 当前窗口句柄
for h in handles:   #  h是随便起的变量,表示句柄,该句柄如果包含在所有句柄(handles)中
    if h !=curren_handle:   #如果不是当前句柄,则:
        #切换
        driver.switch_to.window(h);

#切换到第二个窗口并输入用户名
driver.find_element_by_css_selector('.user').send_keys('我是frame1');
sleep(3)
#driver.close();
driver.switch_to.window(curren_handle);
driver.find_element_by_css_selector(".names").send_keys('admin1');
driver.find_element_by_partial_link_text('登录注册页面').click();
handles=driver.window_handles;
for h in handles:   #  h是随便起的变量,表示句柄,该句柄如果包含在所有句柄(handles)中
    if h !=curren_handle:   #如果不是当前句柄,则:
        #切换
        driver.switch_to.window(h);

driver.find_element_by_css_selector('#username').send_keys('我是第三个窗口的')
sleep(3)
driver.quit();



切换窗口的方法函数封装起来,要用的时候调用的版本:

from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
url='F:/python/hmtlfile/windows.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、在主页面输入用户名
2、在新窗口页面1中输入用户名
3、在新窗口页面2中输入用户名
"""
#获取当前窗口句柄     --->目的:判断只要不是当前主窗口句柄,就一定是新开的窗口句柄
curren_handle = driver.current_window_handle;
def handless():
    handles = driver.window_handles; #获取所有窗口句柄     这句话的位置不能变
    # 判断  不是 当前窗口句柄
    for h in handles:  # h是随便起的变量,表示句柄,该句柄如果包含在所有句柄(handles)中
        if h != curren_handle:  # 如果不是当前句柄,则:
            # 切换
            driver.switch_to.window(h);

#在主页面输入用户名
driver.find_element_by_css_selector(".names").send_keys('admin');
sleep(2)
driver.find_element_by_partial_link_text('弹窗页面').click();
handless();

#切换到第二个窗口并输入用户名
driver.find_element_by_css_selector('.user').send_keys('我是frame1');
sleep(3)
#切回主窗口
driver.switch_to.window(curren_handle);
driver.find_element_by_css_selector(".names").send_keys('admin1');
driver.find_element_by_partial_link_text('登录注册页面').click();
#再次调用函数
handless();
driver.find_element_by_css_selector('#username').send_keys('我是第三个窗口的')
sleep(3)
driver.quit();


十七、  截屏
如果自动化测试脚本运行时出现了异常,选择截屏保存当时的信息
因为自动化脚本是由程序去执行的,因此有时候打印的错误信息并不十分明确,如果在执行出错的时候
对当前窗口截图保存,那么通过图片就可以非常直观地看到出错的原因

方法:
driver.get_screenshot_as_file(imgpath)
          imgpath:图片保存路径

实例:
import time;
from selenium import webdriver;
from time import sleep;

driver=webdriver.Chrome();
url='F:/python/hmtlfile/windows.html';
driver.get(url);
driver.maximize_window();
"""
需求:
1、在页面输入用户名
2、截屏保存在当前路径下(扩展:保存到上一级的image目录下、使用时间戳来动态保存图片)
"""
driver.find_element_by_css_selector('.names').send_keys('admin');
#driver.get_screenshot_as_file('./jiepin.png');
#driver.get_screenshot_as_file('../image/jiepin.png');
driver.get_screenshot_as_file('../image/%s.png'%(time.strftime('%Y_%m_%d %H_%M_%S')));#想要使用time,要先导入time包。ctrl+alt+空格导包。
#strftime是将时间转为字符串
sleep(3);
driver.quit();


十八、验证码处理
验证码是一种随机生成的信息(数字、字母、汉字、图片、算术题等)为了防止恶意的请求行为,增加应用的安全性

处理方式:Selenium中并没有对验证码处理的方法,只能根据项目情况使用以下方式
1、去掉验证码(仅能够在测试环境下使用)
2、设置万能验证码(生产环境和测试环境使用):例如在代码中加入生成的验证码后面加一句“or  888888"之类的,设置一下复杂一点的验证码并且在脚本中提交的一直是这个验证码
3、验证码识别技术(通过python-tesseract来识别图片类型验证码,但成功率很低)
4、记录cookie(通过记录cookie进行跳过登录 :这是最靠谱的一种办法,项目中建议使用这种方法

十九、Cookie
1、说明:
Cookie是由Web服务器生成,并且保存在用户浏览器上的小文本文件,它可以包含用户相关的信息。
数据格式:键值对组成
产生:客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie数据
使用:当浏览器再次请求该网站时,浏览器把请求的数据和Cookie数据一同提交给服务器,服务器检查 该
Cookie,以此来辨认用户状态

2、应用场景:


3、Selenium操作cookie
1)get_cookie(name)           --->获取指定cookie        括号里的name是cookie的名称
2)get_cookies()                  ---->获取本网站所有本地cookies
3)add_cookie(cookie_dict)   ---->添加cookie       cookie_dict:一个字典对象,必选的键包括:“name" and "value"

3.1 案例:
需求:使用cookie实现跳过登录
1)手动登录百度,获取cookie
2)使用获取到的cookie,达到登录目的,然后就可以执行登录之后的操作

import time;
from selenium import webdriver;
from time import sleep;
driver=webdriver.Chrome();
driver.maximize_window();
url='https://www.baidu.com';
driver.get(url);
driver.add_cookie({'name':'BDUSS','value':'g1a2J2N35RNENxakRQQnpiY3N1WlExNXk3WWtnNFNTcDlLSHNyYXVneTZWTnhlRVFBQUFBJCQAAAAAAAAAAAEAAACwstAKc2h1aXFpbmdmdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALrHtF66x7ReUW'})
#上面这个value值,是去firefox浏览器复制方便一点,步骤:1、打开百度,点击登录,在输入用户名密码后先清空所有cookie,然后点击【登录】按钮,然后
# 在“网络--->域名为www.baidu.com,类型为html的person文件里,找到Cookie名字为BDUSS后面的一长串内容,复制过来就行了
"""
需求:
使用cookie绕过百度登录
步骤:
1、手动登录百度网站
2、手动获取登录后的cookies 'BDUSS'
3、使用selenium内的add_cookie({'name':'BDUSS','value':'根据实际填写'})           #这个BDUSS是百度的,其他网站的不是这样
"""
sleep(3);
#需要刷新才能看到效果
driver.refresh();
sleep(5);
driver.quit();


扩展:
可以先使用这种方法来获取上面那一长串的value
cookie=driver.get_cookie('BDUSS');
print('cookie:',cookie)


另外:获取所有的cookie信息:
cookies=driver.get_cookies()



二十、UnitTest框架
framework:框架、为解决一类事情的功能集合
UnitTest框架:是python自带的一个单元测试框架,用它来做单元测试。
为什么使用UnitTest框架:
1、能够组织多个用例去执行
2、提供丰富的断言方法
3、能够生成测试报告

UnitTest核心要素
1、TestCase       测试用例
2、TestSuite    套件(批量用例)
3、TestRunner    运行(以文本形式 text TestRunner)
4、TestLoader    运行
5、Fixture    (工厂装置)两个函数,前置、后置必运行函数

1、TestCase  测试用例详解:

案例:定义一个实现加法操作的函数,并对该函数进行测试

定义测试用例:
1)、导包:import unittest
2)、定义测试类:新建测试类必须继承unittest.TestCase
3)、定义测试方法:测试方法名称必须以test开头

执行测试用例:
方式一:使用pycharm在代码上点击鼠标右键,选择使用UnitTest运行
方式二:调用unittest.main()来运行

实例:
import unittest;
"""
需求:
定义一个实现加法\减法操作的函数,并对该函数进行测试
步骤:
1)、导包:import unittest
2)、定义测试类:新建测试类必须继承unittest.TestCase
3)、定义测试方法:测试方法名称必须以test开头
"""
#编写求和函数
def add(x,y):
    return x+y;
#编写减法函数
def sub(x,y):
    return x-y;

#定义测试类并继承
class Test01(unittest.TestCase):
    #定义测试方法  注意:以test字母开头
    def test_add(self):
        #调用 要用的函数
        result=add(1,1)
        print('结果为:',result);
    #定义第二个测试方法:
    def test_sub(self):           #假设光标定在这里或上面的test_add方法后面,运行unittest,运行结果 那里是显示Test passed:1。
                                    # 如果想运行多个用例,则需要把光标定在classrg dm
        result=sub(10,5)
        print('结果为:',result);

运行结果为:
Ran 2 tests in 0.003s

OK
结果为: 2
结果为: 5


2、TestSuite  测试套件:
说明:多条用例集合在一起,就是一个TestSuite
使用:
1、实例化:suite=unittest.TestSuite()            #suite为TestSuite实例化的名称
2、添加用例:suite.addTest(ClassName("MethodName"))         #添加的用例是另外一个py文件写好的UniteTest文件里的类名和方法名
3、添加扩展:suite.addTest(unittest.makeSuite(ClassName))    #搜索指定ClassName内以test开头的方法并添加到测试套件中

实例:
"""
目标:UnitTest框架      ---TestSuite使用

步骤:
1)、导包:import unittest
2)、实例化获取 TestSuite对象
3)、调用addTest() 方法添加用例到指定套件中
"""
#导包:import unittest
import unittest;
from UnitTest.UnitTest_01 import Test01;
#实例化获取 TestSuite对象
suite=unittest.TestSuite()
#调用addTest 方法添加用例到指定套件中
#写法1:类名(”方法名“)
suite.addTest(Test01("test_add"));
suite.addTest(Test01("test_sub"));

#扩展  写法2 添加测试类中所有test开头的方法
suite.addTest(unittest.makeSuite(Test01));

#执行测试套件
runner=unittest.TextTestRunner();
runner.run(suite);


3、TextTestRunner是用来执行测试用例和测试套件的          执行后会有文本显示通过或没通过
使用:
1、先导包
2、实例化:runner=unittest.TextTestRunner();
3、执行:runner.run(suite)   #suite为测试套件名称

4、TestLoader 类
说明:用来加载TestCase到TestSuite中,即加载满足条件的油荒用例(默认是以test开头的方法,这里可以按实际情况设置以什么开头),并
          把测试用例封装成测试套件。
          使用unittest.TestLoader,通过该类下面的discover()方法自动搜索指定目录下指定开头的.py文件,
          并将查找到的测试用例组装到测试套件;
用法:
         suite=unittest.TestLoader().discover(test_dir,pattern='test*.py')
                   test_dir :指定的测试用例目录
                   pattern:为查找的.py文件的格式,默认为'test*.py'
       也可以使用unittest.defaultTestLoader 代替 unittest.TestLoader();
运行:


实例:
假设在UnitTest文件夹写UnitTest_Run_testloader.py文件来运行case文件夹下面的用例们,执行其中所有以test开头的.py文件里的用例
"""
目标:使用TestLoader()类
作用:搜索指定目录下指定开头的.py文件,在py文件中搜索test开头的测试方法,并将这些方法添加到测试套件中
需求:运行case目录下test01.py至test05.py文件

步骤:
1)、导包:import unittest
2)、调用方法
3)、执行 套件
"""
#导包:import unittest
import unittest;
#调用方法
#写法一
suite=unittest.TestLoader().discover("../case",pattern='test*.py');#如果不需要指定文件以什么开头,执行case目录下所有模块,则不需要pattern参数
#写法二、扩展,使用defaultTestLoader,不需要括号,推荐使用:
#suite=unittest.defaultTestLoader.discover('../case');         #直接调用case目录下所有模块文件
#执行测试套件
unittest.TextTestRunner().run(suite);


以上实例,假设case目录下的文件列表是这样子的:
test01.py
test02.py
test03.py
test04.py
test05.py
aaaaa.py

则根据pattern='test*.py'来筛选,以a开头的最后一个文件是不会被添加到测试套件中的,仅执行前5个文件,如果不加pattern参数,则第六个也执行

TestLoader与TestSuite的区别(它们都是测试套件,只是实现方式不同)
1、TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中的某个测试方法)
2、TestLoader是搜索指定目录下的指定开头的.py文件,并添加测试类中的所有以test开头的测试方法到测试套件中,不能指定添加测试方法;

工作中建议使用TestLoader

5、Fixture:是一个概述,对一个测试用例环境的初始化和销毁就是一个Fixture
控制级别:
1)函数级别(实例1)       setUp(self)  每一个方法之前都运行一次,执函数执行之后就会执行一次tearDown()
2)类级别(实例2)       setUpClass 只在类之前运行一次,比如执行十个用例,只打开一次浏览器和最大化浏览器和设置隐式等待,如果用函数级别的就会特别烦
            注意:类方法必须使用@classmethod修饰
3)模块级别(仅作了解,项目中很少用到):setUpModule()\tearDownModule()

提示:无论使用函数级别还是类级别,最常用的场景就是:
          初始化:
    1、获取浏览器实例化对象
    2、最大化浏览器
    3、设置隐式等待
          结束:
    关闭浏览器驱动

实例1:
"""
目标:unittest 中的fixture用法
    fixture其实就是两个函数,这2个函数可以一起使用也可以单独使用
        1、初始化函数:def setUp()
        2、结束函数:def tearDown()

需求:每个方法之前都要运行一次setUp,之后都要运行一次tearDown
"""
#导包:import unittest
import unittest;
class Test03(unittest.TestCase):    #需要继承,写在括号里
    def setUp(self):
        print("setUp被执行");
    def tearDown(self):
        print("tearDown被执行")
    def test01(self):
        print("test01被执行")
    def test02(self):
        print("test02被执行")

结果是:
setUp被执行
test01被执行
tearDown被执行
setUp被执行
test02被执行
tearDown被执行


实例2、
"""
目标:unittest 中的fixture用法
    fixture其实就是两个函数,这2个函数可以一起使用也可以单独使用
        1、初始化函数:def setUp()
        2、结束函数:def tearDown()

需求:每个方法之前都要运行一次setUp,之后都要运行一次tearDown
"""
#导包:import unittest
import unittest;
class Test03(unittest.TestCase):    #需要继承,写在括号里
    @classmethod
    def setUpClass(cls):          #执行类方法,需要使用@classmethod来修饰,否则报错
        print("setUpClass被执行");
    @classmethod
    def tearDownClass(cls):
        print("tearDownClass被执行");
    def setUp(self):
        print("setUp被执行");
    def tearDown(self):
        print("tearDown被执行")
    def test01(self):
        print("test01被执行")
    def test02(self):
        print("test02被执行")

结果是:
setUpClass被执行
setUp被执行
test01被执行
tearDown被执行
setUp被执行
test02被执行
tearDown被执行
tearDownClass被执行


二十一、断言     assert
让程序代替人为判断测试程序执行结果 是否符合预期结果的过程

自动化脚本在执行的时候一般是无人值守状态,我们不知道执行结果是否符合预期结果,所以我们需要让程序
代替人为检测程序执行的结果是否符合预期结果。这就需要使用断言。

UnitTest断言方法:(常用的)
1、assertTrue(expr,msg=None)    验证expr是true,如果是false,则fail
2、assertFalse(expr,msg=None)    验证expr是false,如果是true,则fail    
3、assertEqual(expected,actual,msg=None)    验证expected==actual,不等则fail(比较重要,需要掌握)
4、assertNotEqual(first,second,msg=None)    验证first!=second,相等则fail
5、assertIsNone(obj,msg=None)    验证obj是None,不是则fail
6、assertIsNotNone(obj,msg=None)    验证obj不是None,是则fail
7、assertIn(member,container,msg=None)    验证是否member in container(需要掌握)
8、assertNotIn(member,container,msg=None)    验证是否member not in container

实例:
"""
目标:unittest 中的常用断言
    1、assertTrue:如果结果为True通过,否则失败

"""
import unittest;
def add(x,y):
    return x+y;

class Test03(unittest.TestCase):    #需要继承,写在括号里
    def test01(self):
        result=add(4,4);
        flat=result>=8;
        self.assertTrue(flat);
        self.assertEqual("wq","wq");#应用场景,登录成功后,获取用户名称看是否等于预期的名称
        #判断后面的字符串是否包含前面的字符串
        self.assertIn('hello','helloword');
        #判断是否为None
        flag=None;
        self.assertIsNone(flag);


实例2:
"""
需求:登录百度
    1、输入:输入正确的用户名和不输入密码就点击登录
    2、断言:提示信息为“请您输入密码”
    3、要求:如果断言出错,截屏保存

"""
import unittest;
from selenium import webdriver;
from time import sleep;
import time;


#定义测试类 并继承 unittest.TestCase
class TestBaiduLogin(unittest.TestCase):
    #定义初始化方法
    def setUp(self):
        #获取浏览器驱动并打开url并最大化浏览器和设置隐式等待
        self.driver=webdriver.Chrome();
        url="https://baidu.com"
        self.driver.get(url);
        self.driver.maximize_window();
        self.driver.implicitly_wait(30);
    #定义结束方法
    def tearDown(self):
        sleep(3);
        self.driver.quit();
    #关闭浏览器驱动
    #定义登录测试方法  密码输入错误
    def test_login_error_psw(self):
        driver=self.driver;
        # 点击登录连接
        driver.find_element_by_css_selector(".s-top-login-btn").click();
        driver.find_element_by_css_selector('.tang-pass-footerBarULogin').click();


        #输入用户名
        driver.find_element_by_css_selector('.pass-text-input-userName').send_keys('shuiqingfu')
        #输入密码
        driver.find_element_by_css_selector('.pass-text-input-password').send_keys('');
        #点击登录
        driver.find_element_by_css_selector('.pass-button-submit').click();
        sleep(3)
        #获取登录后提示信息
        result=driver.find_element_by_css_selector(".pass-generalError-error").text;
        print('提示信息:',result);
        #定义预期结果
        expect_result='请您输入密码2';
        try:
            #断言
            self.assertEqual(result,expect_result)
        except AssertionError:
            # 截图   使用时间戳来动态生成图片名称,不会重复
            driver.get_screenshot_as_file("../image/{}.png".format(time.strftime("%Y_%m_%d %H_%M_%S")));
        #抛出异常
        raise

运行结果:
Tests failef :1

提示信息: 请您输入密码


Ran 1 test in 22.700s

FAILED (errors=1)

Error
Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.3.4\plugins\python-ce\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equals
    old(self, first, second, msg)
  File "C:\Users\wushuirong\AppData\Local\Programs\Python\Python35\lib\unittest\case.py", line 817, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Users\wushuirong\AppData\Local\Programs\Python\Python35\lib\unittest\case.py", line 1190, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "C:\Users\wushuirong\AppData\Local\Programs\Python\Python35\lib\unittest\case.py", line 662, in fail
    raise self.failureException(msg)
AssertionError: '请您输入密码' != '请您输入密码2'
- 请您输入密码
+ 请您输入密码2
?       +


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "F:\python\UnitTest\UnitTest_Run_assert断言实例.py", line 51, in test_login_error_psw
    self.assertEqual(result,expect_result)
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.3.4\plugins\python-ce\helpers\pycharm\teamcity\diff_tools.py", line 38, in _patched_equals
    raise error
teamcity.diff_tools.EqualsAssertionError:  :: 请您输入密码 != 请您输入密码2



扩展:
"""
目标:python自带的断言

"""
#判断2个字符串是否相等
# assert 'hello'=='hello';
#assert 'hello'=='hallo';  #错误类型:AssertionError
#第二个字符串是否包含第一个字符串
# assert 'h' in 'hello';
# assert 'ha' in 'hello';
#判断是否为True\False       1/0
# assert True;
# assert False;

二十二、参数化
根据需求动态获取参数并引用的过程
应用场景:解决相同业务逻辑,不同测试数据的问题
如何实现:
需要安装unittest扩展插件parameterized来实现
File-->Settings-->Project:python-->Project Interpreter--->点击+号:输入parameterized---->Install Package来进行安装
安装完之后在cmd里面输入pip list可以找到

import unittest
from parameterized import parameterized;
"""
目标:parameter插件应用
步骤:
1、导包   from parameterized import parameterized;
2、修饰测试函数   @parameterized.expand(列表类型数据)
3、在测试函数中使用变量接收,传递过来的值

语法:
    1、单个参数:值为列表
    2、多个参数:值为列表嵌套元组  如:[(1,2,3),(2,3,4)]

"""

class Test01(unittest.TestCase):
    #单个参数
    # @parameterized.expand(["1","2","3"])
    # def test_add_one(self,num):
    #     print("num:",num)
    #多个参数  写法一:一般调试的时候才这样子写,实际项目中,数据是分离出去的
    # data=[(1,2,3),(2,3,5)]
    # @parameterized.expand(data)
    # def test_add_more(self,a,b,result):
    #     print('{}+{}={}:'.format(a,b,result))
    #多个参数  写法二:实际工作中用这种
    #先定义一个获取数据的函数
    def get_data():
        return [(1,2,3),(3,3,6)]
    @parameterized.expand(get_data())
    def test_add_more2(self,a,b,result):
        print("{}+{}={}:".format(a,b,result));



实例:
import unittest
from parameterized import parameterized;
"""


"""
def add(x,y):
    return x+y;
def get_data():
    return [(1,2,3),(3,3,5)]       #这里的5改成6,则会通过测试,这里3+3=5则会提示出错
class Test01(unittest.TestCase):
    @parameterized.expand(get_data())
    def test_add_more2(self,a,b,expect):
        result=add(a,b);
        assert result==expect;

二十三、UnitTest的跳过
对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行
使用方法:
#直接将测试函数标记成跳过
@unittest.skip('代码未完成')
场景:一般适合功能未完成用例
#根据条件判断测试函数是否跳过
@unitest.skipIf(condition,reason)
场景:一般判断条件满足就不执行,如:达到指定版本,此功能失效
实例:
import unittest
version=20;         #这个数字如果大于25就会跳过,小于25就会执行test03
class Test01(unittest.TestCase):
    @unittest.skip("此方法功能暂未完成")
    def test01(self):
        """此方法功能暂未完成"""
    def test02(self):
        print('test02');
    @unittest.skipIf(version>25,"大于25,跳过")
    def test03(self):
        print("test03");

二十四、html报告
1、首先去下载一个插件
HTMLTestRunner下载

HTMLTestRunner下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html
2、HTMLTestRunner修改

因为这个模块原本给python2.0用的,我用的python是3.x,所以下载后需要做些修改。

下载后修改:(Ctrl+G可以跳转到指定行)

    94行引入的名称要改,从 import StringIO 改成import io。
    539行 self.outputBuffer = StringIO.StringIO() 要改成self.outputBuffer=io.StringIO()
    631行 print >>sys.stderr, ‘\nTime Elapsed: %s’ % (self.stopTime-self.startTime)
    修改为:print (sys.stderr, ‘\nTime Elapsed: %s’ %(self.stopTime-self.startTime))
    642行,if not rmap.has_key(cls): 需要换成 if not cls in rmap:
    766行的uo = o.decode(‘latin-1’),改成 uo=o
    772行,把 ue = e.decode(‘latin-1’) 直接改成 ue = e
————————————————

3、HTMLTestRunner存放路径

修改好的模块存放在…\python\tools下

4、把用例放在case文件夹下
5、建一个report文件夹存放报告
6、建立执行代码的文件:
import unittest
import time
from tools.HTMLTestRunner import HTMLTestRunner;
"""
基于unittest框架执行生成hmtl报告
操作:
1、复制HtmlTestRunner.py文件到指定目录
2、导包
3、获取报告存放文件流,并实例化HTMLTestRunner类,执行run方法

"""
#定义测试套件
suite=unittest.defaultTestLoader.discover('../case',pattern='test*.py');
#定义报告存放路径及文件名称
report_dir='../report/{}.html'.format(time.strftime("%Y_%m_%d %H_%M_%S"))
#获取报告文件流 并执行
with open(report_dir,'wb') as f:
    HTMLTestRunner(stream=f,verbosity=2,title='XX项目自动化测试报告',description='操作系统 win10').run(suite);

7、运行后在report里就有报告了


二十五、PO实践
PO:page(页面)  object(对象)
v1:不采用任何模式(线性模型)
v2:unittest
v3:业务代码和页面对象分离:
v4:实际项目中的PO模式编写:

测试用例(使用TPShop项目的登录页):
1、账号不存在
      进入登录页面
      输入一个不存在的用户名
      输入密码
      输入验证码
      点击登录按钮
      获取错误提示信息

2、密码错误
      进入登录页面
      输入用户名
      输入一个错误的密码
      输入验证码
      点击登录按钮
      获取错误提示信息


V1实例:
 #导包
from selenium import webdriver;
import time
 #获取driver对象
driver=webdriver.Chrome()
 #最大化浏览器
driver.maximize_window();
 #隐式等待
driver.implicitly_wait(3);
 #打开url
url='http://localhost/index.php/Admin/Admin/login.html'
driver.get(url)
 # 输入不存在的用户名
driver.find_element_by_name('username').send_keys('wushuirong');
 #输入密码
driver.find_element_by_name('password').send_keys('123456')
 #输入验证码
driver.find_element_by_css_selector('#vertify').send_keys('123456')
driver.find_element_by_name('submit').click();
 #获取登录后的错误提示信息
tips=driver.find_element_by_css_selector('.error').text;
print('提示信息是:',tips);
expect='用户名不存在';
try:
    assert tips==expect;
except AssertionError:
    driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));

V2实例:
#导包
import unittest;
import time;
from selenium import webdriver;
#新建测试类 并继承
class TestLogin(unittest.TestCase):
    driver=None;
    #初始化
    @classmethod
    def setUpClass(cls):
        cls.driver=webdriver.Chrome();
        cls.driver.maximize_window();
        cls.driver.implicitly_wait(3);
        url='http://localhost/index.php/Admin/Admin/login.html'
        cls.driver.get(url);
    #结束
    @classmethod
    def tearDownClass(cls):
        cls.driver.quit();
    #新建测试方法
    #用户名不存在
    def test_login_username_not_exist(self):
        # 输入不存在的用户名
        driver=self.driver;
        driver.find_element_by_name('username').send_keys('wushuirong');
        # 输入密码
        driver.find_element_by_name('password').send_keys('123456')
        # 输入验证码
        driver.find_element_by_css_selector('#vertify').send_keys('123456')   #因为没有改源代码,所以这个验证码是错的,下面的提示信息永远是验证码错误,暂时先这样
        driver.find_element_by_name('submit').click();
        # 获取登录后的错误提示信息
        tips = driver.find_element_by_css_selector('.error').text;
        print('提示信息是:', tips);
        expect = '用户名不存在';
        try:
            assert tips == expect;
        except AssertionError:
            driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));
    time.sleep(3);
    def test_login_password_error(self):
        # 输入正确的用户名
        driver = self.driver;
        driver.find_element_by_name('username').clear();
        driver.find_element_by_name('username').send_keys('admin');
        # 输入错误的密码
        driver.find_element_by_name('password').send_keys('123456')
        # 输入验证码
        driver.find_element_by_css_selector('#vertify').send_keys('123456')  # 因为没有改源代码,所以这个验证码是错的,下面的提示信息永远是验证码错误,暂时先这样
        driver.find_element_by_name('submit').click();
        # 获取登录后的错误提示信息
        tips = driver.find_element_by_css_selector('.error').text;
        print('提示信息是:', tips);
        expect = '密码错误';
        try:
            assert tips == expect;
        except AssertionError:
            driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));

V3实例:页面层和业务层分离
首先在V3文件夹里再建两个文件夹page和scripts分别装页面层和业务层的代码,然后页面层的文件名称必须以page开头,业务层的文件名称以test开头
以下是page_login.py
"""
页面对象编写技巧
类名:使用大驼峰将模块名称抄进来,有下划线就去掉下划线
方法:根据业务需求,每个操作步骤单独封装一个方法
    方法名:page_xxx
"""
from selenium import webdriver;
class PageLogin:
    def __init__(self):
        self.driver = webdriver.Chrome();
        self.driver.maximize_window();
        self.driver.implicitly_wait(3);
        url = 'http://localhost/index.php/Admin/Admin/login.html'
        self.driver.get(url);

# 页面层
    #输入用户名
    def page_input_username(self,username):
        self.driver.find_element_by_name('username').send_keys(username);
    #输入密码
    def page_input_password(self,pwd):
        self.driver.find_element_by_name('password').send_keys(pwd);
    #输入验证码
    def page_input_verify_code(self,code):
        self.driver.find_element_by_css_selector('#vertify').send_keys(code)
    #点击登录
    def page_input_submit(self):
        self.driver.find_element_by_name('submit').click();
    #获取异常提示信息
    def page_input_error(self,error):
        return self.driver.find_element_by_css_selector('.error').text;

#业务层
    #组装登录业务方法,给业务层调用
    def page_login(self,username,pwd,code):
        self.page_input_username(username);
        self.page_input_password(pwd);
        self.page_input_verify_code(code);
        self.page_input_submit();

以下是test_login.py:
# 导包
import unittest;
from v3.page.page_login import PageLogin;       #把页面层的模块和类导进来
import time;
from parameterized import parameterized;

# 新建测试类
class TestLogin(unittest.TestCase):
    # 初始化方法
    def setUp(self):
        # 获取登录页面对象
        self.login = PageLogin();

    # 结束方法
    def tearDown(self):
        self.login.driver.quit();

    # 新建测试方法
    @parameterized.expand([('wushuirong','123456','8888','账号不存在'),('admin','123455','8888','密码错误')])
    def test_login(self, username,pwd,code,expect):
        #调用测试登录方法
        self.login.page_login(username,pwd,code)
        #获取登录后的提示信息
        msg=self.login.page_input_error(expect);
        #断言
        try:
            self.assertEqual(msg,expect)
        except AssertionError:
            self.driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));



运行结果是:
Tests failed:2
AssertionError: '验证码错误!' != '账号不存在'
- 验证码错误!
+ 账号不存在

AssertionError: '验证码错误!' != '密码错误'
- 验证码错误!
+ 密码错误

因为没有改过源代码,没有设置万能验证码,所以这里的验证码一直是错的,没办法验证登录名是否存在和密码错误,
实际项目中需要设置万能验证码,或者直接用cookies来跳过登录这一步,或者直接去掉验证码。


V4实例    比V3多了基类,公共的方法适用于任何页面任何项目
建三个文件夹
base (基类):      存放page页面里一些公共的方法,给page用
例如:class Base:
    #初始化方法
    def __init__(self):
        pass
    #查找元素方法(暂时提供下面三个方法用)
    def base_find_element(self):
        pass
    #点击方法
    def base_click(self):
        pass
    #输入方法
    def base_input(self):
        pass
    #获取文件方法
    def base_get_text(self):
        pass
    #截图方法
    def base_get_image(self):
        pass

注意:
       1、以上方法,解包只需要一次,在查找元素解包(详细看代码)
       2、driver为虚拟,谁调用base时,谁传入,无需关注从哪里来
       3、loc:真正使用loc的方法只有查找元素
    
page(页面对象):一个页面封装成一个对象
         应用:继承base;(不用导包的方式)
         实现:
    1、模块名:page+实际操作模块名称        page_login.py
    2、页面对象名:以大驼峰的方法将模块名抄进来,有下划线就去掉下划线
    3、方法:涉及元素,将每个元素操作单独封装一个操作方法
    4、组装:根据需求组装以上操作步骤。

例如:
class PageLogin():
    #输入用户名
    def page_input_username(self):
        pass
    #输入密码
    def page_input_password(self):
        pass
    #输入验证码
    def page_input_verity_code(self):
        pass
    #点击【登录】
    def page_click_login_btn(self):
        pass
    #获取登录异常信息
    def page_get_err_info(self):
        pass
    #截图
    def page_get_screenshot(self):
        pass

    #组合业务方法
    def page_login(self):
        pass

然后还有一件事情就是页面配置数据,例如登录用户名、密码、验证码等信息,需要另外起一个文件存放,把一个项目里所有类似的信息存放在同一个文件里比较方便管理
就是page文件夹下面建一个__init__.py文件,即利用文件包(文件包比文件夹多一个init文件)的机制,在同一个page文件里的文件可以通过文件夹名.来获取__init__文件下的变量
实际操作看下面的“page文件夹里的详情”:

scripts(业务层):导包调用page页面
    实现:
         1、模块:test+实际操作模块名称   test_login.py
         2、测试业务名称:以大驼峰方法将模块名抄进来,有下划线就去掉下划线。TestLogin
         3、方法:
              1)初始化方法setUp()   注:在unittest框架中,不能使用def __init__()初始化方法
        #实例化页面对象
        #前置操作   如:打开登录页
              2)结束方法 tearDown()
        #关闭浏览器驱动
              3)测试方法
        #根据要操作的业务来实现

base.py详细代码:
from selenium.webdriver.support.wait import WebDriverWait
import time;

class Base:
    #初始化方法
    def __init__(self):
        # self.driver=driver;
        #临时代替driver
        self.driver=webdriver.Chrome();
        self.driver.maximize_window();
        self.driver.get("http://localhost/index.php/Admin/Admin/login.html");
    
    #查找元素方法(暂时提供下面三个方法用)
    def base_find_element(self,loc,timeout=30,poll=0.5):  #设置后面两个参数的目的是为了有些场景的等待时间可能 会是2秒或其他时长,如果不传参就默认30秒,如果传参就按实际参数来等待
        #使用显式等待找到一个元素
        return WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(lambda x:x.find_element(*loc)); #loc是位置
    #点击方法
    def base_click(self,loc):
        self.base_find_element(loc).click();
    #输入方法
    def base_input(self,loc,value):
        el=self.base_find_element(loc);
        el.clear();
        el.send_keys(value);
    #获取文件方法
    def base_get_text(self,loc):
        return self.base_find_element(loc).text;
    #截图方法
    def base_get_image(self):
        self.driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));


page文件夹里的详情:
1、__init__.py的详细代码:
from selenium.webdriver.common.by import By;
"""以下为登录页面元素的配置信息"""
#用户名
login_username=By.NAME,'username';

#密码
login_pwd=By.NAME,'password'
#验证码
login_verify_code=By.CSS_SELECTOR,'#vertify';
#登录按钮
login_btn=By.NAME,'submit';
#获取登录后的错误提示信息
login_err_info=By.CSS_SELECTOR,'.error';

"""以下为订单页面元素的配置信息"""
#暂时没有

page_login.py文件详细代码:
from v4 import page;   #在这里包入page包,就可以直接page.    点了之后就会出现刚才在init文件里写的全部变量
from v4.base.base import Base


class PageLogin(Base):   #继续Base的方法,在这里输入后按ctrl+alt+空格导包
    #输入用户名
    def page_input_username(self,username):
        self.base_input(page.login_username,username);    #这一行代码,首先self.后显示在base里设置的“输入方法  base_input",选择后
                                                         #再在括号里输入loc,即页面元素,在init文件里设置的“用户名  login_username",
                                                        #这里输入page.之后就会跳出来可以选择该变量;最后的username是参数,需要在外面存
                                                        #放的txt等文件里准备好测试数据,再传进来
    #输入密码
    def page_input_password(self,pwd):
        self.base_input(page.login_pwd,pwd)
    #输入验证码
    def page_input_verity_code(self,code):
        self.base_input(page.login_verify_code,code)
    #点击【登录】
    def page_click_login_btn(self):
        self.base_click(page.login_btn);
    #获取登录异常信息
    def page_get_err_info(self):
        return self.base_get_text(page.login_err_info); #获取一定要返回
    #截图
    def page_get_screenshot(self):
        self.base_get_image();

    #组合业务方法
    def page_login(self,username,pwd,code):
        self.page_input_username(username);
        self.page_input_password(pwd);
        self.page_input_verity_code(code);
        self.page_click_login_btn();


scripts文件夹下的test_login.py 详细代码:

#导包
import unittest;

from parameterized import parameterized

from v4.page.page_login import PageLogin;
def get_data():
    return [('wushuirong','123456','8888','账号不存在'),('admin','123455','8888','密码错误')];
#新建测试类并继承
class TestLogin(unittest.TestCase):
    #setUp
    def setUp(self):
        #实例化 获取页面对像PageLogin
        self.login=PageLogin();
    #tearDown
    def tearDown(self):
        self.login.driver.quit();
    #登录测试方法
    @parameterized.expand(get_data())
    def test_login(self,username,pwd,code,expect_result):
        #调用登录方法
        self.login.page_login(username,pwd,code);
        #获取登录提示信息
        msg=self.login.page_get_err_info();
        try:
            #断言
            self.assertEqual(msg,expect_result);
        except AssertionError:
            self.login.page_get_screenshot();
            raise;           #加上这句话,会使得如果断言有错就会抛异常,同时截图。如果
        #不加上这句话,只会截图,用例还是会pass

PS:断言出错时截图,同时抛异常,用例不是pass:加上raise

最后,在这个方法的类处定点光标,然后运行,即可以进行完整的测试。

二十六、数据驱动(重中之重)
以数据来驱动整个测试用例的执行,也就是测试数据决定测试结果
比如测试加法,测试数据是1和1,那结果就是2,如果测试数据是1和2,测试结果就是3

特点:自动化脚本写完之后基本不会再动,维护的重点在测试数据上,实现它要依赖于参数化的技术

传入数据的方式(测试数据的来源)
1)直接定义在测试脚本中(简单直观,但代码和数据未实现真正的分离,不方便后期维护)
2)从文件读取数据,如JSON、excel、xml、txt等格式文件
3)从数据库中读取数据
4)直接调用接口获取数据源
5)本地封装一些生成数据的方法

1、JSON
是JS对象表示法,基本文本,独立于语言的轻量级数据交换格式

语法规则:
- 大括号保存对象
- 中括号保存数组
- 对象数组可以互相嵌套
- 数据采用键值对表示
- 多个数据由逗号分隔

JSON值
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组
- 对象
- null

数据操作:
1、首先要导入依赖包
import json
2、python字典与JSON之间的转换
a) 把python字典类型转换为JSON字符串
实例:
"""
目标: 把python字典类型转换为JSON字符串
案例:data={"name":"张三","age":18}
操作:
1、导包
2、调用dumps()方法 将字典转换成json字符串
注意:json中还有一个方法dump()  写入json  不要写错

"""
#导包
import json
#定义字典
data={"name":"张三","age":18}
#调用dumps()方法 将字典转换成json字符串
print("转换之前的数据类型:",type(data))
d2=json.dumps(data);
print("转换之后的数据类型:",type(data))
print(d2);

结果是:
转换之前的数据类型:
转换之后的数据类型:
{"name": "\u5f20\u4e09", "age": 18}

b)将json转换成Python字典
"""将字符串转换成字典"""
#定义字符串
data='{"name":"张三","age":18}';
print("转换之前的数据类型:", type(data))
d3=json.loads(data);
print("转换之后的数据类型:", type(d3))
print(data);

结果:
转换之前的数据类型:
转换之后的数据类型:
{"name":"张三","age":18}

注意:
错误写法:data="{'name':'张三'}"
这样子写会报错,说属性名必须用双引号,因此一定要用单引号包双引号来写
3、JSON文件读写
a)读取json文件
with open('data.json',encoding='UTF-8') as f:
        data=json.load(f); #返回的数据类型为字典或列表
实例:读取的是下面例子写入的json文件
"""
目标: 读取road
案例:
之前写入的文件
data={"name":"tom","age":18}
"""
#导包
import json
#打开要读取的文件流 并调用load方法
with open("./data/write2.json",encoding="utf-8") as f:
   data=json.load(f);
   print(data);

b)写入JSON文件
param={'name':'tom', 'age':20}
with open('data2.json', 'w', encoding='UTF-8') as f:
      json.dump(param,f);   #写什么东西,往哪写  f就是上面那个文件流

"""
目标: 写入json
案例:
1、data={"name":"tom","age":18}
2、data={"name":"张三","age":18}
操作:
1、导包
2、调用dump()方法

"""
#导包
import json
#定义字典
data={"name":"tom","age":18}
data2={"name":"张三","age":18}
#获取文件流 并写入数据
with open("./data/write.json","w",encoding="utf-8") as f:
    #调用dump()方法
    json.dump(data,f);
with open("./data/write2.json","w",encoding='utf-8') as f2:
    json.dump(data2,f2,ensure_ascii=False);   #这里如果没有加最后一个ensure_ascii=False,则write2文件里的中文会以阿斯卡玛的方式显示


结果是在data文件夹里生成两个文件write.json和write2.json,内容分别是:

{"age": 18, "name": "tom"}

{"age": 18, "name": "张三"}



综合实例一:网页计算器安全
1、采用po模式 的分层思想对页面进行封装
2、编写测试脚本
3、使用参数化传入测试数据
PS:input的值不是获取text,而是获取其value属性的值

分析:
结构:
     base
    #初始化方法
    #查找元素
    #点击
    #获取value属性方法封装
    #截图
     page
    #点击数字
         for n in str(num):
                          loc=By.ID,"simple{}".format(n);
              #调用base内的方法
    #点击加号
    #点击等号
    #获取结果
    #组装业务方法
     scripts
    #初始化方法
        #获取计算页面页面对象
        #获取driver
    #结果方法
                    #关闭driver
    #调用测试方法
         #调用加法运算业务方法
         #断言
         #截图



driver封装
   类名:
    #定义类变量
    driver=None;
    #获取driver方法
    @classmethod
    def get_driver(cls):
        if cls.driver is None:
            #实例化浏览器
            #最大化
            #打开浏览器
            cls.driver.get(page.url);
        return cls.driver;
    #退出driver
    @classmethod
    def quit_driver(cls):
        if cls.driver:
            cls.driver.quit();
            #注意:此处一定要置空
            cls.driver=None;

读取json数据封装
1、读取工具封装
      #导包
      #打开json获取文件流  并调用load方法
2、组装读取出来的数据格式封装
      #预期格式:[(),()]
      #默认实际格式{"":"","":""}
       思路:
    1、新建空列表
    2、使用字典.values()方法获取所有的字典值;
    3、使用列表.append((字典.get("键名")))
    4、返回列表arrs
项目实际:
文件夹与文件层级:
网页计算器实例:
      base:
    base.py
    get_driver.py
      data:
    calc.json
      image:
      page:
    __init__.py
    page_calc.py
      scripts:
    test_calc.py
       tool:
    read_json.py

整体思路:
1、首先建好文件夹和文件名后,先写base文件里的基础方法
2、封装好get_driver
3、page文件里先把所有用例涉及的元素写好配置信息,在__init__.py文件里
4、page_calc.py写好全部操作的方法
5、tool里写好read_json.py的读取json文件的方法,以便在写test_calc.py的时候直接调用
6、写test_calc.py文件
7、准备测试用例数据

具体源码:
base.py:
import time;
from selenium.webdriver.support.wait import WebDriverWait
class Base:
    #初始化方法
    def __init__(self,driver):
        self.driver=driver;
    #查找元素
    def base_find_element(self,loc,timeout=30,poll=0.5):
        """          #输入三对双引号后按一下回车键就会显示该方法下的所有参数
        :param loc:
        :param timeout:
        :param poll:
        :return:
        """
        return WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(lambda x:x.find_element(*loc));
    #点击
    def base_click(self,loc):
        self.base_find_element(loc).click();
    #获取value属性方法封装
    def base_get_vlaue(self,loc):
        return self.base_find_element(loc).get_attribute('value');
    #截图
    def base_get_image(self):
        self.driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));

get_driver.py:
from selenium import webdriver;
from 网页计算器实例 import page;
class GetDriver:
    #设置类属性
    driver=None;
    #获取driver
    @classmethod
    def get_driver(cls):
        if cls.driver is None:
            #实例化浏览器
            cls.driver=webdriver.Chrome();
            #最大化
            cls.driver.maximize_window();
            #打开浏览器
            cls.driver.get(page.url);
        return cls.driver;
    #退出driver
    @classmethod
    def quit_driver(cls):
        if cls.driver:
            cls.driver.quit();
            #注意:此处有一个大坑,关闭后driver不置空,跑多条用例是跑不起来了,因为上面判断了if cls.driver is None,所以要加下面这句
            cls.driver=None;

__init__.py
"""以下为计算器配置数据"""
from selenium.webdriver.common.by import By
#由于数字键有一定的规律,所以暂时不用定位此键,用到的时候再考虑此键怎么解决
# calc_num=By.CSS_SELECTOR,"simple9";
#加号
calc_add=By.CSS_SELECTOR,"#simpleAdd";
#=号
calc_eq=By.CSS_SELECTOR,'#simpleEqual';
#获取结果
calc_result=By.CSS_SELECTOR,'#resultIpt';
#清屏
calc_clear=By.CSS_SELECTOR,'#simpleClearAllBtn';

"""以下为服务器域名配置地址"""
url="http://cal.apple886.com/"

page_calc.py:
from selenium.webdriver.common.by import By
from 网页计算器实例.base.base import Base;
from 网页计算器实例 import page;
class PageCalc(Base):
    #点击数字方法
    def page_click_num(self,num):
        for n in str(num):
            loc=By.ID,"simple{}".format(n);
            self.base_click(loc);
    #点击加号
    def page_click_add(self):
        self.base_click(page.calc_add)
    #点击=号
    def page_click_eq(self):
        self.base_click(page.calc_eq);
    #获取结果方法
    def page_get_value(self):
        return self.base_get_vlaue(page.calc_result);
    #点击清屏
    def page_click_clear(self):
        self.base_click(page.calc_clear);
    #截图
    def page_get_image(self):
        self.base_get_image();
    #组装加法运算方法
    def page_add(self,a,b):
        self.page_click_num(a);
        self.page_click_add();
        self.page_click_num(b);
        self.page_click_eq();

test_calc.py:
import unittest;
from parameterized import parameterized;
import time;
from 网页计算器实例.base.get_driver import GetDriver
from 网页计算器实例.page.page_calc import PageCalc
from 网页计算器实例.tool.read_json import read_json
def get_data():
    datas=read_json('calc.json');
    #新建空列表
    arrs=[]
    for data in datas.values():
        arrs.append((data['a'],data['b'],data['expect']));
    return arrs;
class TestCalc(unittest.TestCase):
    driver=None;
    #setUpClass
    @classmethod
    def setUpClass(cls):
        #获取driver
        cls.driver=GetDriver().get_driver();
        #初始化页面对象
        cls.calc=PageCalc(cls.driver);
    #tearDown
    @classmethod
    def tearDownClass(cls):
        GetDriver().quit_driver();
    #测试方法 加法
    @parameterized.expand(get_data())
    def test_add_calc(self,a,b,expect):
        #调用加法运算业务方法
        self.calc.page_add(a,b);
        time.sleep(3)
        try:
            #断言
            self.assertEqual(self.calc.page_get_value(),str(expect)); #这里如果不加str会断言出错“3”!=3
            #截图
        except:
            self.calc.page_get_image();
            raise;

read_json.py:
#导包
import json;
#调用load方法
def read_json(filename):
    filepath='../data/'+filename;
    with open(filepath,'r',encoding='utf-8') as f:
        return json.load(f);


calc.json:
{
"calc_001":{"a": 1,"b": 2,"expect": 3},
  "calc_002":{"a": 1001,"b": 2,"expect":1002},
  "calc_003":{"a": 99,"b": 2,"expect": 101}
}

以上测试用例数据,可以看到第二条的预期结果是故意写错的。因此最终运行结果是:
Tests failed:1,passed:2
002 != 1003

Expected :1003
Actual   :1002      


综合实例二:某网站的登录模块的单元测试实例
1.1 实现步骤
1、绽测试用例
2、采用PO模式的分层思想对页面进行封装
3、编写测试脚本
4、定义数据文件,实现参数化

1.2 用例设计
ID       模块    优先级    测试标题             预置条件                    步骤描述        测试数据                                  预期结果              测试结果
login_001   登录    P0    用户名错误        1、打开首页        1、输入错误用户名    1、用户名:123        提示框提示:
                         2、点击登录链接             2、输入密码    2、密码:123456        账号不存在
                            3、输入验证码    3、验证码:万能验证码
                            4、点击登录按钮
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
login_002   登录    P0    密码错误           1、打开首页        1、输入用户名    1、用户名:username             提示框提示:
                         2、点击登录链接             2、输入错误密码    2、密码:123456        密码错误
                            3、输入验证码    3、验证码:万能验证码
                            4、点击登录按钮
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
login_003   登录    P0    用户名为空        1、打开首页        1、不输入用户名    1、用户名:        提示框提示:
                         2、点击登录链接             2、输入密码    2、密码:123456        用户名不能为空
                            3、输入验证码    3、验证码:万能验证码
                            4、点击登录按钮
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
login_004   登录    P0    密码为空        1、打开首页        1、输入用户名    1、用户名:username            提示框提示:
                         2、点击登录链接             2、不输入密码    2、密码:         密码不能为空
                            3、输入验证码    3、验证码:万能验证码
                            4、点击登录按钮
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
login_005   登录    P0    验证码为空        1、打开首页        1、输入用户名    1、用户名:username            提示框提示:
                         2、点击登录链接             2、输入密码    2、密码:123456        验证码不能为空
                            3、不输入验证码    3、验证码:万能验证码
                            4、点击登录按钮
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
login_006   登录    P0    正常登录           1、打开首页        1、输入用户名    1、用户名:username    登录成功
                         2、点击登录链接             2、输入密码    2、密码:123456        
                            3、输入验证码    3、验证码:万能验证码
                            4、点击登录按钮

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

登录成功的标志:一般有"退出"这类的字眼

查找元素判断是否操作成功  思路封装
 def base_if_success(self,loc):
        try:
            self.base_find_element(loc,timeout=2)
           #找到元素   assertTrue
            return True;
        except:
            return False;

如何区分正向逆向用例
      思路:在测试数据中添加一个标识正向用例或逆向用例的标记,如True/False
      步骤:
    1、调用登录方法(此登录方法中,只有输入用户名、输入密码、输入验证码、点击登录按钮)   不要封装点击登录连接
    2、判断是否正向用例:
        判断是否有”退出“
        点击”退出“
        点击登录连接地址
         否则:
        获取异常登录 信息
        断言操作
        如果异常提示是弹框,则需要关闭弹窗



二十八、日志:
用于记录系统运行时的信息,对一个事件的记录;也称为Log
A:作用:
--调试程序
--了解系统程序运行的情况,是否正常
--系统程序运行故障分析与问题定位
--用来做用户行为分析和数据统计

B:日志级别:优先级、重要性或严重程度
DEBUG      调试级别,打印非常详细的日志信息,通常用于对代码的调试
INFO         信息级别,打印一般的日志信息,突出强调程序的运行过程
WARNING 警告级别,打印警告日志信息,表明会出现潜在错误的情形,一般不影响软件的正常使用
ERROR      错误级别,打印错误异常信息,该级别的错误可能会导致系统的一些功能无法正常使用
CRITICAL   严重错误级别,一个严重的错误,这表明系统可能无法继续运行

说明:一般建议只使用前四个级别
当为程序指定一个日志级别后,程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的。

C:日志的基本用法:
logging模块:python中有一个标准库模块logging可以直接记录日志
C1、步骤:
1、导包  如:import logging
2、调用相应的级别方法,记录日志信息  logging.debug('debug...');
C2、设置级别:
logging.basicConfig(level=logging.DEBUG)    
提示:
1、默认级别为:logging.WARNING
2、设置级别时调用的是logging文件夹下面的常量(DEBUG),而不是调用小写的方法
3、切记:设置级别后,日志信息只会记录大于等于此级别的信息;

C3、设置日志格式:    
默认的日志格式为:
日志级别:Logger名称:日志内容

自定义日志格式:
logging.basicConfig(format="%(levelname)s:%(name)s:%(message)s");

format参数可能用到的:
占位符            描述
%(name)s               Logger的名字
%(levelno)s        数字形式的日志级别
%(levelname)s        文本形式的日志级别                   ***
%(pathname)s        调用日志输出函数模块的完整路径名,可能没有
%(filename)s         调用日志输出函数的模块的文件名              ***
%(module)s         调用日志输出函数的模块名
%(funcName)s          调用日志输出函数的函数名
%(lineno)d        调用日志输出的语句所在的代码行        ***
%(created)f        当前时间,用UNIX标准的表示 时间的浮点数表示
%(relativeCreated)d     输出日志信息时,自Logger创建以来的毫秒数
%(asctime)s        字符串形式的当前时间。默认格式是“2003-07-07 16:49:45,897”   ****
%(thread)d        线程ID。可能没有
%(threadName)s        线程名。可能没有
%(process)d        线程ID。可能没有
%(message)s        用户输出的消息                ***

#设置日志格式的示例代码
fmt='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(level=logging.INFO,format=fmt);

C4、将日志信息输出到文件中:
默认情况下Python的logging模块将日志打印到了标准输出中(控制台)
将日志信息输出到文件中:
logging.basicConfig(filename='a.log');

最终代码:
#导包
import logging;
#设置日志格式
fmt='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
#设置日志级别\格式\设置日志保存到指定文件中
logging.basicConfig(level=logging.INFO,format=fmt,filename='./logs/log01.log');  #要用大写的INFO,大写的是变量,小写的是方法
#调用指定级别,输入日志信息     默认只显示warnning及以上的级别。在加入上面那句设置级别之后才会显示全部级别
logging.debug('this is a debug....')
logging.info('info....')
logging.warning("warning");
logging.error('error');
logging.critical('critical');

在logs文件夹里成生的log01.log文件内容是这样子的:
2020-05-21 14:39:36,731 INFO [root] [test01_logging01.py(:12)] - info....
2020-05-21 14:39:36,731 WARNING [root] [test01_logging01.py(:13)] - warning
2020-05-21 14:39:36,731 ERROR [root] [test01_logging01.py(:14)] - error
2020-05-21 14:39:36,731 CRITICAL [root] [test01_logging01.py(:15)] - critical

PS:如果想要把message里的内容写成中文而不显示乱码:
在logging.basicConfic这里按住basicConfic的ctr键进入其底层代码__init__文件里改一下底层代码,但是暂时不要这样子。

日志的使用实例:
step1、先在tool文件夹里加上get_log.py文件,源代码如下:
#导包
import logging;
#定义和获取Logging函数
def get_logging():
    fmt='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s';
    logging.basicConfig(level=logging.INFO,filename="../log/log01.log",format=fmt);
    return logging;

step2、在base.py文件里实例化get_logging和导包,然后在每一个操作前面或后面加上log.info(),如下面源码所示:

from selenium import webdriver;
from selenium.webdriver.support.wait import WebDriverWait
import time;
from testwo.tool.get_log import get_logging
log=get_logging();
class Base:
    #初始化方法
    def __init__(self, driver):
        log.info("初始化driver{}".format(driver));
        self.driver = driver;
    def base_find_element(self,loc,timeout=30,poll=0.5):  #设置后面两个参数的目的是为了有些场景的等待时间可能 会是2秒或其他时长,如果不传参就默认30秒,如果传参就按实际参数来等待
        log.info('正在查找元素:{}'.format(loc));
        #使用显式等待找到一个元素
        return WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(lambda x:x.find_element(*loc)); #loc是位置
    #点击方法
    def base_click(self,loc):
        log.info('正在点击元素:{}'.format(loc));
        self.base_find_element(loc).click();
    #输入方法
    def base_input(self,loc,value):
        log.info('正在给元素{}输入内容:{}'.format(loc,value));
        el=self.base_find_element(loc);
        el.clear();
        log.info('正在清空{}的值'.format(loc));
        el.send_keys(value);
        log.info('正在给元素{}输入内容'.format(loc));
    #获取文件方法
    def base_get_text(self,loc):
        log.info('正在获取元素:{}的文本'.format(loc));
        return self.base_find_element(loc).text;
    #截图方法
    def base_get_image(self):
        self.driver.get_screenshot_as_file('../image/{}.png'.format(time.strftime("%Y_%m_%d %H_%M_%S")));

    # 查找元素判断是否操作成功
    # 思路封装
    def base_if_success(self,loc):
        try:
            self.base_find_element(loc,timeout=2);
            log.info("判断元素:{} 存在".format(loc));
            return True;
        except:
            log.info("判断元素:{} 不存在".format(loc))
            return False;

step3、在test_login.py文件里导包和实例化,并在相应的地方加上log.error:

#导包
import unittest;
from parameterized import parameterized
from testwo.base.get_driver import GetDriver
from testwo.page.page_login import PageLogin
from testwo.tool.read_json import read_json
from testwo.tool.get_log import get_logging
log=get_logging();
def get_data():
    datas=read_json('login.json');
    #新建空列表
    arrs=[]
    for data in datas.values():
        arrs.append((data['username'],data['pwd'],data['code'],data['expect'],data['success']));
    return arrs;
class LestLogin(unittest.TestCase):
    login=None;
    @classmethod
    def setUpClass(cls):
        try:
            # 获取driver
            cls.driver = GetDriver().get_driver();
            # 初始化页面对象
            cls.login=PageLogin(cls.driver);
            cls.login.page_click_login_link();
        except Exception as e:
            log.error(e);
    @classmethod
    def tearDownClass(cls):
        cls.login.driver.quit();
    #登录测试方法
    @parameterized.expand(get_data())
    def test_login(self,username,pwd,code,expect_result,success):
        #调用登录方法
        self.login.page_login(username,pwd,code);
        if success:
            #判断“退出”是否存在
            try:
                self.assertTrue(self.login.page_login_success());
                #点击退出
                self.login.page_click_logout();
                try:
                    self.assertTrue(self.login.page_logout_success());
                except:
                    self.login.page_get_screenshot();
                # 点击登录连接
                self.login.page_click_login_link();
            except Exception as e:
                self.login.page_get_screenshot();
                log.error(e);
        else:
            #获取登录后提示信息
            msg=self.login.page_get_err_info();
            print('错误信息是:',msg)
            try:
                self.assertEqual(msg,expect_result);
            except Exception as e:
                self.login.page_get_screenshot();
                log.error(e);
            # except AssertionError:
            #     self.login.page_get_screenshot();
            raise;

运行结果生成的log01.log文件如下所示(部分):

在log01.log文件那里右键---show in exploer,然后再用记事本打开:(可以ctr+F查找ERROR关键字来看错误信息)

2020-05-21 16:46:54,709 INFO [root] [base.py(__init__:9)] - 初始化driver
2020-05-21 16:46:54,752 INFO [root] [base.py(base_click:17)] - 正在点击元素:('css selector', '.pull-right>a')
2020-05-21 16:46:54,775 INFO [root] [base.py(base_find_element:12)] - 正在查找元素:('css selector', '.pull-right>a')
2020-05-21 16:46:55,600 INFO [root] [base.py(base_input:21)] - 正在给元素('id', 'inputName')输入内容:shuiqingfu
2020-05-21 16:46:55,601 INFO [root] [base.py(base_find_element:12)] - 正在查找元素:('id', 'inputName')
2020-05-21 16:46:55,672 INFO [root] [base.py(base_input:24)] - 正在清空('id', 'inputName')的值
2020-05-21 16:46:55,727 INFO [root] [base.py(base_input:26)] - 正在给元素('id', 'inputName')输入内容
2020-05-21 16:46:55,731 INFO [root] [base.py(base_input:21)] - 正在给元素('name', 'password')输入内容:
2020-05-21 17:14:43,055 INFO [root] [base.py(base_find_element:12)] - 正在查找元素:('id', 'user.errors')
2020-05-21 17:14:43,566 ERROR [root] [test_login.py(test_login:59)] -  :: 验证码错误 != 账号不存在

使用日志的高级用法:
日志封装:
#定义获取日志类
    #定义类属性 logger=None
    @classmethod
    #定义获取logger日志器的类方法
        if cls.logger is None:   #判断类属性logger是否为空
            #获取日志器对象
            #设置日志器级别
            #获取 控制台处理器
            #获取文件处理器
            #获取格式器
            #将格式器添加到处理器中
            #将格式器添加到日志器中
        return 类属性 logger
    注意:1、以上条件无论是否成立,最后都会返回类属性logger
    2、当第一次调用时,条件一定成立,将类属性logger设置不为空
    3、当第二次以上调用时,永远返回第一次设置的类属性对象。

具体源码:
#导包
import logging.handlers;
class GetLogger:
    logger=None;
    @classmethod
    def get_logger(cls):
        if cls.logger is None:
            #获取日志器
            cls.logger=logging.getLogger();
            #设置日志器 级别
            cls.logger.setLevel(logging.INFO);
            #获取处理器 控制台
            sh=logging.StreamHandler();

            #获取处理器 文件--以时间分隔
            th=logging.handlers.TimedRotatingFileHandler(filename="../log/log02.log",when='midnight',interval=1,backupCount=30,encoding='utf-8');


            #设置格式器
            fmt='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s';
            fm = logging.Formatter(fmt);

            #将格式器添加到处理器 控制台
            sh.setFormatter(fm);

            #将格式器添加到  处理器 文件
            th.setFormatter(fm);
            #将处理器添加到 日志器
            cls.logger.addHandler(sh);
            cls.logger.addHandler(th);
            return cls.logger;
            #测试效果,包括中文
        if __name__=='__main__':
            logger=GetLogger().get_logger();
            logger.info("info信息被执行")
            logger.error("error信息被执行");


然后在base.py和test.login.py文件里,将:
log=get_logger()
改成:
log=GetLogger().get_logger();
并且重新导包:
from testwo.tool.logger import GetLogger;

之后运行test_login.py生成的日志就是(部分,可以正常显示中文):
2020-05-21 20:57:15,428 INFO [root] [base.py(__init__:12)] - 初始化driver
2020-05-21 20:57:15,429 INFO [root] [base.py(base_click:20)] - 正在点击元素:('css selector', '.pull-right>a')
2020-05-21 20:57:15,429 INFO [root] [base.py(base_find_element:15)] - 正在查找元素:('css selector', '.pull-right>a')


最后:项目实战了
一、自动化测试的流程
1、需求分析
2、挑选适合做自动化测试的功能
3、设计测试用例
4、搭建自动化测试环境【可选】
5、设计自动化测试项目的架构【可选】
6、编写代码
7、执行测试用例
8、生成测试报告并分析结果

实际工作中的流程:
1、将功能用例转化自动化用例(在功能用例模板后新增一列是否自动化)
2、搭建自动化测试环境(本机执行自动化的依赖环境:python\pycharm\浏览器、浏览器驱动、selenium\parameterized)
3、搭建自动化框架(PO模式+数据驱动+log+报告)   ps:unittest是一个用例管理和执行的框架
4、编写代码
5、执行用例
6、生成报告\分析log

项目需求:
登录商城、搜索商品并添加到购物车、下订单、支付整个流程
用例使用正向用例

自动化测试结构:
1、base
2、page
3、scripts
4、tool
5、data
6、log
7、image
8、report

根据用例决定base.py文件里有哪些方法:
from selenium import webdriver;
from selenium.webdriver.support.wait import WebDriverWait


class Base:
    def __init__(self,driver):
        self.driver=driver;
    #查找元素 方法 封闭
    def base_find_element(self,loc,timeout=30,poll=0.5):
        #使用显式等待查找元素
        return WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(lambda x:x.find_element(*loc));
    #点击元素
    def base_click(self,loc):
        self.base_find_element(loc).click();
    #输入元素
    def base_input(self,loc,value):
        el=self.base_find_element(loc);
        el.clear();
        el.send_keys(value);
    #获取文本信息
    def base_get_text(self,loc):
       return self.base_find_element(loc).text;
    #截图
    def base_get_screenshot(self):
        self.driver.get_screenshot_as_file("../image/{}.png".format("%Y_%m_%d %H_%M_%S"));
    #判断元素是否存在的方法
    def base_element_is_exist(self,loc):
        try:
            self.base_find_element(loc,timeout=2);
            return True;
        except:
            return False;


 后面的代码,打算把整个实例一起拿出来另外写一篇,这部份的笔记就先这样子了。 

191°|1888 人阅读|3 条评论

小窝  2020-05-15

哇塞 经验分享 置顶推首页了


林夕  2020-06-10

超赞,写的很详细~


敏敏  2020-07-06

超赞,写的很详细~收藏了

登录 后发表评论