一、Selenium介绍
1、Selenium是什么?
(1)Selenium是功能自动化测试的工具(或叫做“测试框架”)
(2)**了解:**功能测试也可以叫“UI层测试”
2、Selenium适用场景:
Web应用程序(在浏览器里运行的被测系统、B/S)
注意:不适用于客户端软件(C/S)
3、优点:
(1)开源免费
(2)支持“多”种操作系统平台:Windows、Mac、Linux等
(3)支持“多”种浏览器:Firefox、Chrome、Safari、IE\Edge等
(4)支持“多”种编程语言:Python、Java、C#、Ruby等
4、Selenium主要用途:代替人工做重复性功能测试工作。
(1)回归测试
(2)兼容性测试
(3)数据量较多测试
5、Selenium带来的好处:
提升测试效率,节约测试时间,可以实现“无人值守”的自动测试。
6、Selenium不适合:
新功能测试、缺陷返测。
说明:自动化和手工测试是相辅相成的。
7、Selenium做自动化测试的条件:
(1)需求相对稳定
(2)主流程没有太多缺陷:冒烟测试通过
(3)测试周期较长:一般半年以上的项目
8、Selenium主要实现原理:2.0版本以后使用WebDriver操作浏览器。
(1)通过驱动程序初始化来启动一个浏览器(本质建立与浏览器连接会话)。
说明:****不同的浏览器有不同的驱动程序(文件)。
(2)在会话中,自动化测试代码好比是一个客户端,调用驱动程序(好比是服务器)的HTTP接口(基于HTTP封装的WebDriver Wrie Protocal),携带的参数的格式是JSON。
(3)驱动程序(就是服务器)接收到请求,解析后,根据要求调用浏览器厂商预留的接口实现浏览器里的各种界面操作,从而实现功能自动化测试中界面操作的效果。
(4)最后,退出驱动程序(结束本次连接会话、自动关闭浏览器)
9、Selenium组件:
(1**)Selenium IDE:**初学者使用的小工具,功能简单,可以录制回放。—了解!
(2)Selenium WebDriver:功能强大,没有界面,只提供API。支持三个“多”。—重点!!!
(3)**Selenium Grid:**可以支持分布式测试,提升测试效率。–不演示。
二、Selenium IDE
1、Selenium IDE是什么?
Selenium集成开发环境,它是一个小工具,能做简单的功能自动化测试,支持录制生成脚本、运行回放脚本,有界面,可以在界面上操作完成测试。
2、说明:
Selenium IDE目前支持的浏览器有Firefox、Chrome、Edge。以浏览器插件形式存在。
3、安装Selenium IDE:是浏览器的一个插件(附加组件)
(1)离线安装:
拖拽老师下发的资料包里的selenium_ide-3.17.4-fx.xpi到Firefox窗口里,在提示上
注意:这个文件不用解压缩,直接拖拽。


4、Selenium IDE功能:
(1)录制脚本
(2)编辑脚本
(3)运行脚本(回放):执行自动测试
(4)进行断言:完成数据检查
5、Selenium IDE的录制回放的基本使用步骤:
被测系统:ECShop(前台:http://localhost/upload/index.php)
(1)用Firefox尝试打开被测系统,复制网址

(2)启动Selenium IDE

(3)点击第一个项目

(4)输入工程名称(如:pro1),点击OK

(5)粘贴被测系统网址,点击第一个按钮开始录制,就会自动启动一个Firefox浏览器窗口。

(6)在该浏览器里,按照提前设计好的测试用例文档一步一步操作,最后关闭浏览器
(7)点击Selenium IDE窗口右上角红色圆形按钮来停止录制

(8)输入测试脚本名称(如:test1),点击OK

(9)保存

(10)调整运行速度:Slow(慢速)

(11)运行调试:

三、搭建Selenium WebDriver+Python+Firefox测试环境
1、安装Python和Pycharm—略!
2、安装selenium库:这是一个第三方代码库,不是python自带的,所以需要单独安装。
pip install selenium==3.141.0
或
pip install selenium==3.141.0 -i 网址
网址:https://pypi.tuna.tsinghua.edu.cn/simple


如果本来就安装成功,再次安装会提示

输入pip show selenium

Pycharm—菜单—文件—新建项目-位置自己定义(项目名称自己定义,不要叫做selenium这样的关键字)-选择Python解释器为先前配置的解释器(确认python.exe所在的路径与pip show selenium看到的路径中lib的上一级文件夹相同)

创建–点击“此窗口”

新建“Python软件包”day01—新建python文件demo1001.py

书写脚本 from selenium import webdriver

**说明:**如果有红线,代表selenium没有安装成功,或pycharm里使用的解释器错误,解决方案是再次安装selenium库。

3、安装浏览器:如Firefox—略。
**注意:建议把Firefox安装路径在默认**路径下

4、准备浏览器对应的驱动程序:如Firefox对应的驱动程序叫做geckodriver.exe
(1)资料包里geckodriver-v0.30.0-win64.zip解压缩,得到其中的geckodriver.exe
(2)粘贴到python.exe所在的路径里。
注意:按照浏览器版本下载能兼容的驱动,一般公司里会使用最新版本浏览器搭配最新版的驱动程序使用。
5、测试整套环境:demo1001.py书写代码
启动Firefox浏览器,打开Ecshop前台前台首页,等待3秒,关闭浏览器。
from selenium import webdriver
from time import sleep#导入休眠函数
driver=webdriver.Firefox()#启动Firefox
driver.get('http://localhost/upload/index.php')#打开目标网页(如:ecshop前台首页)
sleep(3)#等待3秒
driver.quit()#关闭浏览器
四、Selenium WebDriver里代码内容和html语法
1、代码内容包括:启动浏览器、打开网页、查找到元素(定位元素)、操作元素、检查数据、关闭浏览器。
2、**定位元素:**是操作元素和检查数据的前提。
3、定位元素的原则:根据网页元素在网页源代码中的特征进行元素的查找。网页源代码是html格式的。
4、html基本介绍:html是超文本标记语言,是网页设计使用的语言。
5、html里的标记:用<>括起来的描述网页信息的基本符号。
(1)单标记:<标记名称 属性名1=属性值1 属性名2=属性值2>
或
<标记名称 属性名1=属性值1 属性名2=属性值2/>
示例:
<img src=“themes/default/images/bnt_home.gif” />
(2)双标记: <标记名称 属性名1=属性值1 属性名2=属性值2>内容</标记名称>
示例:
<a href=“article.php?id=2” >隐私保护
说明:双标记的内容里如果出现其他的标记,称为标记嵌套,里层称为“下级元素”或“子元素”,外层称为“上级元素”或“父元素”。
6、html语法里常用元素的表示方式:
(1)文本框:
<input type=“text”> —普通文本框
<input type=“password”> —密码文本框
(2)按钮:
<input type=“submit”> --提交按钮(最常见)
<input type=“reset”> --重置按钮
<input type=“file”> —文件按钮(上传按钮、浏览按钮)
—普通按钮(不常见)
—普通按钮(不常见)
(3)单选钮(单选框):一组里只能选择其中一个,互斥单选按钮是多个元素,他们都是input标记的,type属性值是radio,互斥的多个元素name属性值相同,value属性值不同,其中只有一个有checked属性的,是默认选中的。

(4)复选框:****每个都是独立的。
有checked属性的就是默认被选中的,没有这个checked属性就是默认不被选中的。

(5)下拉列表:****select代表一整个下拉列表,option代表其中的一个选项。
说明:除了传统下拉列表(select),现在有些开发人员会使用其他控件组装为下拉列表的样式。

(6)超级链接:简称为”链接“或”超链接“,用于跳转网页。
<a href=“……”>内容

(7)图片:
<img src=“xxx/xx.png”>

(8)文本域(大文本框、富文本框、多行文本框):与普通文本框的区别是能输入多行文本信息,普通文本框只能输入一行信息。

(9)表格:****table,下级是tbody,再下级是tr代表行,tr下级是td代表列。
(10)层(图层):div,代表网页中一块长方形区域,主要为了方便实现网页布局。有个别的区域浮动在上层,称为“浮层”。
(11)表单:form,也是一块区域,用来收集客户填写或选择的信息,后续可以通过脚本处理提交给后台服务器
五、Selenium WebDriver元素定位的方法介绍
1、什么是元素的定位?
查找页面上的元素,可能是一个,也可能是多个。
2、为什么要定位元素?
定位是操作和检查的前提。
3、定位什么类型的元素?
文本框、按钮、复选框、单选钮、超级链接、图片、下拉列表、文本域……
4、如何定位元素?利用网页源代码中调查的目标元素的那些信息实现定位。

5、Selenium WebDriver元素定位的方法:八大方法—重点!!!
(1)**ID定位:**用id属性值定位—通用!最建议使用!
(2)NAME定位:用name属性值定位—通用!建议使用!
(3)LINK_TEXT定位:用链接**完整文本定位链接**—不通用!建议使用!
(4)PARTIAL_LINK_TEXT定位:用链接**一部分文本定位链接****-**–不通用!建议使用!
(5)CLASS_NAME定位:用类名实现元素定位—通用!建议少用!因为类名可能重复。
(6)TAG_NAME定位:用标记名称实现定位–通用!建议少用!因为标记名称可能重复。
(7)**XPATH**定位:高级定位,用XPATH表达式实现定位—通用!功能强大!学习难度大!
(8)**CSS_SELECTOR**定位:高级定位,用CSS技术里SELECTOR实现定位-通用!功能强大!学习难度大!
6、八大定位方法有通用的语法格式:
driver.find_element(By.定位类型,‘定位数据’) --查找一个元素(常用)
driver.find_elements(By.定位类型,‘定位数据’) --查找多个元素
**前提:**比如要提前导入Selenium里提供的定位专用类By
from selenium.webdriver.common.by import By
7、元素的基本操作方法:
(1)**click**方法:无参,点击。
(2)**clear**方法:无参,清空。
(3)**send_keys**方法:有一个参数,输入数据。
六、演示前六个基础定位方法和基本操作
1、ID定位
driver.find_element(By.ID,‘id属性值’).操作方法(操作数据)
driver.find_element(By.ID,‘username’).send_keys(‘123’)#向用户名文本框里输入123
from selenium import webdriver
from time import sleep#导入休眠函数
driver=webdriver.Firefox()#启动Firefox
driver.get('http://localhost/upload/index.php')#打开目标网页(如:ecshop前台首页)
driver.find_element(By.ID,'username').send_keys('123')#向用户名文本框里输入123
sleep(3)#等待3秒
driver.quit()#关闭浏览器
2、NAME定位:
driver.find_element(By.NAME,‘name属性值’).操作方法(操作数据)

driver.find_element(By.NAME,‘extend_field1’).send_keys(‘777’)#向MSN文本框里输入777
3、LINK_TEXT定位:只能定位超级链接!!!
driver.find_element(By.LINK_TEXT,‘完整链接文本’).click()

driver.find_element(By.LINK_TEXT,‘查看购物车’).click()
特殊情况:既有文本,又有子元素,可以忽略子元素,用文本进行定位。如果没有文本,不能用此定位方法。

driver.find_element(By.LINK_TEXT,‘积分商城’).click()
4、PARTIAL_LINK_TEXT定位:与LINK_TEXT定位功能类似,只不过使用部分链接文本进行定位,一般用于文本较长的超级链接(标记名称是a)。
driver.find_element(By.PARTIAL_LINK_TEXT,‘部分链接文本’).click()

**问题:**挑选哪一部分文本进行超级链接的定位?
原则:选择这个超级链接特有的文本,另外,注意最好不要包含可能变化的数字部分。
driver.find_element(By.PARTIAL_LINK_TEXT,‘总计金额’).click()#点击文本里包含“总计金额”的超级链接。
5、CLASS_NAME定位:用类名进行元素定位,建议尽量少用!
<标记名称 class=“类名1 类名2 类名3”>
**说明:多个类名之间使用空格**分隔。
driver.find_element(By.CLASS_NAME,‘某一个类名’).操作方法(‘参数’)

driver.find_element(By.CLASS_NAME,‘bnt_blue_2’).click()#点击“查询该订单号”按钮
6、TAG_NAME定位:用标记名称定位,建议少用!
driver.find_element(By.TAG_NAME,‘标记名称’).操作方法(‘参数’)


driver.find_element(By.TAG_NAME,‘textarea’).send_keys(‘欢迎来达内’)# 输入留言内容:“欢迎来达内”
七、XPath定位-高级定位方法之一
1、XML是什么?
可扩展标记语言,它是对HTML的扩展,可以自己定义标记名称和属性名,经常被开发人员用于作为配置文件来使用。
说明:XML与HTML语法相似。
2、XPath=XML Path是在XML文档里查找信息的一种技术,可以描述一个节点或多个节点所在的路径(路径表达式)来查找这个信息。
Selenium里使用的XPath:描述HTML文档(网页)中元素所在的路径。
3、XPATH定位语法:
driver.find_element(By.XPATH,‘xpath表达式’).操作方法(‘参数’)
driver.find_element(By.XPATH,“xpath表达式”).操作方法(‘参数’)
注意:因为xpath表达式内部可能存在引号,所以python程序里的引号要与xpath表达式内部的引号不同。
4、初学者可以借助于浏览器来复制得到目标元素的xpath表达式:—了解!
以Firefox为例:目标元素源代码—右击–复制–XPath


/html/body/div[1]/div[2]/ul/li[1]/font/a[1]/img
5、Xpath表达式分类:
(1)绝对路径xpath表达式:从根节点(最顶级)开始逐层描述,以/开头。–较长,设计的层级较多,在版本更新时维护成本高,建议尽量少用!
/html/body/div[1]/div[2]/ul/li[1]/font/a[1]/img
(2)相对路径xpath表达式:不从根节点开始描述,以//开头。建议使用!因为更易维护。
//*[@id=“keyword”]
6、Xpath与元素的关系:多对多
(1)一个元素可以有多种xpath表达式
(2)一个xpath表达式也可以对应一个或多个元素
7、借助Selenium IDE(是Firefox的一个插件)也可以获得元素的Xpath表达式(一般都会有相对路径Xpath):
(1)录制目标元素的操作步骤(可以是点击、输入等)
(2)从代码区域表格里找到那一个步骤(click),点击选中它
(3)从下方的Target后的下拉列表里选择一个带有xpath字样的选项,就可以复制xpath=后的字符串,得到目标元素的一个xpath表达式的写法

//font[@id=‘ECS_MEMBERZONE’]/a/img —登陆
//font/a/img —登陆
8、校验自己书写的xpath表达式是否能准确定位元素
(1)在某个网页上按下**F12**查看网页源代码
(2)在“查看器”下方“搜索HTML”的文本框里输入xpath表达式,回车
(3)查看后面的m/n 其中n代表该xpath能定位到的元素总数量,如果是1,代表该xpath能准确定位。

9、XPath表达式语法详解:主要讲解相对路径Xpath
(1)//标记名称 —在整个网页里查找该标记的元素。
例如:
//textarea —查找当前网页里所有文本域
//input —查找当前网页里所有文本框、按钮、单选按钮、复选框
//a —查找当前网页里所有超级链接
//img —查找当前网页里所有图片
(2)//父元素的标记名称/子元素的标记名称 –按照父子关系查找目标元素。
示例://form/a —查找form这个父元素下级的a元素

(3)说明:如果增加了父节点还是不准确,可以继续增加祖父节点、曾祖父、高祖父……
比如:
//子
//父/子
//祖父/父/子
//曾祖父/祖父/父/子
(4)各个层级的标记名称之后,都可以增加[]描述筛选元素的条件,这个[筛选条件]称为“谓词”或“谓语”
(5)[索引号] —从1开始编号的整数,代表同父****(兄弟关系)里同标记名称的元素的顺序号。
示例1://form/input[1] —form下级的第一个input

示例2://font/a[1]/img —登录

示例3://tr[1]/td[2]/input –第1行(tr)第2列(td)里的input

(6)[@属性名称] —筛选存在该属性的那个元素,使用较少!
示例://a[@name]/img --查找有name属性的a标记元素的下级子的img(图片)

(7)[@属性名称=‘属性值’] —用属性值来筛选元素。–常用!!!重点!!!
注意:xpath表达式里的引号可以支持单引号,也可以支持双引号。
示例1: [//a[@href=“pick_out.php”]](mailto:/a[@href=) —查找href属性值等于pick_out.php的一个a标记的元素

示例2:[//li/a[@href=“flow.php”]](mailto:/li/a[@href=)

(8)[子节点的标记名称] --筛选有某标记子节点的父节点,使用较少!
示例://form[select]/a —有select子节点的父元素form下级的a元素(高级搜索)

(9)[子节点的标记名称=‘文本值’] —筛选子节点的文本值等于特定值的父元素。
说明:子节点的文本值是指子节点(必须是双标记)的两个尖括号之间的文本。
示例://select[option=‘所有品牌’] —用选项(option)的文本找到下拉列表(select)

练习:demo3003,打开登录页,在登录页里,通过某个列(td)文本是“用户名”找到它所在的行(tr),再找该行的第二列(td[2]),再找第二列里的文本框(标记是input),输入123
//tr[td=‘用户名’]/td[2]/input

(10)多条件组合筛选
语法格式一:连续写多个[],每个[]里写一个条件,默认条件之间是“与”的关系。
示例://input[@name=‘msg_type’][@value=‘2’]

语法格式二:在一个[]写多个条件,条件之间使用逻辑运算符分隔开(and与、or或),注意逻辑运算符前后各有一个空格。条件里不能只包含一个数字(索引号)。
示例://input[@name=‘msg_type’ and @value=‘2’]

//input[@name=‘msg_type’][3] —正确
//input[@name=‘msg_type’ and 3] --错误
//input[@name=‘msg_type’ and position()=3] --正确
(11)xpath里的常用函数:position,无参,可以获得当前节点的索引号(整数)
//input[@name=‘msg_type’ and position()>3] --正确
(12)xpath里的常用函数:last,无参,可以获得同标记兄弟关系中最后一个节点的索引号(也就是兄弟节点中同标记元素个数),主要用于实现在兄弟较多或兄弟节点个数可能会变化时倒数第几个的查找。
//input[@name=‘msg_type’ and position()=last()] —最后一个
//input[@name=‘msg_type’][last()] —最后一个
//input[@name=‘msg_type’][last()-1] —倒数第二个
(13)xpath里的常用函数:text,无参,获得当前节点的文本(两个尖括号之间的文本),不仅可以用于超级链接。
示例1://a[text()=‘高级搜索’] ----等价于LINK_TEXT定位的效果

示例2://option[text()=‘联想’] —不能用LINK_TEXT定位

(14)xpath里的常用函数:contains,判断包含关系的函数,有2个参数,只要第1个参数的值里包含第2个参数的值,就返回True,否则返回False。常用的用法是第1个参数是@属性名称或text(),第2个参数是一个字符串。 —最常用!
常用用法一:[contains(@属性名称,‘字符串’)]
常用用法二:[contains(text(),‘字符串’)]
示例1://select[@id=‘select’]/option[contains(text(),‘耳机’)]

示例2:
//img[contains(@src,‘bnt_log.gif’)] —“登录”图片

(15)xpath里的节点符号:****//
父/子 — 逐级向下查找
祖先//后代 — 跳级向下查找
示例://form[@id=“compareForm”]**//**img —查找搜索结果区域(form)里唯一的一张图片(img)

(16)xpath里的节点符号:****() 提升优先级 —了解!!!
示例:(//div[@id=“show_best_area”]//img**)**[1] —首页上精品推荐区域中所有图片里的第一张

(17)xpath里的统配符: * —了解!建议少用!
/* 或 //* —任意标记名称
@* —任意属性名称
示例1: //*[@id=“keyword”] —关键词文本框
示例2: //input[@*=“keyword”] —关键词文本框
示例3: //[@=“keyword”] —关键词文本框
(18)xpath里的节点符号: /… 查找父元素(向上一级查找)
示例: //input[@id=“keyword”]/…/a —高级搜索

书写xpath的思路小结:
1.先找目标元素自己的属性值特征或文本值特征
2.找父元素的特征(用/分隔后,结合索引号)
3.下一步找祖父元素的特征(用/分隔后,结合索引号)
4.下一步找子元素的特征(用/…分隔后)
5.下一步找源代码中附近的元素的特征(反复用/…分隔找到与目标共同的祖先,然后再用/向下查找到目标元素)
八、CSS_SELECTOR定位-高级定位方法之一—了解!
1、高级定位方法:XPATH定位和CSS_SELECTOR定位,功能有重叠,所以建议重点学习XPATH定位即可。
2、XPATH定位 对比 CSS_SELECTOR定位:
(1)XPATH定位:功能更强大,既可以向下查找,也可以向上查找。CSS_SELECTOR定位只能往下查找。语法比CSS_SELECTOR定位更复杂。
(2)CSS_SELECTOR定位:选择器表达式更简短,定位速度更快,功能不如XPATH那么强大。
3、CSS介绍:
(1)CSS是:层叠式样式单,是用来修饰HTML的样式的一种独立的技术。
(2)CSS SELECTOR是:CSS选择器,是CSS中用来选择要修饰样式的元素的一种技术。
4、初学者利用浏览器复制得到CSS选择器:
Firefox—打开网页—目标元素右击—检查—在目标源代码的代码行上右击—复制—CSS选择器。

5、CSS_SELECTOR定位语法:
driver.find_element(By.CSS_SELECTOR,‘CSS选择器’).操作方法(‘参数’)
示例:
driver.find_element(By.CSS_SELECTOR,‘#ECS_MEMBERZONE > a:nth-child(2) > img:nth-child(1)’).click()#点击“登录”
6、书写CSS选择器:可以在Firefox的“查看器”进行校验
(1)**类型选择器—用标记名称**来选择元素
标记名称
例如:
input
img
select
(2)**ID选择器–用ID**属性值来选择元素
#ID属性值
例如:
#keyword
(3)**类选择器—用类名**来选择元素(类名是CLASS_NAME定位使用的类名)
.类名
例如:
.go —搜索按钮

(4)**属性选择器–用特定的属性值**来选择元素,所有属性都可以使用(通用)
[属性名称=‘属性值’] —注意:不加@
例如:
[name=‘keywords’]

(5)组合:以上四种条件可以任意组合,注意如果有标记名称,把标记名称写在最前面。
例如:
input#keyword[name=‘keywords’].B_input
(6)父>子 —逐级向下查找
例如: li>a[href=“flow.php”] —查看购物车
li#topNav>a[href=“flow.php”] —查看购物车
#topNav>a[href=“flow.php”] —查看购物车

(7)祖先后代 —跳级向下查找,用空格分隔
例如:div#search a —高级搜索

(8)利用**索引号**:语法比xpath索引号复杂,排号规则与xpath也不同。
举例:
<input……> 1 1
<a……> 1 2
<input……> 2 3
<input……> 3 4
<a……> 2 5
<a……> 3 6
(9)css里索引号使用三种写法:
<1> 标记:first-child —索引号是1
<2> 标记:last-child —索引号最大(最后一个子)
<3> 标记:nth-child(索引号) —特定索引号(通用)
例如: li#topNav>a:nth-child(3) —标签云

例如: font#ECS_MEMBERZONE>a:nth-child(2)>img —登录

九、查找一个元素和查找多个元素的方法
1、find_element方法:查找一个元素
(1)参数:****By.XXX,字符串
(2)返回值:WebElement对象(表示一个元素)
(3)说明:
如果网页里符合条件的有一个元素,就会**返回这个元素**
如果网页里符合条件的有多个元素,就会返回**第一个**元素
如果网页里没有符合条件的元素,就会报错(抛出异常NoSuchElementException,代表定位失败)

2、find_elements方法:查找多个元素
(1)参数:****By.XXX,字符串
(2)返回值:List(表示有多个元素对象组成的列表,顺序是网页源代码里的书写顺序)
(3)用法一:
# 查找多个元素,使用索引号获得其中某一个进行操作
driver.find_elements(By.NAME,‘msg_type’)[2].click()
(4)用法二:
# 查找多个元素,使用循环来遍历操作(逐个进行操作)
for i in range(5):
driver.find_elements(By.NAME, ‘msg_type’)[i].click()
sleep(2)
# 查找多个元素,使用循环来遍历操作(跳过第一个,逐个进行操作)
for i in range(1,5):
driver.find_elements(By.NAME, ‘msg_type’)[i].click()
sleep(2)

# 查找多个元素(包含“首页”的那一排超级链接),使用循环来遍历操作
for i in range(11):
driver.find_elements(By.XPATH,‘//div[@id=“mainNav”]/a’)[i].click()
sleep(2)
(5)说明:
如果网页里符合条件的有一个元素,就会返回**包含这1个元素的列表**
如果网页里符合条件的有多个元素,就会返回包含这**几个元素的列表**
如果网页里没有符合条件的元素,就**会返回一个空列表**
3、注意事项:
不论使用find_element查找一个元素,还是使用find_elements查找多个元素,都可以直接操作,也可以把找到的数据赋值给变量,使用该变量来代表这一个或多个元素。但是注意该变量不是一直有效,一旦网页刷新或跳转了,该变量就无法使用(变量失效了,不能表示那一个或多个元素了),此时使用该变量时,报错(抛出StaleElementReferenceException,代表变量失效),解决方案是一旦网页刷新或跳转了之后,需要重新定位元素。


十、浏览器和网页级别的基本操作(不用定位元素)
1、启动浏览器:
driver=webdriver.Firefox()
2、在浏览器里打开一个网页:get方法
driver.get(‘http://localhost/upload/message.php’)#打开留言板页
3、关闭浏览器:quit方法
driver.quit()
4、后退:back方法,跳转回上一个页面。
driver.back()

5、前进:forward方法,只有后退过,才能使用,前进到后退前的页面。
driver.forward()

6、刷新:refresh方法,重新加载当前网页
driver.refresh()

7、最小化浏览器窗口:minimize_window方法
driver.minimize_window()
8、最大化浏览器窗口:maximize_window方法
driver.maxmize_window()
9、设置浏览器窗口尺寸(自定义尺寸):set_window_size(宽度、高度)
**说明:**参数的单位是像素值(px),整个屏幕的宽度和高度可以从屏幕分辨率里调查到。
driver.set_window_size(600,350)#自定义浏览器窗口尺寸
十一、模拟键盘操作
1、作用:
<font style="background-color:#FFEFD1;">模拟客户按下键盘上的特殊按键</font>:<font style="color:#FA541C;">比如回车、退格、控制、上下左右箭头、Home、End、上一页、下一页……</font>
2、Selenium中,定位到元素后,使用send_keys方法不仅可以输入键盘的字母和数字等按键,也可以模拟这些特殊按键的输入操作。
3、具体的模拟键盘按键的操作步骤:
(1)导入按键专用类Keys
from selenium.webdriver.common.keys import Keys
(2)定位元素,调用send_keys方法,参数写Keys类里的代表目标按键的常量。
示例:
# 定位关键字文本框
k=driver.find_element(By.ID,‘keyword’)
# 输入a
k.send_keys(‘a’)
# 输入回车–达到点击“搜索”按钮的操作效果
k.send_keys(Keys.ENTER)#ENTER代表回车键
4、常用的按键常量:
ENTER —回车键
BACKSPACE —退格(回格),用来删除光标前面的一个字符
DELETE —删除键,用来删除光标后面的一个字符
TAB —制表符
SHIFT —上档键
CONTROL --控制键(Ctrl)
ALT —换挡键
HOME —起始键,用来把光标移动到开头处
END —结束键,用来把光标移动到末尾处
INSERT --插入键
PAGE_UP —上一页
PAGE_DOWN —下一页
ARROW_UP —上箭头
ARROW_DOWN —下箭头
ARROW_LEFT —左箭头
ARROW_RIGHT —右箭头
5、模拟组合键(同时按下这些键):比如Ctrl+a(全选)、Ctrl+c(复制)、Ctrl+v(粘贴)……
实现方式:把多个按键的参数,写在send_keys方法的参数处,也就是说send_keys方法的参数可以有多个。
u=driver.find_element(By.NAME,‘username’)# 定位用户名文本框
u.send_keys(‘vip’)#用户名文本框里输入vip__
sleep(1)
u.send_keys(Keys.CONTROL,‘a’)#组合键Ctrl+a全选
十二、上传文件用send_keys方法来选择文件
1、Selenium自动化测试上传文件遇到的问题:Selenium由于技术限制,无法像客户那样对文件上传的对话框进行自动操作,因为该对话框不属浏览器和网页级别的内容。

2、“浏览”或“上传”的按钮:一般都具备这个特点,就是它的网页源代码里是input标记,type属性值是**file**,我们称为“文件型按钮”。

3、针对“文件型按钮”,我们可以用send_keys方法来模拟客户选择文件,参数是文件的路径(注意:不要用click方法)。
(1)准备一个本地文件 E:\123.txt

(2)定位文件型按钮,调用send_keys方法,参数写本地文件的路径。


注意:\需要使用转义字符\
(3)常见错误:文件不存在

(4)**说明:**文件路径,需要写绝对路径
(5)尝试:使用相对路径,一个点代表当前脚本所在的文件夹,但是结论是不支持相对路径(提示文件不存在)。

(6)变通支持相对路径:借助于Python语法里获得当前路径,拼接目标文件的完整路径。
import os
c=os.getcwd()# 获得当前路径E:\se\seproject\day04__
p=os.path.join(c,‘123.txt’)#拼接目标文件的完整路径
# 用“浏览”按钮来选择一个本地文件__
driver.find_element(By.NAME,‘message_img’).send_keys§
(7)补充说明:文件经常与脚本不放在一个文件夹里,是一个单独的与脚本目录同级的文件夹。—常用!


十三、操作信息对话框(提示框)
1、信息对话框的**特点:**
(1)特点一:右击后无法通过右键菜单看到“检查”这个选项,也就是说无法查看源代码。
(2)特点二:它位于界面最上层,如果不关闭它,后面的网页无法继续操作。

2、注意:区分信息对话框与网页里的div标记的控件(浮动起来),只要能用右键菜单“检查”看到html源代码的控件,就按照元素的操作方式进行操作。
3、信息对话框(提示框)在Selenium中进行自动操作的实现方式:
(1)第一步:切换到信息框
语法:变量1=driver.switch_to.alert
说明:变量1是Alert(信息框对象)类型
示例:
a1=driver.switch_to.alert
(2)第二步:获得信息框里的文本信息—可选步骤!
语法:变量2=变量1**.text**
说明:变量2是str类型
注意:text是Alert(信息框对象)类型的属性名,不是方法名,所以后面不加小括号。
示例:
t1=a1.text
(3)第三步:点击按钮来关闭它
**语法:**变量1.方法()
说明:**accept方法可以点击“确定”按钮(常用),dismiss方法**可以点击“取消”按钮。
示例1:
a1.accept()
示例2:
a1.dismiss()
4、常见错误:
(1)**NoAlertPresentException:消息框不存在**时,执行切换消息框语句,就会出现此异常。

(2)**UnexpectedAlert**PresentException:消息框出现之后,没有关闭它,就不能操作后面的网页,如果操作,就报此异常。

十四、切换浏览器窗口(或浏览器标签页)
1、应用场景:当在一个浏览器窗口里进行某项操作后,出现一个新的浏览器窗口(或浏览器标签页),那么需要在窗口之间进行切换。如果不切换,只能操作原来窗口里的网页。

2、注意事项:Selenium代码执行时,不会自动切换窗口,只有执行到切换窗口语句时才会切换。
3、切换到**最新**窗口(最后启动的一个窗口):–常用!
(1)语法:
变量1=driver.window_handles
driver.switch_to.window(变量1[-1])
(2)解释:****window_handles获得由WebDriver本次运行期间所自动启动所有窗口的****句柄(说明:窗口句柄就是窗口的唯一标识数据),是一个list(列表),列表里的每一个窗口的句柄,这些句柄的顺序就是窗口启动的顺序。用这个列表里的最后一个成员(也就是最后启动的那一个窗口句柄)作为window方法的参数,就可以切换到最新窗口。
(3)示例:
# 切换到最新窗口
a=driver.window_handles
driver.switch_to.window(a[-1])
4、新内容:使用索引号0切换到最先启动的那一个窗口。
# 切换回最先启动的窗口
b=driver.window_handles
driver.switch_to.window(b[0])
5、补充:关闭窗口的操作方法
(1)quit方法:无参,关闭所有窗口。
driver.quit()
(2)close方法:无参,只关闭当前一个窗口,保留其他窗口。
driver.close()
说明:当前窗口(current window)指刚操作或刚切换到的窗口。关闭当前一个窗口后,就算最后只剩下一个窗口,也必须要执行切换窗口的语句,才能操作它。
6、万能切换窗口方法:当窗口启动的顺序不清楚的复杂业务中使用这个方法。—了解!不常用!
a=driver.current_window_handle#获得当前窗口的句柄
b=driver.window_handles#获得所有窗口句柄(列表)
for c in b: #遍历每一个窗口句柄
if c!=a:#判断如果不是当前窗口的话
driver.switch_to.window(c)#就先切换到那个窗口
if ?:#判断是不是目标窗口
break#如果是,就退出循环
7、判断是不是目标窗口,有很多种判断方法:?
(1)driver.title==“目标窗口内网页的标题”
(2)“目标窗口内网页的标题部分字符串” in driver.title
(3)driver.current_url.endswith(“url结尾的字符串”)
(4)“网页里的部分文本字符串” in driver.page_source
8、案例:
# 切换到一个窗口(满足的条件是:网页标题里包含“用户协议”)
a=driver.current_window_handle
b=driver.window_handles
for c in b:
if c!=a:
driver.switch_to.window©
if “用户协议” in driver.title:
break
十五、切换Frame(HTML框架)
1、HTML框架:是指html语法里的或,用来在一个html网页里嵌套其他html网页。只有在复杂的网页里才可能出现。

2、应用场景:在目标网页中定位元素失败,发现这个元素在某一个html源代码中,而这个html源代码是用或嵌套在其他html网页里,此时在Selenium中需要切换Frame。

3、如何切换Frame?
语法:driver.switch_to.frame(参数)
参数有几种写法:
(1)第一种写法(最常用):“frame或iframe标记的id属性值”
说明:如果没有id属性,也可以使用name属性值来进行切换。
示例:
driver.switch_to.frame(‘header-frame’)

(2)第二种写法:索引号,是指目标frame是第几个frame,从0开始编号,整数。
driver.switch_to.frame(0)
driver.switch_to.frame(1)#第2个frame
(3)第三种写法:WebElement对象(目标frame的元素对象),需要使用find_element方法找到frame元素。
f=driver.find_element(By.XPATH,“//frame[contains(@src,‘menu’)]”)#f变量的数据类型是WebElement(代表一个元素)
driver.switch_to.frame(f)

4、复杂的需求:切换到某一个frame内子网页里,进行一些操作之后,希望进入别的frame内子网页里进行操作,必须要先切换回默认主网页(最外层html网页),然后才能切换到同级别的别的Frame。
(1)切换回默认主网页(**最外层**html网页):不管当前在第几层frame子网页里。
driver.switch_to.default_content() —固定语法!!!
(2)更为复杂的情况:frame下的html子网页内部还有frame,就是多层嵌套。

**切换到上一层(父级网页):**如从第3层切到第2层。
driver.switch_to.parent_frame() —不常用!!!
5、注意事项:
(1)切换消息框(driver.switch_to.alert)与当前Frame无关,切换完消息框,关闭它,也并不影响之前所切换的frame。
(2)切换浏览器窗口(driver.switch_to.window(xxx))之后,总是默认自动回到默认最外层主网页里,会影响之前所切换的frame。
(3)切换到子网页里,进行某些操作之后,如果网页跳转到一个没有frame的网页,那么也必须要切换到主网页,才能继续操作。


十六、下拉列表的操作
1、html里下拉列表:
(1)标准实现方式:select、下级是option
(2)非标准实现方式:div、ul……
2、Selenium自动化测试时,操作下拉列表:两种方式
(1)模拟手工操作:点击下拉列表、等待、点击选项
(2)专属操作类:Select类(专门用来操作select标记的元素)
3、使用Select类来进行select标记的标准下拉列表的自动操作:
(1)第一步:**导入**这个专属操作类Select
from selenium.webdriver.support.select import Select
(2)第二步:**定位**select标记的元素
语法:变量1=driver.find_element(By.……,‘……’)
示例:
c=driver.find_element(By.ID,‘category’)

(3)第三步:**创建**Select类型的对象(也叫做“实例化”),该类构造函数的参数是第二步所查找定位到的元素对象。
语法:变量2=Select(变量1)
示例:
s1=Select©
(4)第四步:调用Select类的方法,也可以访问到Select类里提供的属性的值。
4、Select类里的方法
(1)select_by_visible_text方法:参数是<option……>与之间的完整文本字符串。可以通过选项的文本来选择一个选项。 —最常用!!!
示例1:
s1.select_by_visible_text(‘手机配件’)

示例2:建议从html里复制这个字符串,粘贴到python程序里使用。
s1.select_by_visible_text(’ 耳机’)

(2)select_by_index方法:参数是索引号(从0开始编号,整数,代表第几个选项)
示例:
选择第3个选项__
s1.select_by_index(2)
(3)**select_by_value**方法:参数是option标记的value属性值(str类型,可以是数字、字母、汉字)
示例:
通过value属性值来选择一个选项__
s1.select_by_value(‘7’)

(4)de****select_by_visible_text方法:取消文本是xx的选项,只有多选的下拉列表才能使用(select标记,有multiple属性)。

(5)deselect_by_index方法:通过索引取消一个选项
(6)deselect_by_value方法:通过value属性值取消一个选项
(7)deselect_all方法:取消所有选项
示例:
s.deselect_by_visible_text('商品列表')
sleep(2)
s.deselect_by_index(4)
sleep(2)
s.deselect_by_value('users.php?act=list')
sleep(3)
s.deselect_all()#取消全部选项
5、下拉列表的高级操作:****随机选择一个选项
(1)第一步:**导入**python里的random模块(随机模块)
import random
(2)第二步:**生成**一个随机整数(调用random模块里的randint函数,参数是随机数的范围,最小值是0,最大值是选项总个数减1)
i=random.randint(0,14)
(3)第三步:用这个随机整数作为索引号来选择一个选项
s1.select_by_index(i)
十七、Selenium里的等待操作
1、Selenium自动化测试中等待的必要性:
模拟客户操作频率,而且网页加载或跳转都需要时间。
2、Selenium实现**等待三种方式**:
(1)线程休眠(强制等待):调用time模块里的sleep函数来等待固定的时间。—最简单,初学者使用!
优点:语法简单。
缺点:功能单一,时间固定,不灵活,效率低,或代码运行不稳定。
from time import sleep
sleep(10)#等待10秒
(2)隐式等待:全局的等待元素出现的超时时间的设置
优点:全局设置(只需要设置一次,对所有元素定位都有效,灵活等待,等待到元素出现为止)
缺点:只限定于等待元素出现,不能等待其他的条件,而且一旦等待元素消失代码运行效率较低。
语法:driver.implicitly_wait(超时时间)
说明:参数的单位是秒,代表**最多**等待几秒,一般是10几秒。
**位置:**一般写在启动浏览器步骤之后。
driver.implicitly_wait(15)#隐式等待:超时时间是15__秒
(3)显式等待:可以自己书写代码描述等待的具体条件,根据条件完成等待,也可以设置等待的超时时间。—了解!
优点:功能最强大
缺点:语法最复杂,适合编程经验丰富的人员
十八、截屏操作
1、截屏**目的**:保留证据。
2、何时截图:网页元素定位失败或出现其它任何问题时,截图保留当时的网页状态。
3、Selenium中提供了截图的操作方法:****两种级别
(1)对整个当前浏览器的网页区域进行截图:driver.save_screenshot(‘图片文件路径’)
说明:如果有滚动条,不会截取滚动条滚动后才出现的区域,直截取当前屏幕上默认显示的部分。
(2)对网页里某个元素的区域进行截图:driver.find_element(By.……,‘……’).screenshot(‘图片文件路径’)
4、图片文件支持:后缀是.png格式,路径可以支持绝对路径,也可以支持相对路径(.代表当前文件夹,…上一级文件夹)。
5、常见问题:截图不完整,解决方案是设置窗口尺寸后截图。
driver.set_window_size(1280,2500)#把浏览器窗口尺寸设置为能展示出网页所有内容的大小(把网页缩小拉高)
sleep(4)
driver.save_screenshot(‘D:\a2.png’)#截图,就可以截图完整的网页区域
6、元素截图

7、**相对路径:**新建一个项目下级的screenshots的文件夹目录,希望截图保存到此处。
**说明:**当前脚本代码目录(day06)与新建的screenshots的文件夹目录是平级的。
注意:****文件夹路径必须是存在的。

十九、单元测试框架unittest介绍
1、unittest:是单元测试框架,可以用来管理单元测试、Selenium测试、接口测试的代码。
2、pytest和unittest都是单元测试框架,功能有些重叠。
3、unittest学习**目的**:为了用它管理Selenium测试的代码
(1)提供代码的组织管理的方式(比如一个文件里可以管理多条用例的实现代码)
(2)实现代码的复用
(3)批量运行
(4)汇总测试结果,生成易于阅读的测试结果报告
(5)实现数据驱动测试(把测试数据提取出来,存储在文件里进行管理)
(6)提供了很多的**断言**方法
4、unittest框架里的核心要素:
(1)TestCase:测试用例。—最重要!!
(2)TestSuite:测试套件,是测试用例的集合,用例管理多个测试用例内容,可以给批量运行时使用。—重要!!
(3)Assertion:断言,用于实现数据检查。—重要!!
(4)TestFixture:测试固件(或叫做“测试夹具”),用来复用代码,实现测试用例执行前的准备工作,以及测试用例执行后的收尾工作。
(5)TestLoader:测试加载器,用来加载指定的测试用例到测试套件里。
(6)TestRunner:测试运行器(测试执行器),用来运行测试套件,汇总测试结果,生成测试报告。
5、使用unittest框架做简单的单元测试:
(1)测试目标程序:计算器(只包含简单的加法、减法功能)

class Cal:
# 构造函数:初始化参与计算的两个数据
def __init__(self,x,y):
self.x=x
self.y=y
# 方法:add,加法计算,返回结果
def add(self):
result=self.x+self.y
return result
# 方法:sub,减法计算,返回结果
def sub(self):
result=self.x-self.y
return result
(2)设计单元测试的测试用例:
用例1:测试10和20的加法计算结果,检查是否返回30
用例2:测试1.04和2.01的加法计算结果,检查是否返回3.05
……
(3)用unittest框架实现单元测试的测试代码:


新建python文件时,文件名称没有规则要求,但是选择“Python单元测试”的文件类型。

导入被测模块里的被测类
from dev.cal import Cal
在测试方法里新建该类型的对象(传入测试数据),调用被测方法,接收返回的实际值数据,最后用断言方法来检查该数据是否符合预期要求。
def test_add1(self):#test_……测试方法
c1=Cal(10,20)#创建Cal类型的对象,用例1:数据是10和20
a1=c1.add()#调用add方法
self.assertEqual(30, a1)
一个类里可以定义多个测试方法,来测试多条测试用例
def test_add2(self):
c2=Cal(1.04,2.01)#用例2:数据是1.04和2.01
a2=c2.add()#调用add方法
self.assertEqual(3.05,a2)
6、Pycharm里的当前项目的运行模式(运行姿势):
(1)文件–设置

(2)工具—Python集成工具—默认测试运行程序—选择Unittest—确定

(3)运行—编辑配置

(4)选择Python测试下级的每一个项目,点击减号进行删除

(5)增加Unittest测试运行配置

(6)选择当前项目目录,确定

(7)确定

(8)检查运行模式:

(9)建议点击灰色对号:来展示成功和失败的所有结果

7、unittest框架里的断言:父类TestCase里提供了很多的断言方法,常用的断言方法如下
(1)assertEqual(预期值,实际值) 检查两个数据**类型相同**并且值相等。 常用!重要!

(2)assertIn(x,y) —检查x被包含在y里(一般常用的是检查字符串之间的包含关系) 常用!重要!

def test_01(self):
a='1234567'
b='345'
c='100'
self.assertIn(b,a)#成功
self.assertIn(c,a)#失败
#说明:断言失败后,后面的代码不会被执行
(3)assertTrue(x) —检查唯一的参数值等于True
def test_02(self):
a='1234567'
b='123'
c='345'
self.assertTrue((b in a) and len(a)==7)#b包含a里,并且a的长度是7
self.assertTrue(a.startswith(b))#检查a的开头部分是b
self.assertTrue(a.startswith(c)) # 检查a的开头部分是c 失败!!
(4)assertFalse(x) —检查唯一的参数值等于False
def test_03(self):
a='1234567'
b='123'
self.assertFalse(a.endswith(b))#成功,a的结尾部分不是b
(5)assertAlmostEqual(x,y,n) —检查小数x和小数y几乎相等(精度是n,代表精确到小数点后第n位相等),规则是四舍五入的进位原则。
def test_04(self):
a=10
b=3
self.assertAlmostEqual(3.33,a/b,2)
self.assertAlmostEqual(3.3333333,a/b)#第三个参数可选参数,默认值是7
c=20
self.assertAlmostEqual(6.67,c/b,2)
(6)assertGreater(x,y) —检查x大于y
def test_05(self):
a=10
b=3
self.assertGreater(a,b)#成功
self.assertGreater(b,a)#失败
8、说明:所有的断言方法,都可以通过参数名msg给一个字符串类型的参数值,来自定义失败消息。
self.assertGreater(b,a,msg=‘b不大于a’)#失败
二十、unittest框架里的其它知识点
1、测试固件:在测试用例类里的**固定名称**的方法,用来做准备工作和收尾工作,主要用于代码的复用。
(1)**setUp**方法:准备工作,在每一个测试方法之前,自动执行一遍该方法。
(2)**tearDown**方法:收尾工作,在每一个测试方法之后,自动执行一遍该方法。
class MyTestCase(unittest.TestCase):
def test_03(self):
print('03')
def tearDown(self):
print('9999999999999999999')
def setUp(self):
print('11111111111111111111')
def test_01(self):
print('01')
def test_02(self):
print('02')
(3)执行顺序:setUp----test_01—tearDown—setUp----test_02—tearDown—setUp----test_03—tearDown……
(4)说明:决定运行顺序的只有方法名称。
2、批量运行:测试套件、测试加载器、测试运行器
(1)新建python软件包:testsuite


(2)新建python文件:不用选择python单元测试


(3)在该文件里导入unittest,书写判断main的语句
import unittest
if name == ‘main’:
(4)调用unittest里的**defaultTestLoader(默认的测试加载器)里的discover**方法来查找测试用例,加载到测试套件里
discover方法的**参数**有两个:
第一个:描述查找用例的文件夹路径
第二个:描述文件名称应该符合的规则
示例:*代表任意字符串。
file_path='..\\testcase\\'
file_name='case*.py'
s1=unittest.defaultTestLoader.discover(file_path,file_name)#到指定路径下查找指定名称规则的python文件中的所有测试用例,加载到一个测试套件中,返回。
(5)运行测试套件:****创建测试运行器的对象(调用运行器类的构造函数),然后调用它的run方法来运行,参数就是测试套件对象。
r1=unittest.TextTestRunner()#创建运行器对象
r1.run(s1)#运行测试套件
(6)问题:测试结果报告形式不是有利于阅读,因为是纯文本的格式,也不利于分享结果。
优化方案:不使用unittest框架里自带的TextTestRunner,换为使用第三方测试运行器HTMLTestRunner,这样可以生成html格式(网页版)测试结果报告文件,可以在浏览器里查看,也可以分享给其他人查看。
3、使用HTMLTestRunner的用法:
(1)从老师下发的资料包里复制utils文件夹里的HTMLTestRunner1.py

(2)在当前Pycharm的项目seproject1下新建一个python软件包,假设叫做utils(代表外部工具模块),把那个文件粘贴到这个包里。



(3)为了演示它的使用,复制suite1.py,粘贴到testsuite包里,新名称是suite2.py
(4)导入HTMLTestRunner类
from utils.HTMLTestRunner1 import HTMLTestRunner

(5)新建目录:
(6)(用于存储html格式的测试结果报告文件)


(7)继续实现suite2.py:打开一个html文件
# 以写入模式(wb)来打开一个测试结果报告文件
# 注意:__文件的目录必须提前手工创建、文件可以自动在运行此程序时自动创建
f1=open(‘…\report\result.html’,‘wb’)
(8)创建运行器对象,运行测试套件,关闭文件
r1=HTMLTestRunner(f1)#创建运行器对象
r1.run(s1)#运行测试套件
f1.close()#关闭文件
(9)运行测试套件代码,生成html报告

(10)查看报告:在左侧result.html右击—打开于—浏览器—Firefox
二十一、unittest与Selenium集成(用unittest框架来管理Selenium自动化测试代码)
1、testcase包里新建文件:Python单元测试

2、导入各项资源
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
3、在该类里定义一个名称是**setUp**的方法,来实现启动浏览器的步骤,注意:变量driver(局部变量)必须要改为self.driver(类里各个方法都可以使用的成员变量)
说明:也可以增加一些其他准备工作的步骤,比如设置隐式等待,浏览器窗口最大化。
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(14)#隐式等待
self.driver.maximize_window()#浏览器窗口最大化
4、在该类里定义一个名称是**tearDown**的方法,实现关闭浏览器的步骤
def tearDown(self):#收尾工作
self.driver.quit()#关闭浏览器
5、在这个类里,至少要有一个测试方法test_……:打开xx网页、定位xxx元素、操作、数据获取、数据检查……等业务步骤和数据检查的内容。
def test_01(self):
self.driver.get('http://localhost/upload/user.php')#打开登录页
self.driver.find_element(By.NAME,'submit').click()#立即登录
sleep(4)
a1=self.driver.switch_to.alert#切换到消息框
t1=a1.text#获得消息框里的文本信息
a1.accept()#点击“确定”
self.assertIn('用户名不能为空',t1)#检查该信息里包含‘用户名不能为空’
self.assertIn('登录密码不能为空',t1)#检查该信息里包含‘登录密码不能为空’
二十二、Selenium测试中经常做的业务检查(获得数据、检查数据正确性)
1、检查提示信息对话框里的文本信息
a2=self.driver.switch_to.alert
t2=a2.text
self.assertEqual(‘请输入搜索关键词!’,t2)
2、检查当前网页的标题正确:主要用于检查网页跳转成功
#检查当前网页跳转到购物车页(如何检查?检查标题开头部分是“购物流程”)
t=self.driver.title
self.assertTrue(t.startswith(‘购物流程’))
3、检查网址(URL)正确:主要用于检查网页跳转成功
# 检查跳转到注册页(检查URL以“register”结尾)
u=self.driver.current_url
self.assertTrue(u.endswith(‘register’))

**练习:**testcase包里新建testcase7003.py(“Python单元测试”类型),setUp方法启动浏览器,tearDown方法里关闭浏览器,test_01方法里打开前台首页,点击“登录”,等待2秒,检查网址(URL)以“user.php”结尾,输入错误用户名abc和密码123,点击“立即登录”,检查网页标题以“系统提示”开头,检查网页里有一个文本提示信息“用户名或密码错误”,等待5秒,检查网页标题以“用户中心”开头。
import unittest
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
def test_01(self):
self.driver.get('http://localhost/upload/index.php')#打开前台首页
self.driver.find_element(By.XPATH,'//img[contains(@src,"bnt_log.gif")]').click()#点击“登录”
# 检查URL以“user.php”结尾
u1=self.driver.current_url
self.assertTrue(u1.endswith("user.php"))
self.driver.find_element(By.NAME,'username').send_keys('abc')#输入用户名abc
self.driver.find_element(By.NAME,'password').send_keys('123')#输入密码123
self.driver.find_element(By.NAME,'submit').click()#立即登陆
# 检查标题以“系统提示”开头
t1=self.driver.title
self.assertTrue(t1.startswith('系统提示'))
# 检查网页里有一个文本提示信息“用户名或密码错误”
ps1=self.driver.page_source
self.assertIn('用户名或密码错误',ps1)
sleep(9)#等待9秒,网页会自动跳转
# 检查标题以“用户中心”开头(目标:检查自动跳转回登录页)
t2=self.driver.title
self.assertTrue(t2.startswith('用户中心'))
def test_02(self):
self.driver.get('http://localhost/upload/index.php')
self.driver.find_element(By.ID,'keyword').send_keys('1234567')#输入关键词
self.driver.find_element(By.NAME,'imageField').click()#点击搜索
sleep(2)
# 检查搜索结果区域里的提示信息是“无法搜索到您要找的商品!”
m=self.driver.find_element(By.CLASS_NAME,'f5').text
self.assertEqual('无法搜索到您要找的商品!',m)
if __name__ == '__main__':
unittest.main()
4、检查网页里出现的文本内容:
如果网页出现时间较短,来不及调查网页元素的定位方法,可以获得当前网页源代码(self.driver.page_source),然后使用assertIn检查其中包含预期文本即可。
# 检查网页里有一个文本提示信息“用户名或密码错误”
ps1=self.driver.page_source
self.assertIn(‘用户名或密码错误’,ps1)

5、检查静态文本类型的元素中的文本内容:
定位到元素,再获得元素里的文本(元素对象.text),最后在进行检查,比上一种检查更精准。
静态文本:是指双标记,文本位于两个<>之间。
# 检查搜索结果区域里的提示信息是“无法搜索到您要找的商品!”
m=self.driver.find_element(By.CLASS_NAME,‘f5’).text
self.assertEqual(‘无法搜索到您要找的商品!’,m)#注意预期值数据类型也写str

6、检查文本框里的当前内容:
定位到文本框,使用元素的**get_attribute**方法,参数写html标记的value属性名(str类型),就可以获得文本框里的内容(str类型),然后可以进行检查。
# 检查关键词文本框里当前显示的内容是“1234567”
v1=self.driver.find_element(By.ID, ‘keyword’).get_attribute(‘value’)
self.assertEqual(‘1234567’,v1)#注意:数据类型str

**练习:**case7004.py新建test_02,打开P806的详情页(24号商品),检查购买数量文本框里的默认值是“1”(注意数据类型是str)
import unittest
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
# 导入下拉列表专用操作类Select
from selenium.webdriver.support.select import Select
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
def test_01(self):
self.driver.get('http://localhost/upload/index.php')
self.driver.find_element(By.ID,'keyword').send_keys('1234567')#输入关键词
self.driver.find_element(By.NAME,'imageField').click()#点击搜索
# 检查关键词文本框里当前显示的内容是“1234567”
v1=self.driver.find_element(By.ID, 'keyword').get_attribute('value')
self.assertEqual('1234567',v1)#注意:数据类型str
def test_02(self):
self.driver.get('http://localhost/upload/goods.php?id=24')#打开P806的详情页
#检查"购买数量"文本框里的默认值是“1”(注意数据类型是str)
n=self.driver.find_element(By.ID,'number').get_attribute('value')
self.assertEqual('1',n)#注意1要加引号
# 检查灰色单选钮默认已经被选中
b1=self.driver.find_element(By.ID,'spec_value_167').is_selected()
self.assertTrue(b1)
# 检查数据线复选框当前默认没有被选中
b2=self.driver.find_element(By.ID,'spec_value_168').is_selected()
self.assertFalse(b2)
def test_03(self):
self.driver.get('http://localhost/upload/user.php?act=register')
# 检查“我已看过并接受……”复选框默认已经被选中。
s1=self.driver.find_element(By.NAME,'agreement').is_selected()
self.assertTrue(s1)
# 定位注册页里的下拉列表(标记名是select的元素)
q=self.driver.find_element(By.NAME,'sel_question')
# 创建Select类型的对象
s=Select(q)
# 获得默认选项(WebElement类型,option标记的元素)
o=s.first_selected_option
# 得到默认选项的文本(从<option>和</option>中间获得)
t=o.text
# 检查该默认选项的文本等于“请选择密码提示问题”
self.assertEqual('请选择密码提示问题',t)
if __name__ == '__main__':
unittest.main()
7、检查单选钮或复选框是否已经被选中(带点或带勾):
定位到元素,调用**is_selected**方法(无参),就可以得到它返回值(布尔值),如果希望它被选中,就用assertTrue断言它,如果希望它没有被选中,就用assertFalse断言它。
(1)示例1:
# 检查灰色单选钮默认已经被选中
b1=self.driver.find_element(By.ID,‘spec_value_167’).is_selected()
self.assertTrue(b1)

(2)示例2:
# 检查数据线复选框当前默认没有被选中
b2=self.driver.find_element(By.ID,‘spec_value_168’).is_selected()
self.assertFalse(b2)

**练习:**test_03,打开注册页,检查“我已看过并接受……”复选框默认已经被选中。
8、检查下拉列表的当前选项的文本正确:
<font style="background-color:#FBF5CB;">导入Select类,定位html里的标记名是select的那个元素</font>,<font style="background-color:#CEF5F7;">创建Select类型的对象,使用Select对象的</font>**<font style="color:#E8323C;background-color:#CEF5F7;">first_selected_option</font>**<font style="background-color:#CEF5F7;">属性值就获得当前选项</font>(<font style="background-color:#F9EFCD;">数据类型是WebElement对象,代表option标记的元素</font>),<font style="background-color:#FBE4E7;">使用该元素的text属性值获得当前选项的文本(str类型)</font>,最后可以检查它。 ---重点!
# 导入下拉列表专用操作类Select
from selenium.webdriver.support.select import Select
# 定位注册页里的下拉列表(标记名是select的元素)
q=self.driver.find_element(By.NAME,'sel_question')
# 创建Select类型的对象
s=Select(q)
# 获得默认选项(WebElement类型,option标记的元素)
o=s.first_selected_option
# 得到默认选项的文本(从<option>和</option>中间获得)
t=o.text
# 检查该默认选项的文本等于“请选择密码提示问题”
self.assertEqual('请选择密码提示问题',t)

**练习:**新建case7005.py,setUp、tearDown与前面代码一致,test_01里打开首页,点击“高级搜索”,等待3秒,检查品牌下拉列表里的默认选项文本是“所有品牌”。
import unittest
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
def test_01(self):
self.driver.get('http://localhost/upload/index.php')
self.driver.find_element(By.LINK_TEXT,'高级搜索').click()#点击“高级搜索”
sleep(3)
# 检查品牌下拉列表里的默认选项文本是“所有品牌”
b=self.driver.find_element(By.ID,'brand')
s1=Select(b)
o1=s1.first_selected_option
t1=o1.text
self.assertEqual('所有品牌',t1)
def test_02(self):
self.driver.get('http://localhost/upload/index.php')#打开ecshop前台首页
# 检查前台首页里的分类下拉列表的选项总个数是15
c=self.driver.find_element(By.ID,'category')
s=Select(c)
a=s.options#获得所有选项(列表)
n=len(a)#获得列表的长度(选项的个数,int类型)
self.assertEqual(15,n)#注意:预期值不能加引号
if __name__ == '__main__':
unittest.main()
9、检查下拉列表里的选项总个数正确:
导入Select类,定位html里的标记名是select的那个元素,创建Select类型的对象,使用<font style="background-color:#FBE4E7;">Select对象的</font>**<font style="color:#E8323C;background-color:#FBE4E7;">options</font>**<font style="background-color:#FBE4E7;">属性值来获得所有选项(列表类型)</font>,<font style="background-color:#E8F7CF;">调用</font>**<font style="color:#E8323C;background-color:#E8F7CF;">len</font>**<font style="background-color:#E8F7CF;">函数来获得该列表的长度就是选项的总个数(int类型)</font>,<u>最后用assertEqual检查它等于几,也可以用assertGreater检查它大于几。</u>
from selenium.webdriver.support.select import Select
# 检查前台首页里的分类下拉列表的选项总个数是15
c=self.driver.find_element(By.ID,'category')
s=Select(c)
a=s.options#获得所有选项(列表)
n=len(a)#获得列表的长度(选项的个数,int类型)
self.assertEqual(15,n)#注意:预期值不能加引号
二十三、数据驱动测试(本质叫就是“参数化”)
1、数据驱动测试:数据改变引起测试结果的改变
2、为什么要做数据驱动测试:
(1)测试**代码复用**
(2)数据和代码分离
(3)通过一份代码测试多组数据,来验证不同的测试场景
3、数据驱动测试工作:
把测试数据从现有测试代码里提取出来,进行单独管理,让自动化测试代码只写一份,反复使用不同组数据来运行。
4、数据驱动测试模块安装:
<u> Data-Driven Test (ddt),在Python语言unittest框架里,需要安装第三方库ddt来实现数据驱动测试。</u>
cmd输入pip install ddt

如果安装失败,也可以增加镜像服务器地址
cmd输入pip install ddt** -i** https://pypi.douban.com/simple
也可以通过Pycharm进行安装:



5、ddt基础装饰器:
@data —装饰测试方法,用于提供多组数据
@ddt —装饰测试用例实现类,用于遍历数据,测试多组数据。
6、案例1:
用a关键词进行搜索
用b关键词进行搜索
用c关键词进行搜索
7、ddt基础实现步骤:支持一个数据的测试。
(1)导入装饰器
from ddt import ddt,data
(2)给测试用例类前书写@ddt

(3)给测试方法前书写@data(数据1,数据2,数据3)

(4)给测试方法增加一个形参变量来表示那一个测试数据

(5)在测试方法的方法体里使用形参变量作为测试数据

import unittest
from ddt import ddt,data
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@data('a','b','c')
def test_something(self,kw):
print(kw)
self.driver.get('http://localhost/upload/index.php')#前台首页
self.driver.find_element(By.ID,'keyword').send_keys(kw)#注意:变量kw名称前后不要加引号
self.driver.find_element(By.NAME,'imageField').click()#点击“搜索”
sleep(5)
if __name__ == '__main__':
unittest.main()
8、注意事项:在左侧文件上右击菜单里选择“运行……”,不要在右侧代码区右击运行。


9、案例2:使用不同数据进行有效留言
用例1:
输入电子邮件地址jack@126.com,输入主题hello,输入留言内容是12345,点击“我要留言”
用例2:
输入电子邮件地址rose@163.com,输入主题:你好,输入留言内容是:我要买手机,点击“我要留言”
10、情况:
一组数据里包含多个值,需要**使用列表或元组或字典**来存储一组数据里的多个值,然后把这多个列表或元组或字典写在@data后面的小括号里。
11、@unpack:
专门用于对列表或元组或字典,进行解包操作。
12、ddt支持多个数据值的实现步骤:以列表为例
(1)导入装饰器:ddt、data、unpack
from ddt import ddt,data,unpack
(2)在测试用例类前书写@ddt

(3)在测试方法前书写@data([值1,值2,值3],[值1,值2,值3])
注意:每一个列表里的数据值的个数必须是一样的。

(4)在测试方法前书写@unpack
注意:@data、@unpack、def这三行对齐,而且紧挨着写。

(5)给测试方法增加多个形参变量,形参变量的名称随意,形参变量的个数必须要与每一个列表里的数据值的个数要一致****。

(6)在测试方法的方法体里使用这些形参变量做测试。
import unittest
from ddt import ddt,data,unpack
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@data(['jack@126.com','hello','12345'],['rose@163.com','你好','我要买手机'])
@unpack
def test_something(self,e,t,c):
print(e,t,c)
self.driver.get('http://localhost/upload/message.php')#打开留言板页
self.driver.find_element(By.NAME,'user_email').send_keys(e)#输入电子邮件地址:变量e
self.driver.find_element(By.NAME,'msg_title').send_keys(t)#输入主题:变量t
self.driver.find_element(By.NAME,'msg_content').send_keys(c)#输入留言内容:变量c
self.driver.find_element(By.XPATH,'//input[@value="我要留言"]').click()
# 检查网页源代码里包含‘您的留言已成功发表’
ps1=self.driver.page_source
self.assertIn('您的留言已成功发表',ps1)
if __name__ == '__main__':
unittest.main()
13、案例3:使用不同数据进行有效留言
用例1:
输入电子邮件地址jack@126.com,点击留言类型里的“询问”单选按钮(第3个,索引号是2),输入主题hello,输入留言内容是12345,点击“我要留言”
**用例2:**输入电子邮件地址rose@163.com,点击留言类型里的“求购”单选按钮(第5个,索引号是4),输入主题:你好,输入留言内容是:我要买手机,点击“我要留言”
14、难点:如何根据@data装饰器后的测试数据不同,来点击不同的单选按钮?

(1)分析:一组单选按钮html里有相同的name属性值,所以可以用NAME定位结合find_elements来一次性定位到所有单选按钮,根据列表的索引号的不同来点击不同的单选按钮。
(2)**思路:**在准备测试数据时,准备索引号,在测试方法里把列表的索引号换为变量。
(3)注意:索引号从0开始编号,数据类型要求是int类型。
(4)实现步骤:
先准备索引号的测试数据

然后在形参变量里,相应的顺序位置,增加一个形参变量

最后,在测试方法的方法体里,使用find_elements查找所有单选按钮,用这个变量i做为索引号获取其中一个单选按钮,进行点击操作

**题目:**case8001.py—>case8004.py用不同的条件搜索后,检查总计记录数不同(检查的预期值也不需要跟随关键词进行参数化)。
a 4
b 1
c 24
import unittest
from ddt import ddt,data,unpack
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@data(['a','4'],['b','1'],['c','24'])
@unpack
def test_something(self,kw,exp):
print(kw,exp)
self.driver.get('http://localhost/upload/index.php')#前台首页
self.driver.find_element(By.ID,'keyword').send_keys(kw)#注意:变量kw名称前后不要加引号
self.driver.find_element(By.NAME,'imageField').click()#点击“搜索”
sleep(5)
# 检查总计记录数等于预期值
n=self.driver.find_element(By.XPATH,'//div[@id="pager"]/span/b').text
self.assertEqual(exp,n)#注意:exp和n都是str类型
if __name__ == '__main__':
unittest.main()
二十四、读取外部数据文件
1、应用场景:测试数据的数据量大时使用
2、使用外部数据文件的优势:
(1)数据管理更方便
(2)数据修改维护更新也只需要修改数据文件,不需要修改代码
(3)多个代码可以复用数据文件
3、ddt库里提供了**@file_data**装饰器可以读取外部文件的数据,但是它支持的数据文件的格式是json和yaml格式。
4、以json为例演示:
(1)seproject1下新建目录testdata,专门用于存储测试数据文件。


(2)在testdata下新建文件:search_data.json

(3)这个search_data.json文件里,书写测试数据
[{“kw”:“a”,“exp”:“4”},
{“kw”:“b”,“exp”:“1”},
{“kw”:“c”,“exp”:“24”}]
说明:一个[]代表一个数组,数据里的每个json对象用逗号分隔开,每个json对象里用多个”key”:”value”键值对描述一个测试数据值,数据和数据之间用逗号分隔。
**注意:**只支持双引号,不支持单引号。
5、@file_data装饰器用法:
(1)导入file_data装饰器
from ddt import ddt,unpack,file_data
(2)在测试方法前写@file_data(‘文件路径.json’)
(3)测试方法的形参变量加入,注意形参变量名称必须要与json文件里的key要一致。
import unittest
from ddt import ddt,unpack,file_data
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@file_data('..\\testdata\\search_data.json')
@unpack
def test_something(self,kw,exp):
print(kw,exp)
self.driver.get('http://localhost/upload/index.php')#前台首页
self.driver.find_element(By.ID,'keyword').send_keys(kw)#注意:变量kw名称前后不要加引号
self.driver.find_element(By.NAME,'imageField').click()#点击“搜索”
sleep(5)
# 检查总计记录数等于预期值
n=self.driver.find_element(By.XPATH,'//div[@id="pager"]/span/b').text
self.assertEqual(exp,n)#注意:exp和n都是str类型
if __name__ == '__main__':
unittest.main()
注意:i变量的数据类型是int。

import unittest
from ddt import ddt,unpack,file_data
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@file_data('..\\testdata\\message_data.json')
@unpack
def test_something(self,e,i,t,c):
print(e,i,t,c)
self.driver.get('http://localhost/upload/message.php')#打开留言板页
self.driver.find_element(By.NAME,'user_email').send_keys(e)#输入电子邮件地址:变量e
self.driver.find_elements(By.NAME,'msg_type')[i].click()#点击索引号是i的单选钮
self.driver.find_element(By.NAME,'msg_title').send_keys(t)#输入主题:变量t
self.driver.find_element(By.NAME,'msg_content').send_keys(c)#输入留言内容:变量c
self.driver.find_element(By.XPATH,'//input[@value="我要留言"]').click()
# 检查网页源代码里包含‘您的留言已成功发表’
ps1=self.driver.page_source
self.assertIn('您的留言已成功发表',ps1)
if __name__ == '__main__':
unittest.main()
6、csv格式文件:比json和yaml格式更简洁明了,更方便数据的书写和维护。
(1)问题:@file_data不支持csv格式
解决方案:python实现一个读取csv格式数据文件的工具模块,把读取到的数据给@data来进行使用。
(2)说明:python语法里自带csv模块直接可以读取csv格式文件的数据,不需要单独安装别的库。
(3)utils包(工具)—新建python文件—假设叫做:read_csv.py,用来读取csv文件里的数据。

(4)实现read_csv.py里的代码:定义一个类CSVUtil,构造函数接收csv文件的路径,get_list_data方法读取数据,以列表的形式返回这些数据。
说明:建议跳过第1行(预留的注释行)
import csv
#CSV工具类
class CSVUtil:
def __init__(self,file_path):
self.file_path=file_path#初始化csv文件的路径
def get_list_data(self):#读取csv数据,返回列表
value_rows=[]
f=open(self.file_path,encoding='utf-8')#打开csv文件
f_csv=csv.reader(f)
next(f_csv)#跳过第1行(预留的注释行),从第2行开始读取
for row in f_csv:#遍历数据文件里的每一行数据
value_rows.append(row)#把每一行数据追加到结果列表里
f.close()#关闭csv文件
return value_rows
(5)testdata目录下新建一个文件:search_data.csv



(6)书写数据内容:第一行写注释,从第2行开始写数据内容,数据不用加引号(所有数据读取后,都会被当做字符串类型看待),数据和数据之间必须要用英文逗号分隔。
注意事项:最后一行数据之后,不要再加多余的换行符。每行里的逗号个数必须要一致。

(7)在read_csv.py末尾,测试一个该工具模块

说明:
如果使用相对路径描述csv文件所在的文件夹,运行报错,提示FileNotFoundException(文件没有找到),那么建议把相对路径修改为绝对路径(从盘符开始描述文件位置)。
7、使用读取到的csv文件里的数据作为Selenium测试中ddt所使用的数据。
(1)复制case8004.py为case8007.py
(2)导入utils包的read_csv模块里的CSVUtil类

(3)**使用该工具类读取到csv文件里的数据:**代码写在导入语句之后,@ddt之前的位置,存储到变量里。

(4)使用变量的值作为@data后的数据,但是记得用*把大列表解包,使用其中的小列表作为参数。

import unittest
from ddt import ddt,data,unpack
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
from utils.read_csv import CSVUtil
# 读取csv文件里的数据,存储在变量d里(数据类型是列表,列表的每个成员还是列表)
u=CSVUtil('..\\testdata\\search_data.csv')#创建CSVUtil对象
d=u.get_list_data()#读取数据,返回列表
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
# d=[['a','4'],['b','1'],['c','24']]
#@data(['a','4'],['b','1'],['c','24'])
@data(*d)
@unpack
def test_something(self,kw,exp):
print(kw,exp)
self.driver.get('http://localhost/upload/index.php')#前台首页
self.driver.find_element(By.ID,'keyword').send_keys(kw)#注意:变量kw名称前后不要加引号
self.driver.find_element(By.NAME,'imageField').click()#点击“搜索”
sleep(5)
# 检查总计记录数等于预期值
n=self.driver.find_element(By.XPATH,'//div[@id="pager"]/span/b').text
self.assertEqual(exp,n)#注意:exp和n都是str类型
if __name__ == '__main__':
unittest.main()
(5)注意:从csv里读取的所有数据,数据类型都是str,如果想当成别的类型来使用,一定要强制转换数据类型。

例如:
self.driver.find_elements(By.NAME,‘msg_type’)[int(i)].click()#点击索引号是i的单选钮,注意:强制转换变量i的数据类型为int类型,然后才能当做索引号来使用。
import unittest
from ddt import ddt,data,unpack
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
from utils.read_csv import CSVUtil
u=CSVUtil('..\\testdata\\message_data.csv')
d=u.get_list_data()
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
def tearDown(self):
self.driver.quit()
@data(*d)
@unpack
def test_something(self,e,i,t,c):
print(e,t,c)
print(type(i))#str
self.driver.get('http://localhost/upload/message.php')#打开留言板页
self.driver.find_element(By.NAME,'user_email').send_keys(e)#输入电子邮件地址:变量e
self.driver.find_elements(By.NAME,'msg_type')[int(i)].click()#点击索引号是i的单选钮,注意:强制转换变量i的数据类型为int类型,然后才能当做索引号来使用。
self.driver.find_element(By.NAME,'msg_title').send_keys(t)#输入主题:变量t
self.driver.find_element(By.NAME,'msg_content').send_keys(c)#输入留言内容:变量c
self.driver.find_element(By.XPATH,'//input[@value="我要留言"]').click()
# 检查网页源代码里包含‘您的留言已成功发表’
ps1=self.driver.page_source
self.assertIn('您的留言已成功发表',ps1)
if __name__ == '__main__':
unittest.main()
练习:目录testdata下新建login_data.csv,在该文件里准备登陆时使用的无效测试数据,testcase包里新建case8009.py,读取login_data.csv里的测试数据,用于登陆测试(包括步骤有,打开登录页、输入用户名、输入密码、点击“立即登陆”,检查网页源代码里包含“用户名或密码错误”)
login_data.csv
用户名,密码
abc,123
def,456
ghi,789

import unittest
from ddt import ddt,data,unpack
from utils.read_csv import CSVUtil
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
u=CSVUtil('..\\testdata\\login_data.csv')
d=u.get_list_data()
@ddt
class MyTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(13)
def tearDown(self):
self.driver.quit()
@data(*d)
@unpack
def test_something(self,un,pw,msg):
print(un,pw,msg)
self.driver.get('http://localhost/upload/user.php')#打开登录页
self.driver.find_element(By.NAME,'username').send_keys(un)#输入用户名:变量un
self.driver.find_element(By.NAME,'password').send_keys(pw)#输入密码:变量pw
self.driver.find_element(By.NAME,'submit').click()#点击“立即登陆”
# 检查网页源代码里包含“用户名或密码错误”
ps1=self.driver.page_source
self.assertIn(msg,ps1)
if __name__ == '__main__':
unittest.main()
二十五、Selenium项目介绍
1、复习回顾:一个项目做功能自动化测试的条件
(1)需求相对稳定
(2)主流程测试通过
(3)测试周期较长(一般半年以上)
2、面试题:验证码怎么做自动化测试?
(1)验证码不适合做自动化测试,因为验证码设计的初衷就是要屏蔽电脑去操作它,这样可以保证系统的安全性。
(2)首先尽量修改配置来不显示验证码,然后再进行自动化测试。
(3)无法配置消失的情况下,建议采用万能验证码进行测试。—需要开发人员协助添加。
(4)没有万能验证码的情况下,可以使用一些第三方技术(比如:Python语言里使用ddddocr)破解验证码—难度大。
(5)最后,无法测试验证码时,也可以采用半自动方式进行测试。
3、用Selenium做功能自动化测试的基本流程:
(1)需求分析
(2)制定测试计划
(3)搭建测试环境:包括被测系统运行环境、自动化测试代码开发环境
(4)设计测试用例:文档
(5)编写自动化测试代码 —耗时多!!!
(6)调试代码 —耗时多!!!
(7)新版被测系统需要做功能测试时,运行代码来做测试
(8)分析结果报告、提交缺陷
(9)编写测试总结报告:文档
(10)当需求变更时,维护测试用例文档和测试代码。 —耗时多!!!
4、自动化测试实现时,关注点有:
(1)代码**复用**性
(2)代码**健壮**性:多做判断、多做检查
(3)代码**可维护**性:设计合理的自动化测试代码的架构
5、自动化测试架构设计的本质:
分门别类的管理各项资源(包括:测试代码、数据、文件等)—分层。
二十六、ECShop后台登录模块
1、功能:有效账号能登陆成功,无效账号登陆时给出提示。
2、设计测试用例

3、设计测试数据:存储在单独的文件里
说明:为了做自动化测试时,更方便使用这些测试数据,单独设计了一个数据文件(csv格式),把测试用例文档中本来应该书写的测试数据的具体值,都写在这个文件里。
**建议:**把该数据文件复制到Pycharm里seproject1下的testdata目录里,在Pycharm里打开查看它。

4、特殊数据说明:预期结果编号—为了自动化测试单独增加的数据。
(1)测试用例里:增加“预期结果编号”,设计原则是相同的预期结果使用相同的预期结果编号,不同的预期结果就用不同的预期结果编号。
(2)测试数据里:根据测试用例编号不同,书写对应的预期结果编号,这样为了在测试数据导入到测试代码中时,可以把要做的不同检查进行区分(测试代码里的判断条件更方便书写)。
5、把测试数据利用read_csv读取出来,给testcase层测试用例实现代码来使用。
(1)testcase包—新建—Python文件—选择”Python单元测试”—输入文件名称(比如bg_login_case_v1)
说明:
bg—background后台
login—登录
case—测试用例
v1—第一版本

(2)导入数据驱动测试使用的资源
from utils.read_csv import CSVUtil
from ddt import ddt,data,unpack
(3)在class前面,导入语句后,书写读取csv文件里的数据的代码
# 读取文件里的测试数据
u=CSVUtil('..\\testdata\\数据_ECShop_后台登录.csv')
d=u.get_list_data()
print(d)#为了调试,打印出来
运行,查看日志:[[‘D001’,‘TC001’……],……[‘D108’……]]
(4)使用ddt技术,把测试数据,传入到测试方法里
@ddt
class MyTestCase(unittest.TestCase):
@data(*d)
@unpack
def test_something(self,dataid,caseid,username,password,captcha,expectedid):
print('数据编号,用例编号,用户名,密码,验证码,预期结果编号')
print(dataid,caseid,username,password,captcha,expectedid)#为了调试,打印这些形参变量的值
运行,查看结果里某几行的数据被打印出来

建议:修改类名MyTestCase为BgLoginTestCase,修改测试方法名test_something为test_bg_login

6、实现测试固件:
(1)导入Selenium测试要用到的一些资源
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
(2)实现setUp方法:做准备工作
(3)实现tearDown方法:做清理收尾工作
@ddt
class BgLoginTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(15)
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()
7、实现测试方法里的**业务步骤**:
(1)打开后台页,输入三个文本框里的数据,点击“进入管理中心”按钮
(2)思考:测试数据,既有有效的数据,也可以无效的数据(为空、错误),操作时,有没有区别?
可以使用send_keys方法直接输入,就算它的空白的值,输入空白和没有输入是一样的。
self.driver.get('http://localhost/upload/admin/privilege.php?act=login')# 打开后台登陆页
self.driver.find_element(By.NAME,'username').send_keys(username)#输入用户名
self.driver.find_element(By.NAME,'password').send_keys(password)#输入密码
self.driver.find_element(By.NAME,'captcha').send_keys(captcha)#输入验证码
self.driver.find_element(By.CLASS_NAME,'button').click()#点击“进入管理中心”按钮
(3)**问题:**怎么做检查?有时成功,有时出现提示?
在不同的情况下做不同的检查,可以通过判断“数据”csv传入到测试方法里的“预期结果编号”等于不同的值,来判断不同的情况。
(4)判断语法结构:if……elif……elif……
# 判断“预期结果编号”等于不同的值,就要做不同的检查
# 注意:从csv里读取的数据,数据类型默认都是__str
if expectedid=='1':# '1'不要写成1
print('做第1种检查')
elif expectedid=='2':
print('做第2种检查')
elif expectedid=='3':
print('做第3种检查')
elif expectedid=='4':
print('做第4种检查')
elif expectedid=='5':
print('做第5种检查')
elif expectedid=='6':
print('做第6种检查')
else:
print(f'这是无效的预期结果编号:{expectedid}')
8、**实现每一种检查**的分支里的具体代码:
(1)第1种检查:登陆进入Ecshop后台(登陆成功)
**分析:**怎么才能检查登陆后台成功?

结论:如果当前网页里出现“退出”这个元素,我们可以认为登陆是成功的。
**思考:**ecshop后台登录后的页面里使用了frame,嵌套了网页,需要在子网页里找到“退出”就代表出现“退出”这个元素,也就代表登陆成功了。
调查:“退出”这个元素属于哪个frame?

进一步思考:考虑到代码的健壮性,需要先保证这个frame是出现在主网页里,然后再切换进入该Frame的子网页内,在子网页里查找“退出”。
难点:****如何检查元素出现?设计一个独立的通用方法(假设名称叫做:is_element_present),用于判断某元素是否出现,定位元素的条件作为方法的参数,让它返回布尔值。
尝试(try)查找该元素(find_element),如果能查找到,就是元素出现(return True),否则(except NoSuchElementException),就是元素没有出现(return False)。
from selenium.common.exceptions import NoSuchElementException
# 自定义方法:判断元素是否出现
# 参数:how----By.XXX 怎么定位
# what---str,用什么数据定位
# 返回值:布尔值(是否出现)
def is_element_present(self,how,what):
try:
self.driver.find_element(how,what)
except NoSuchElementException:
return False#定位失败,元素没有出现
return True#定位成功,元素出现
if expectedid=='1':# '1'不要写成1
print('做第1种检查')
# 检查id属性值等于header-frame的元素出现在主网页里
result11=self.is_element_present(By.ID,'header-frame')
self.assertTrue(result11)
# 切换进入到id属性值等于header-frame的子网页里
self.driver.switch_to.frame('header-frame')
# 检查文本是”退出“的一个超级链接元素出现在当前子网页里
result12=self.is_element_present(By.LINK_TEXT,'退出')
self.assertTrue(result12)
# 点击”退出“这个超级链接
self.driver.find_element(By.LINK_TEXT,'退出').click()
(2)第2种检查:
1-1、检查弹出信息框(出现信息框,能找到信息框)
1-2、检查信息框里的提示信息是“管理员用户名不能为空”
1-3、点击确定后,检查提示框消失(信息框找不到)
目标:检查出现信息框或检查信息框消失,我们可以自定义一个通用方法(假设叫做:is_alert_present),判断是否有消息框位于当前网页上层。返回布尔值,无参。
尝试(try)切换到消息框(driver.switch_to.alert),如果出现切换异常(except NoAlertPresentException),就代表消息框无法切换(没有出现、或已经消失,return False),否则(切换成功,消息框存在,return True)
from selenium.common.exceptions import NoSuchElementException,NoAlertPresentException
# 自定义方法:判断消息框是否存在(出现或消失)
# 无参
# 返回值:布尔值
def is_alert_present(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException:
return False#消息框不存在(未弹出、或已消失)
return True#消息框已存在
elif expectedid=='2':
print('做第2种检查')
# 检查消息框出现(弹出)
result21=self.is_alert_present()
self.assertTrue(result21)
# 切换到消息框
a2=self.driver.switch_to.alert
# 获得消息框里的文本信息
t2=a2.text
# 点击”确定“
a2.accept()
# 检查文本信息是”管理员用户名不能为空“
self.assertEqual('管理员用户名不能为空',t2)
# 检查消息框已经消失
result22=self.is_alert_present()
self.assertFalse(result22)
运行调试,发现断言失败,分析日志
得出结论:实际的提示信息里汉字前后有一些符号,预期值里不写这些符号,字符串不相等。------实际提示信息站在用户角度不影响,可以考虑修改用例为(- 管理员用户名不能为空!****\n)
self.assertEqual(‘- 管理员用户名不能为空!\n’,t2)

(3)做第3种检查
3-1、检查**跳转到**一个系统信息页(检查当前网页标题以“系统信息”结尾)
3-2、检查信息页里的信息是“您输入的帐号信息不正确。”(因为该网页出现时间很短,只有3秒,检查网页源代码里包含预期信息)
3-3、等待3秒后,检查**跳转回到**登陆页(检查网页标题以“管理中心”结尾)
3-4、检查登录页上的**密码文本框当前内容是空**(定位到密码文本框,使用元素的get_attribute方法,加上"value"作为参数,就可以获得当前内容,检查它等于空字符串即可)
elif expectedid=='3':
print('做第3种检查')
# 检查跳转到一个系统信息页(检查当前网页标题以“系统信息”结尾)
t31=self.driver.title
self.assertTrue(t31.endswith('系统信息'))
# 检查信息页里的信息是“您输入的帐号信息不正确。”(因为该网页出现时间很短,只有3秒,检查网页源代码里包含预期信息)
ps31=self.driver.page_source
self.assertIn('您输入的帐号信息不正确。',ps31)#注意:帐---账
sleep(6)# 等待3秒(注意:一般建议比需求里的时间更长一些)
# 检查跳转回到登陆页(检查网页标题以“管理中心”结尾)
t32=self.driver.title
self.assertTrue(t32.endswith('管理中心'))
# 检查登录页上的密码文本框当前内容是空
v3=self.driver.find_element(By.NAME,'password').get_attribute('value')
self.assertEqual('',v3)#预期值是空字符串
(4)第四种检查
elif expectedid=='4':
print('做第4种检查')
# 检查提示框弹出(出现)
result41=self.is_alert_present()
self.assertTrue(result41)
# 切换到信息框
a4=self.driver.switch_to.alert
# 获得信息框里的文本信息
t4=a4.text
# 点击“确定”
a4.accept()
# 检查信息等于“- 您没有输入验证码!\n”---预期值符号问题!
self.assertEqual('- 您没有输入验证码!\n',t4)
# 检查信息框已消失
result42=self.is_alert_present()
self.assertFalse(result42)

**调试技巧:**希望哪组数据先运行,就把它上移到前面,可以提升调试效率。

(5)第五种检查

获得提示框里的信息,如果信息是多行,也可以检查实际信息里包含第一行文本,然后再检查实际信息包含第二行文本。
elif expectedid=='5':
print('做第5种检查')
result51=self.is_alert_present()
self.assertTrue(result51)
a5=self.driver.switch_to.alert
t5=a5.text
a5.accept()
self.assertIn('管理员用户名不能为空',t5)
self.assertIn('您没有输入验证码',t5)
result52=self.is_alert_present()
self.assertFalse(result52)
import unittest
from utils.read_csv import CSVUtil
from ddt import ddt,data,unpack
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException,NoAlertPresentException
# 读取文件里的测试数据
u=CSVUtil('..\\testdata\\数据_ECShop_后台登录.csv')
d=u.get_list_data()
print(d)#为了调试,打印出来
@ddt
class BgLoginTestCase(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(15)
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()
# 自定义方法:判断元素是否出现
# 参数:how----By.XXX 怎么定位
# what---str,用什么数据定位
# 返回值:布尔值(是否出现)
def is_element_present(self,how,what):
try:
self.driver.find_element(how,what)
except NoSuchElementException:
return False#定位失败,元素没有出现
return True#定位成功,元素出现
# 自定义方法:判断消息框是否存在(出现或消失)
# 无参
# 返回值:布尔值
def is_alert_present(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException:
return False#消息框不存在(未弹出、或已消失)
return True#消息框已存在
@data(*d)
@unpack
def test_bg_login(self,dataid,caseid,username,password,captcha,expectedid):
print('数据编号,用例编号,用户名,密码,验证码,预期结果编号')
print(dataid,caseid,username,password,captcha,expectedid)#为了调试,打印这些形参变量的值
self.driver.get('http://localhost/upload/admin/privilege.php?act=login')# 打开后台登陆页
self.driver.find_element(By.NAME,'username').send_keys(username)#输入用户名
self.driver.find_element(By.NAME,'password').send_keys(password)#输入密码
self.driver.find_element(By.NAME,'captcha').send_keys(captcha)#输入验证码
self.driver.find_element(By.CLASS_NAME,'button').click()#点击“进入管理中心”按钮
# 判断“预期结果编号”等于不同的值,就要做不同的检查
# 注意:从csv里读取的数据,数据类型默认都是str
if expectedid=='1':# '1'不要写成1
print('做第1种检查')
# 检查id属性值等于header-frame的元素出现在主网页里
result11=self.is_element_present(By.ID,'header-frame')
self.assertTrue(result11)
# 切换进入到id属性值等于header-frame的子网页里
self.driver.switch_to.frame('header-frame')
# 检查文本是”退出“的一个超级链接元素出现在当前子网页里
result12=self.is_element_present(By.LINK_TEXT,'退出')
self.assertTrue(result12)
# 点击”退出“这个超级链接
self.driver.find_element(By.LINK_TEXT,'退出').click()
elif expectedid=='2':
print('做第2种检查')
# 检查消息框出现(弹出)
result21=self.is_alert_present()
self.assertTrue(result21)
# 切换到消息框
a2=self.driver.switch_to.alert
# 获得消息框里的文本信息
t2=a2.text
# 点击”确定“
a2.accept()
# 检查文本信息是”- 管理员用户名不能为空!\n“
self.assertEqual('- 管理员用户名不能为空!\n',t2)
# 检查消息框已经消失
result22=self.is_alert_present()
self.assertFalse(result22)
elif expectedid=='3':
print('做第3种检查')
# 检查跳转到一个系统信息页(检查当前网页标题以“系统信息”结尾)
t31=self.driver.title
self.assertTrue(t31.endswith('系统信息'))
# 检查信息页里的信息是“您输入的帐号信息不正确。”(因为该网页出现时间很短,只有3秒,检查网页源代码里包含预期信息)
ps31=self.driver.page_source
self.assertIn('您输入的帐号信息不正确。',ps31)#注意:帐---账
sleep(6)# 等待3秒(注意:一般建议比需求里的时间更长一些)
# 检查跳转回到登陆页(检查网页标题以“管理中心”结尾)
t32=self.driver.title
self.assertTrue(t32.endswith('管理中心'))
# 检查登录页上的密码文本框当前内容是空
v3=self.driver.find_element(By.NAME,'password').get_attribute('value')
self.assertEqual('',v3)#预期值是空字符串
elif expectedid=='4':
print('做第4种检查')
# 检查提示框弹出(出现)
result41=self.is_alert_present()
self.assertTrue(result41)
# 切换到信息框
a4=self.driver.switch_to.alert
# 获得信息框里的文本信息
t4=a4.text
# 点击“确定”
a4.accept()
# 检查信息等于“- 您没有输入验证码!\n”---预期值符号问题!
self.assertEqual('- 您没有输入验证码!\n',t4)
# 检查信息框已消失
result42=self.is_alert_present()
self.assertFalse(result42)
elif expectedid=='5':
print('做第5种检查')
result51=self.is_alert_present()
self.assertTrue(result51)
a5=self.driver.switch_to.alert
t5=a5.text
a5.accept()
self.assertIn('管理员用户名不能为空',t5)
self.assertIn('您没有输入验证码',t5)
result52=self.is_alert_present()
self.assertFalse(result52)
elif expectedid=='6':
print('做第6种检查')
else:
print(f'这是无效的预期结果编号:{expectedid}')
if __name__ == '__main__':
unittest.main()
7572

被折叠的 条评论
为什么被折叠?



