第5章
Selenium WebDriver基础
本章先带大家熟悉Selenium WebDriver API基础内容,读者如果是初学者,则建议反复学习本章内容,因为笔者在后期对Selenium WebDriver进行二次封装也是建立在本章基础上进行的。
7min
5.1Selenium简介
在学习Selenium如何使用之前,读者应该简单了解一下Selenium自动化测试工具的原理,并根据自己的需求使用Selenium相应的工具去达到UI自动化测试的目的。
5.1.1Selenium测试准备
要想在Python中使用Selenium,必须先安装Selenium包,再下载浏览器Driver。
1. 安装Selenium包
在Python中Selenium是一个第三方模块包,所以读者需要先使用pip命令对其进行安装,命令如下:
pip install selenium -i https://pypi.douban.io/simple
安装好Selenium之后,读者可以使用pip命令查看是否安装成功,命令如下:
>pip list
PackageVersion
--------- ---------
selenium3.141.0
2. 下载浏览器Driver
通过上述命令安装了Selenium之后,读者还是不能使用Selenium对浏览器进行操作,因为想对浏览器进行操作必须有对应浏览器的Driver。浏览器的Driver是区分版本的,接下来笔者以Chrome浏览器为例,为读者介绍浏览器Driver的下载,步骤如下。
1) 查看浏览器版本
打开Chrome浏览器,单击浏览器右上角“…”→“帮助”→“关于Google Chrome”按钮,此处可以看到浏览器版本信息,如图51所示。
图51Chrome版本
2) 下载对应版本的ChromeDriver
例如笔者安装的Chrome版本是102,所以只需选择下载相近版本的Driver。下载网址如下:
http://chromedriver.storage.googleapis.com/index.html
进入下载页面后,笔者选择最接近的版本102.0.5005.61进行下载,如图52所示。
图52ChromeDriver版本
3) ChromeDriver配置环境变量
由于Python安装时已经配置过环境变量,所以笔者将ChromeDriver放到Python目录下,相当于添加了环境变量。
4) 测试代码
安装完Selenium且下载好对应版本的ChromeDriver后,读者可以使用最简单的Selenium代码打开百度首页。如果代码能通过Chrome浏览器打开百度首页,则表示Selenium准备成功,代码如下:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
5.1.2Selenium工具介绍
笔者使用的是Selenium 3版本,Selenium 3中包含WebDriver、SeleniumIDE、Grid。每个工具有不同的功能,具体如下。
(1) Selenium WebDriver: 提供一套API,可以通过浏览器Driver控制浏览器,从而达到模拟用户操作的目的。WebDriver是Selenium自动化测试中最常用的工具,本书也是针对WebDriver进行详细讲解、封装。
(2) SeleniumIDE是一个图形化工具,对于初学者来讲非常容易上手,但在实际开发过程中一般不会使用SeleniumIDE,所以笔者后边也不会讲解此工具。
(3) Selenium Grid: 使用该工具可以在不同平台和不同机器上分布式运行测试用例。笔者会在后面的章节中做一个简单的介绍。
5.1.3Selenium WebDriver原理
WebDriver采用ServerClient设计模式,Server指的是使用脚本启动的浏览器,Client指的是我们编写的测试脚本。当读者使用测试脚本启动浏览器后,该浏览器就是一个Remote Server,它的职责就是监听Client发送请求,并做出相应的响应。Client测试脚本实现的所有操作会以HTTP请求的方式发给Server端,Server使用浏览器Driver来操作浏览器,操作浏览器的结果Server再使用HTTP请求返回,如图53所示。
图53WebDriver工作流程
5.1.4Selenium Grid原理
Grid的作用就是分布式执行测试用例,Grid的分布式测试由Hub主节点和若干Node节点组成,Hub节点是用来管理Node节点的注册和状态信息,当Hub节点接收远程客户端代码请求调用时会将请求的命令转发给Node节点来执行,如图54所示。
图54Grid简介
例如当自动化测试用例较多又需要在Chrome和Firefox两个浏览器上执行时,一般会部署两台机器分别执行测试用例。有了Grid之后只需启动Hub节点并注册两个需要的Node节点便可以在不同的Node节点中同时执行用例,即分布式执行用例。
5.2WebDriver浏览器操作
Selenium UI自动化测试是针对浏览器进行的,所以进行UI自动化测试的第1步就应该是打开浏览器,然后才能根据自己的需要跳转到某个网站,再去定位某个元素、操作某个元素等。
5.2.1启动浏览器
启动浏览器的代码很简单,只需实例化一个浏览器Driver。笔者实例化了一个Chrome浏览器Driver,用于启动Chrome浏览器,代码如下:
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
示例中,虽然只有一行实例化Chrome浏览器的代码,但当笔者执行该代码后就会打开一个Chrome浏览器。此时的浏览器中没有任何内容,因为笔者并未指定浏览器跳转到哪个具体的网站,如图55所示。
图55启动浏览器
通过前面的学习得知,使用Selenium操作浏览器需要下载对应浏览器的Driver,并将其设置到环境变量中才可以正确地操作浏览器。那么如果浏览器的Driver版本不能正确地执行代码,则会报什么错误?如果浏览器Driver版本正确但没有将Driver设置到环境变量中,则又会报什么错误?带着这两个疑问,笔者将进行针对性测试。
1. 浏览器Driver版本错误
例如在实际工作中,Chrome浏览器的版本进行了自动升级,但笔者并没有下载及升级相应的Chrome Driver,此时执行原有的代码Selenium的报错如下:
#浏览器Driver版本错误
selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 102
Current browser version is 108.0.5359.72 with binary path
C:\Users\test\AppData\Local\Google\Chrome\Application\chrome.exe
当Chrome Driver版本不正确时,Selenium报错为SessionNotCreatedException。提示目前的Chrome Driver只能支持102版本的Chrome浏览器,当前Chrome浏览器的版本是108,所以读者如果在工作中遇到这种报错,则应该下载对应版本的Chrome Driver进行替换。
2. 浏览器Driver没有设置环境变量
如果读者在搭建Selenium环境时忘记了将Chrome Driver配置到环境变量中,则此时执行代码Selenium的报错如下:
#浏览器Driver没有设置环境变量
selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see
https://sites.google.com/a/chromium.org/chromedriver/home
当Chrome Driver没有被配置到环境变量中时,Selenium报错为WebDriverException。提示Chrome Driver需要被配置到环境变量PATH中。
5.2.2导航到网页
以上面的例子打开浏览器后,浏览器中显示的内容为空白,原因是笔者没有指定跳转到哪个具体的网址,接下来笔者将使用自研项目对页面跳转进行测试。
笔者在本地启动自研的测试项目,地址为http://localhost:8080/,其中localhost表示网站的IP地址是本机,8080表示网站的端口。浏览器页面跳转到指定网站的代码很简单,只需调用WebDriver的get()方法,代码如下:
#导航到网页
driver.get('http://localhost:8080/Login')
示例中,笔者在get()方法中传入了自研测试项目的首页地址,执行代码后可以跳转到自研测试项目首页,浏览器的效果如图56所示。
5.2.3最大化浏览器
当笔者启动浏览器并跳转到需要测试的网址后发现了一个问题,即浏览器没有在计算机上全屏显示。如果想要浏览器最大化,则只需调用WebDriver的maximize_window()方法,该方法不需要传入任何参数即可实现浏览器最大化,代码如下:
#最大化浏览器
driver.maximize_window()
图56跳转到测试网址
5.2.4关闭浏览器
假设笔者的自动化测试用例执行完毕,此时就需要关闭脚本打开的浏览器。Selenium中关闭浏览器的方法有两种,一种是使用WebDriver的close()方法,另一种是使用WebDriver的quit()方法。接下来笔者将介绍这两种方法的不同。
1. 关闭当前窗口
假设笔者在自动化测试过程中打开了两个窗口,如图57所示。
图57浏览器打开两个窗口
此时当前窗口是第2个窗口。当笔者只想关闭第2个窗口时,可以调用WebDriver的close()方法,该方法不需要传任何参数,代码如下:
#关闭当前窗口
driver.close()
2. 退出驱动并关闭所有窗口
在上述例子中,当笔者想关闭所有窗口时,需要调用WebDriver的quit()方法,也不需要传任何参数即可关闭所有窗口,当关闭所有窗口同时Driver也就跟着退出了,代码如下:
#退出驱动,关闭所有窗口
driver.quit()
5.2.5总结
以上代码虽然没有涉及使用Selenium操作元素,但每个测试用例在开始和结束时都会执行这些代码,所以笔者对代码进行了总结,后期还会对这些代码进行二次封装,代码如下:
//第5章/new_selenium/my_sel_0.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#关闭所有浏览器
driver.quit()
5.3WebDriver元素定位
通过前面的学习,读者已经可以进入需要测试的网站。接下来读者可以思考一下手工测试会做哪些操作,然后根据手工测试思路进行自动化测试学习。以登录为例,手工测试会先找到用户名和密码输入框,输入用户名和密码,然后单击登录。自动化测试的步骤跟手工测试一样,读者也需要先定位用户名、密码、登录按钮这3个元素。
5.3.1开发者工具
定位元素的工具有很多,前端开发经常会打开浏览器的开发者工具进行定位。打开开发者工具的方式有两种,一种是按F12快捷键; 另一种是在网页上右击,选择“检查”。开发者工具打开后如图58所示,右边有代码的部分就是开发者工具。
图58开发者工具
在图58中,读者在开发者工具中单击最左边的箭头按钮,然后单击用户名输入框,这样就可以定位到用户名输入框的前端代码,代码如下:
示例中,用户名输入框是一个input标签,并且type属性为text。input标签的属性包含placeholder属性、class属性、id属性等,这些属性在实际工作中都可以用来帮助定位元素。
7min
5.3.2id属性定位
以用户名输入框为例,笔者首先尝试使用id属性定位元素,原因是id属性在开发过程中是唯一的,所以使用id属性定位元素一定是唯一的。笔者再次观察用户名输入框的id属性,代码如下:
示例中,用户名输入框id属性值为username,使用id定位元素只需调用WebDriver的find_element_by_id()方法,参数需要传入用户名输入框id属性的值,代码如下:
//第5章/new_selenium/my_sel_1.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#id定位
element = driver.find_element_by_id("username")
print(type(element))
#执行结果
示例中,笔者使用WebDriver的find_element_by_id()方法定位用户名输入框,并将方法返回值赋值给element变量,接着打印element变量的类型。从执行结果可以看出,element变量的类型是WebElement,表示定位成功。
现在读者可以使用id属性定位到元素,那么读者应该考虑一个问题,如果定位的元素不存在,则代码会有什么提示呢?接下来笔者使用find_element_by_id()方法传入错误的参数值,演示元素不存在时代码的报错,代码如下:
//第5章/new_selenium/my_sel_1.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#id定位
element = driver.find_element_by_id("username2")
print(type(element))
print(element)
#执行结果
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="username2"]"}
示例中,笔者调用find_element_by_id()方法时将参数故意错误地写成username2,由于在前端代码中id=username,所以id=username2元素是不存在的。此时执行代码读者可以看到执行结果中报错NoSuchElementException,表示元素不存在,并且指明了id="username2"部分发生了错误,这些报错信息足以让笔者找到问题所在。
5.3.3name属性定位
为了演示name属性定位元素,笔者特意在密码的input标签中添加了name属性,并将name属性的值设置为password,前端代码如下:
相信读者根据前面id属性定位所学知识,可以猜到name属性定位元素只需调用不同的方法。这里笔者就调用WebDriver的find_element_by_name()方法,传入元素name属性值实现name属性元素定位,代码如下:
//第5章/new_selenium/my_sel_2.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#name定位
element = driver.find_element_by_name("password")
print(type(element))
#执行结果
示例中,笔者调用WebDriver的find_element_by_name()方法并传入了密码输入框name属性值password。从执行结果可以看出,密码输入框也是一个WebElement,表示定位成功。
5.3.4class属性定位
HTML中标签的class属性对应的是标签的样式,由于样式并非UI自动化测试的重点内容,所以笔者在前面的章节中并没有进行重点介绍,读者可以简单地将样式理解为元素长什么样子、什么颜色等。
以登录按钮为例,笔者使用开发者工具查看登录按钮的前端代码,代码如下:
在前端代码中,button标签有class属性,button标签外层的div容器也有class属性,那么应该选择哪个class属性值进行定位呢?笔者这里选择的是div标签的class属性值,因为div标签的class属性值更加明确地表示了是一个登录按钮,定位代码如下:
//第5章/new_selenium/my_sel_3.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#class定位
element = driver.find_element_by_class_name("login-btn")
print(type(element))
#执行结果
示例中,笔者使用WebDriver的find_element_by_class()方法,传入div标签的class属性值loginbtn,从执行结果中可以看出class属性定位成功。由于div标签相当于容器,它包含了button标签,所以定位到div标签也就相当于定位到了button标签。读者可以根据HTML结构尝试理解标签之间的关系,这样有助于对后面元素定位的学习。
5.3.5CSS选择器定位
CSS(Cascading Style Sheets)是一种语言,该语言用来描述HTML和XML的元素显示样式。CSS语言中有CSS选择器,在Selenium中也可以使用这些选择器来对元素进行定位操作。笔者在此简单地总结了CSS选择器的一些基本用法,见表51。关于CSS选择器的具体细节读者可以翻阅相关书籍进行详细学习,这里不进行过多讲解。
表51CSS选择器
选择器格式备注
点(.).xxclass选择器
井号(#)#xxid选择器
[attribute=value][name=password]属性选择器
表51中笔者只介绍了3个选择器,即class选择器、id选择器和属性选择器,使用上面这3种选择器就可以把前面学到的id属性定位、name属性定位和class属性定位使用CSS选择器进行实现。
使用CSS选择器定位需要用到WebDriver的find_element_by_css_selector()方法,该方法的参数可以使用id选择器、class选择器、属性选择器。笔者还是以登录为例,分别用这3种不同的选择器定位用户名输入框、密码输入框和登录按钮,代码如下:
//第5章/new_selenium/my_sel_4.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#用户名定位:id选择器
element_username = driver.find_element_by_css_selector("#username")
print(type(element_username))
#密码定位:属性选择器
element_password = driver.find_element_by_css_selector("[name='password']")
print(type(element_password))
#登录按钮定位:class选择器
element_login_btn = driver.find_element_by_css_selector(".login-btn")
print(type(element_login_btn))
#执行结果
示例中,笔者统一使用find_element_by_css_selector()方法定位登录页面的3个元素,定位3个元素时的区别在于传入的参数不同。定位用户名输入框时使用“#id属性值”格式,表示使用的是CSS的id选择器; 定位密码输入框时在方括号中输入标签“name属性=属性值”,表示使用的是CSS的属性选择器; 定位登录按钮时使用“.class属性值”格式,表示使用的是CSS的class选择器。从执行结果可以看出,使用CSS选择器定位3个元素均可以成功。
5.3.6link text定位
link text定位从字面意思上就可以猜测出,这是使用a标签的文本来定位的。笔者以百度首页左上角“新闻”链接为例,先按F12键打开开发者工具,然后抓取前端代码,代码如下:
新闻
从上述代码可以看出,a标签的文本是“新闻”二字,只要读者稍加思索就可以想到,应该调用WebDriver的find_element_by_link_text()方法定位该元素,代码如下:
//第5章/new_selenium/my_sel_5.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('https://www.baidu.com')
#最大化浏览器
driver.maximize_window()
#link text定位
element = driver.find_element_by_link_text("新闻")
print(type(element))
#执行结果
示例中,读者需要注意两个问题。第1个问题,笔者使用百度首页作为测试页面,所以在调用WebDriver的get()方法时,需要传入的参数应该改为百度首页地址; 第2个问题,使用WebDriver的find_element_by_link_text()方法定位a标签元素时,需要传入a标签的文字“新闻”。从执行结果可以看出定位元素成功。
5.3.7partial link text定位
partial link text定位与link text定位方式基本相同,partial link text的英文意思是使用部分文本对a标签元素进行定位,从字面意思可以看出,定位a标签元素时只需传入标签部分文字便可以定位成功。定位时使用WebDriver的find_element_by_partial_link_text()方法,代码如下:
//第5章/new_selenium/my_sel_6.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('https://www.baidu.com')
#最大化浏览器
driver.maximize_window()
#partial link text定位
element = driver.find_element_by_partial_link_text("闻")
print(type(element))
#执行结果
示例中,笔者还是使用百度首页进行测试,但在调用find_element_by_partial_link_text()方法时只传入了新闻的“闻”字,从执行结果可以看出,只传入a标签的部分内容也是可以定位成功的。
5.3.8tag name定位
tag name定位的意思就是通过标签的名字进行定位,但由于一般在同一个页面中同名标签会有多个,所以使用标签名定位并不一定能定位到唯一的标签。笔者还是以登录页面为例,演示如何定位input标签元素,代码如下:
//第5章/new_selenium/my_sel_7.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#link text定位
element = driver.find_element_by_tag_name("input")
print(type(element))
#执行结果
示例中,笔者调用WebDriver的find_element_by_tag_name()方法定位input标签元素,所以传入的参数就是input。从执行结果可以看出定位成功,但读者需要注意的是,当页面包含多个input标签时定位到的是第1个input标签。例如登录页面包含用户名和密码两个输入框,使用find_element_by_tag_name()方法定位到的是用户名输入框。
5.3.9xpath表达式定位
xpath是XML path的简称,它是一种用来确定XML文档中查找信息的语言,HTML可以看作XML的一种实现。xpath定位是笔者后续章节中一直使用的定位方式,读者需要重点关注。对于有id属性或name属性等唯一属性的元素,可以使用简单的xpath进行定位,如果元素没有唯一属性,则可以使用xpath轴进行定位。
10min
1. xpath定位
对于已经有唯一属性的元素来讲xpath定位非常简单,只需找到标签元素并使用唯一属性。笔者总结了xpath简单语法供读者参考,见表52。
表52xpath简单语法
语法格式备注
//html/body/div从根节点开始选取
////input从input标签开始选取
@//input[@id='username']选取input标签的id属性
text()//span[text()='登录']文本定位
contains()//div[contains(@class,'loginbtn')]模糊定位
接下来笔者以登录页面为例,使用xpath定位登录页面上的所有元素,代码如下:
//第5章/new_selenium/my_sel_8.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#用户名定位
element_username = driver.find_element_by_xpath("//input[@id='username']")
print(type(element_username))
#密码定位
element_password = driver.find_element_by_xpath("//input[@name='password']")
print(type(element_password))
#登录按钮定位
element_login_btn_1 = driver.find_element_by_xpath("//div[@class='login-btn']")
element_login_btn_2 = driver.find_element_by_xpath("//span[text()='登录']")
element_login_btn_3 = driver.find_element_by_xpath("//div[contains(@class, 'login-btn')]")
print(type(element_login_btn_1))
print(type(element_login_btn_2))
print(type(element_login_btn_3))
#执行结果
示例中,笔者调用WebDriver的find_element_by_xpath()方法定位元素,由于不同元素有不同的唯一属性,所以笔者分别传入了id属性、name属性、class属性进行定位。对于登录按钮笔者特意使用了多种属性进行定位,包括class属性、text()文本、contains()模糊定位,目的是让读者在使用xpath定位元素时有更多的选择,以便更容易地进行定位。
19min
2. xpath轴定位
除了基础定位方法外xpath还有轴定位方法,即当所要定位的标签元素没有唯一的属性可以定位,但亲戚节点很好定位时,读者可以使用xpath轴先定位元素的亲戚节点,然后通过亲戚节点找到想要的元素。笔者总结了xpath轴定位的语法,见表53。
表53xpath轴定位的语法
轴备注
ancestor选取当前节点的所有先辈(父、祖父等)
ancestororself
选取当前节点的所有先辈(父、祖父等)及当前节点本身
attribute
选取当前节点的所有属性
child
选取当前节点的所有子元素
descendant
选取当前节点的所有后代元素(子、孙等)
descendantorself
选取当前节点的所有后代元素(子、孙等)及当前节点本身
following
选取文档中当前节点的结束标签之后的所有节点
namespace
选取当前节点的所有命名空间节点
parent
选取当前节点的父节点
preceding
选取文档中当前节点的开始标签之前的所有节点
precedingsibling
选取当前节点之前的所有同级节点
self
选取当前节点
其实读者只要明白轴语法英文单词的意思,大概就会猜到轴是用来做什么的了,需要注意的细节就是轴语法如何使用。由于笔者将在后面小节中介绍一个Firefox浏览器xpath定位插件,有了此插件读者可以很方便地获取标签元素的xpath,所以笔者在此只举一些简单的例子。这些例子没有实际意义,但足以让读者理解xpath轴语法应该如何应用,具体如下。
(1) 找到input节点的祖先节点form: //input[@id='username']/ancestor::form。
(2) 找到input节点的父亲节点div: //input[@id='username']/parent::div。
(3) 找到input节点的兄弟节点div: //input[@id='username']/followingsibling::div。
(4) 找到input节点的儿子节点div: //input[@id='username']/child::div。
(5) 找到input节点的后代节点div: //input[@id='username']/descendant::div。
5.3.10By模块定位
上述笔者所讲述的单个元素的定位方法还有另外一种写法,即通过By模块声明定位的方法。当读者使用By声明定位方法时,需要调用WebDriver的find_element()方法,该方法需要传入两个参数,第1个参数是By模块操作的属性,第2个参数是属性值。By模块定位方法见表54。
表54By模块定位方法
方法备注
find_element(By.ID, "")id定位
find_element(By.NAME, "")
name定位
find_element(By.CLASS_NAME, "")
class name定位
find_element(By.CSS_SELECTOR, "")
css选择器定位
find_element(By.LINK_TEXT, "")
链接文本定位
find_element(By.PARTIAL_LINK_TEXT, "")
链接部分文本定位
find_element(By.TAG_NAME, "")
标签名定位
find_element(By.XPATH, "")
xpath定位
虽然笔者在后面的章节中不常使用By模块进行元素定位,但笔者还是以登录页面为例,以By模块的方式实现页面元素的定位,代码如下:
//第5章/new_selenium/my_sel_9.py
from selenium import webdriver
from selenium.webdriver.common.by import By
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#用户名定位
element_username = driver.find_element(By.ID, "username")
print(type(element_username))
#密码定位
element_password = driver.find_element(By.NAME, "password")
print(type(element_password))
#登录按钮定位
element_login_btn = driver.find_element(By.CLASS_NAME, "login-btn")
print(type(element_login_btn))
#执行结果
示例中,笔者使用的是find_element()方法。当笔者想通过id定位用户名输入框时,向第1个参数传入By.ID; 向第2个参数传入用户名输入框id属性的值,其他元素定位以此类推。
5.3.11定位多个元素
除了需要定位单个元素之外,读者在工作中还可能需要定位多个元素,定位多个元素的方法是find_elements_by_id()等。定位单个元素和定位多个元素的区别在于定位单个元素使用的是以find_element开头的方法,而定位多个元素使用的是以find_elements开头的方法。具体见表55。
表55多个元素定位
方法备注
find_elements_by_id()id定位
find_elements_by_name()name定位
find_elements_by_class_name()
class属性定位
find_elements_by_css_selector()
css选择器定位
find_elements_by_link_text()
链接文本定位
find_elements_by_partial_link_text()
链接部分文本定位
find_elements_by_tag_name()
标签名定位
find_elements_by_xpath()
xpath定位
在登录页面中包含两个input标签元素,如果读者想定位这两个input标签,则可以使用WebDriver的find_elements_by_tag_name()方法,代码如下:
//第5章/new_selenium/my_sel_10.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#定位多个元素
elements = driver.find_elements_by_tag_name("input")
print(elements)
#执行结果
[, ]
示例中,笔者使用find_elements_by_tag_name()方法并传入input作为参数,意思是定位到页面上所有的input标签元素。从执行结果可以看出,定位到了两个input标签。
如果读者想要操作每个input标签元素,则可以使用for循环进行遍历,代码如下:
elements = driver.find_elements_by_tag_name("input")
for element in elements:
print(element)
如果读者仅需要操作某个input标签元素,则可以通过下标获取指定元素,前提是读者已经知道input标签在HTML文档中的顺序。例如笔者想要操作用户名输入框,则需要执行的元素的下标为0,代码如下:
element = driver.find_elements_by_tag_name("input")[0]
print(element)
5.3.12XPath插件
在前后端分离的开发过程中,前端开发工程师不可能在每个标签中都添加id或name之类的唯一属性,所以在实际测试工作中,
笔者一般统一使用xpath方式进行定位。由于xpath定位有一定难度,
图59RutoXPath插件
逐级书写会影响测试进度,所以笔者将介绍一款Firefox浏览器插件来解决这一问题。读者可以打开附件组件管理器,搜索RutoXPath Finder然后进行安装,如图59所示。
安装好Ruto插件后,Firefox浏览器的右上角就会出现Ruto的图标。开启Ruto后单击想要进行xpath定位的标签元素,就可以得到元素的多个xpath,读者可以选择其中比较容易理解的xpath进行使用,如图510所示。
图510RutoXPath插件的使用
还是以登录页面为例,笔者使用Ruto插件获取不同的xpath,看一看是否能够成功定位元素,代码如下:
//第5章/new_selenium/my_sel_11.py
from selenium import webdriver
#打开浏览器
driver = webdriver.Chrome()
#导航到网页
driver.get('http://localhost:8080/Login')
#最大化浏览器
driver.maximize_window()
#用户名定位
element_username=driver.find_element_by_xpath("//input[@placeholder='username']")
print(type(element_username))
#密码定位
element_password=driver.find_element_by_xpath("//input[@placeholder='password']")
print(type(element_password))
#登录按钮定位
element_login_btn=driver.find_element_by_xpath("//div[@class='login-btn']//button[1]")
print(type(element_login_btn))
#执行结果
示例中,3个元素的xpath都是由Ruto插件获取的。对于用户名输入框和密码输入框,Ruto插件使用placeholder属性进行定位; 登录按钮定位更加精确,先使用class属性定位到登录按钮的父div标签,再在父div标签中找到第1个button标签。从执行结果可以看出,Ruto插件获取的xpath完全可以正常使用,不会报任何错误。
5.4WebDriver基本操作
学习完标签元素定位后,接下来的工作就是去操作元素了。本节中,笔者会使用第4章HTML基础中的内容进行演示,后面的章节再过渡到笔者自己开发的测试项目上进行二次演示,这样既能兼顾学习的完整性,同时又能达到更好的学习效果。
5.4.1输入操作
在HTML基础中笔者写了一个简单的登录页面,包括用户名输入框、密码输入框和一个登录按钮,代码如下:
用户名:
密码:
1. 输入操作
要对输入框进行输入操作,需要调用WebDriver的定位方法获取Webelement,然后再调用WebElement的send_keys()方法,代码如下:
//第5章/new_selenium/my_sel_html_1.py
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test2.html')
driver.maximize_window()
#输入操作
driver.find_element_by_xpath("//input[@name='username']").send_keys('admin')
driver.find_element_by_xpath("//input[@name='password']").send_keys('123456')
图511输入结果
示例中,笔者调用send_keys()方法并传入用户名和密码,用户名和密码输入成功,如图511所示。
2. 清空操作
当用户名和密码为空时,以上输入操作没有任何问题,但当用户名和密码有默认值时,读者会发现脚本输入的内容会被追加到原内容的后边,这样就会造成登录失败,那么如何解决这一问题呢?
笔者对登录的HTML代码进行了简单修改,给用户名和密码标签增加了value属性,这样用户名和密码就有了默认值,代码如下:
用户名:
密码:
为了解决输入框默认值问题,笔者在定位完元素之后,首先调用WebElement的clear()方法,然后调用该元素的send_keys()方法,这样就能先清除默认值,然后输入新的用户名和密码,代码如下:
//第5章/new_selenium/my_sel_html_2.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test2.html')
driver.maximize_window()
time.sleep(2)
#输入操作
element_username = driver.find_element_by_xpath("//input[@name='username']")
element_username.clear()
element_username.send_keys('admin')
element_password = driver.find_element_by_xpath("//input[@name='password']")
element_password.clear()
element_password.send_keys('123456')
示例中,笔者先定位元素,然后依次调用WebElement的clear()方法和send_keys()方法,结果达到了先清空后输入的目的。另外,笔者在代码中使用了time模块的sleep()方法,该方法是让代码等待几秒,其目的是让读者看清清空操作的过程。
5.4.2单击操作
单击操作不仅指单击按钮,单选框、复选框也是通过单击进行操作的,接下来将一一讲解不同标签元素的单击操作。
1. 按钮单击
还是以登录按钮为例,单击“登录”按钮需要调用WebElement的click()方法,代码如下:
//第5章/new_selenium/my_sel_html_3.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test2.html')
driver.maximize_window()
time.sleep(2)
#输入操作
element_username = driver.find_element_by_xpath("//input[@name='username']")
element_username.clear()
element_username.send_keys('admin')
element_password = driver.find_element_by_xpath("//input[@name='password']")
element_password.clear()
element_password.send_keys('123456')
#单击操作
element_button = driver.find_element_by_xpath("//button[text()='登录']")
element_button.click()
示例中,笔者使用文本属性定位登录按钮,然后调用click()方法对其进行单击操作。
2. 单选框单击
在操作单选框之前,先回忆下单选按钮的前端代码,代码如下:
男
女
示例中,单选框默认选中的选项是“男”,如果笔者想单击选择“女”选项,则需要调用WebElement的click()方法,代码如下:
//第5章/new_selenium/my_sel_html_4.py
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test4.html')
driver.maximize_window()
#选择女
driver.find_element_by_xpath("//input[@value='female']").click()
图512单选结果
示例中,笔者通过观察得出input标签元素的value属性可以区分两个单选框,所以笔者使用value属性进行定位并进行单击操作,执行结果如图512所示。
3. 复选框单击
在操作复选框之前,先回忆下多选按钮的前端代码,代码如下:
自行车
汽车
示例中,复选框默认选中的选项是“汽车”。接下来笔者想在选择时先判断选项是否已被选中,如果没有被选中,则进行单击操作,如果已被选中,则打印该选项已经被选中无须再选择,代码如下:
//第5章/new_selenium/my_sel_html_5.py
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test4.html')
driver.maximize_window()
#定位所有复选框
elements = driver.find_elements_by_xpath("//input[@name='vehicle']")
#遍历并选择
for element in elements:
if element.is_selected():
print("{} 选项已经被选中无须再选择!".format(element.get_attribute("value")))
continue
else:
element.click()
#执行结果
Car 选项已经被选中无须再选择!
示例中,笔者调用WebDriver的find_elements_by_xpath()方法获取所有的复选框,
然后使用for循环对所有复选框进行遍历。调用WebElement的is_selected()方法判断当前复选框是否被选中,如果已被选中,则打印提示并继续循环; 如果没被选中,则调用WebElement的click()方
图513多选结果
法选中该复选框。执行结果如图513所示。
值得注意的是,如果想取消复选框,则只需调用WebElement的click()方法对其进行单击,希望读者不要将事情复杂化,记住复选框都是单击操作即可。
5.4.3下拉列表操作
下拉列表操作比较特殊,操作过程中涉及一个Select类。读者需要先实例化Select类,然后调用select_by_visible_text()方法传入选择项的文本进行选择。
首先回忆下下拉列表HTML代码,代码如下:
代码中,下拉列表默认被选中的选项是“广州”,笔者想将被选中的选项变为“北京”,代码如下:
//第5章/new_selenium/my_sel_html_6.py
from selenium import webdriver
from selenium.webdriver.support.select import Select
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test5.html')
driver.maximize_window()
#定位元素
select_element = driver.find_element_by_xpath("//select[@name='city']")
#实例化Select对象
select = Select(select_element)
#选择北京
select.select_by_visible_text("北京")
图514下拉选中结果
示例中,笔者导入了Select类,当时实例化Select时传入的是select标签元素的xpath,实例化之后就可以调用Select类的select_by_visible_text()方法,传入“北京”进行选中操作。执行结果如图514所示。
当然,选中下拉列表中的选项有多种方法,笔者在这里做了简单总结,读者可以在需要时选择不同的方法,见表56。
表56下拉选择方法
方法备注
select_by_visible_text()
通过文本选中
select_by_value()通过value属性选中
select_by_index()通过下标选中
5.4.4文件上传操作
文件上传操作在前端开发中一般使用input标签并将其type属性设置为file。当用户选择需要上传的文件时,前端开发人员会按照需求对文件进行相应处理。接下来笔者将介绍前端在上传文件时如何处理; WebDriver如何实现文件上传; 第三方工具如何实现文件上传,目的是让读者更加深入地了解文件上传的相关内容。
1. 文件上传实现
以前后端分离系统为例,笔者参考ElementUI中组件的属性,总结出前端开发人员在实现文件上传时一般会考虑以下几点,具体如下。
1) 自动上传
ElementUI中elupload标签有一个autoupload属性,一般单个文件上传时会将该属性设置为True,表示自动上传文件,设置该属性后就不需要用户再单击一个上传按钮进行上传了。
2) 判断文件类型和大小
在上传文件之前,前端开发人员可以调用beforeUpload()方法,在该方法中开发人员可以根据需求判断文件的类型或者文件的大小等。如果文件类型或文件大小不满足要求,则给出错误提示。
3) 调用后端接口
当上传文件合法时,前端开发人员可以调用uploadHttpRequest()方法,在该方法中正式调用后端的上传接口进行文件上传。
2. 文件上传操作
明白了文件上传的原理后,读者就可以调用WebElement的send_keys()方法直接输入文件路径以实现上传功能了。
首先,回忆下简单的HTML文件上传代码,代码如下:
示例中,input标签元素有id属性,所以笔者只需使用id属性进行xpath定位,然后在send_keys()方法中输入文件路径便可以实现文件上传,代码如下:
//第5章/new_selenium/my_sel_html_7.py
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test6.html')
driver.maximize_window()
#文件上传
driver.find_element_by_xpath("//input[@name='file_upload']").send_keys("D://测试.txt")
图515文件上传结果
示例中,笔者使用D盘中名为“测试.txt”的文件进行上传,上传结果如图515所示。
3. 第三方工具文件上传操作
当然,如果读者找不到input标签,无法对其进行输入操作,就需要使用第三方工具进行自动化文件上传操作。笔者这里讲解Selenium如何结合Pywinauto完成文件上传操作。
1) HTML标签和Windows窗口
在使用第三方工具Pywinauto之前,读者应该弄明白什么是HTML网页,以及什么是系统窗口,如图515所示,“选择文件”按钮是HTML标签,Selenium可以操作该按钮,单击“选择文件”按钮后弹出的是系统窗口,Selenium不可以操作弹出的系统窗口,只能使用第三方工具进行操作,如图516所示。
图516文件选择系统弹窗
2) Pywinauto安装
安装Pywinauto非常简单,使用pip命令进行安装即可,代码如下:
pip install pywinauto -ihttps://pypi.doubanio.com/simple/
3) Selenium+Pywinauto实现文件上传
Selenium和Pywinauto工具结合使用时,Selenium只能操作HTML标签,即只能执行到单击“选择文件”按钮,而单击按钮打开的Windows窗口则由Pywinauto工具操作。
(1) Selenium单击input标签报错。
笔者直接调用WebElement的click()方法单击input标签元素时系统会报错,提示如下:
#文件上传
driver.find_element_by_xpath("//input[@name='file_upload']").click()
#执行结果
selenium.common.exceptions.InvalidArgumentException: Message: invalid argument
如果想解决以上报错问题,则需要使用Selenium中的ActionChains动作链类,ActionChains实例化该类后可以模拟移动、单击、双击等不同的鼠标动作。
(2) ActionChains操作。
笔者将单击input标签元素代码修改为先定位input标签元素,然后通过Driver实例化ActionChains动作链,最后调用ActionChains的click()方法单击input标签元素,代码如下:
//第5章/new_selenium/my_sel_html_8.py
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test6.html')
driver.maximize_window()
#单击选择文件按钮
file_input = driver.find_element_by_xpath("//input[@name='file_upload']")
action = ActionChains(driver)
action.click(file_input).perform()
示例中,笔者除调用了ActionChains的click()方法外,还调用了perform()方法。该方法是动作链执行方法,如果不写perform()方法,则ActionChains动作链是不会被执行的。
(3) Pywinauto工具操作。
打开文件并选择系统弹窗后,接下来的工作就应该使用Pywinauto工具来完成了。实例化Pywinauto工具后可以通过Windows窗口的title找到该窗口,然后打开文件所在路径→输入文件名→单击“打开”按钮,即可完成文件上传操作,代码如下:
//第5章/new_selenium/my_sel_html_8.py
import time
import pywinauto
from pywinauto.keyboard import send_keys
#Pywinauto
app = pywinauto.Desktop()
#根据title找到弹出窗口
dialog = app['打开']
#在网址栏输入文件路径
dialog.window(found_index=0, title_re=".*地址.*").click()
send_keys("D:/test") #在网址栏输入文件的路径
send_keys("{VK_RETURN}") #按Enter键
time.sleep(2)
#输入文件名
dialog["Edit"].type_keys("upload_file.txt")
#单击打开按钮
dialog["Button"].click()
示例中,笔者已经在代码上标明了注释信息,这部分不是本书的重点内容,所以笔者不再做具体的解释,如果读者需要使用Pywinauto工具进行文件上传操作,则只需复制代码并更换上传文件的路径和文件名。
5.4.5ActionChains操作
既然在文件上传操作中提到了ActionChains类,接下来笔者就讲解ActionChains类的相关应用。ActionChains叫作动作链,当读者使用ActionChains类的方法时该方法不会被立即执行,而是按照顺序存放在一个队列中,只有当读者调用perform()方法时,队列中的方法才会依次被执行。
还是以登录为例,笔者使用ActionChains类重新编写元素的定位和操作,代码如下:
//第5章/new_selenium/my_sel_html_9.py
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test2.html')
driver.maximize_window()
#找到元素
username = driver.find_element_by_xpath("//input[@name='username']")
password = driver.find_element_by_xpath("//input[@name='password']")
login = driver.find_element_by_xpath("//button[text()='登录']")
#实例化动作链
action = ActionChains(driver)
#编写动作
action.send_keys_to_element(username, 'admin')
action.send_keys_to_element(password, '123456')
action.click(login)
#执行动作
action.perform()
示例中,笔者先定位出3个需要操作的标签元素,然后实例化ActionChains类,并使用action对象对3个标签元素进行动作编写,最后执行ActionChains类的perform()方法执行所有动作。
5.4.6悬停操作
悬停的意思就是将鼠标停放在某个元素上。例如将鼠标悬停在百度的首页的右上角的“设置”上,此时就会出现多个设置功能选项,如图517所示。对这些功能选项直接进行定位操作时会报错,所以需要先将鼠标悬停在“设置”位置,待设置功能选项出现后再进行操作。
图517百度设置
接下来笔者将演示直接操作设置功能选项和鼠标悬停后操作设置功能选项,读者可以从中理解鼠标悬停的作用。
1. 直接操作
按照前面学习过的知识,如果要操作某个元素,则只需定位后操作,笔者依据这个思路对“高级设置”选项进行操作,代码如下:
//第5章/new_selenium/my_sel_html_10.py
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.maximize_window()
#鼠标不悬停,直接单击高级搜索
time.sleep(3)
driver.find_element_by_xpath("//span[text()='高级搜索']").click()
#执行结果
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//span[text()='高级搜索']"}
示例中,笔者直接定位并操作“高级搜索”选项,从执行结果可以看出,代码报错并提示找不到元素。
2. 悬停后操作
既然当直接定位并操作元素时代码会报错,那么就需要像功能测试一样,先将鼠标悬停在“设置”上,再单击“高级设置”选项,代码如下:
//第5章/new_selenium/my_sel_html_11.py
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.maximize_window()
time.sleep(3)
#实例化动作链
action = ActionChains(driver)
#操作设置
setting = driver.find_element_by_xpath("//span[text()='设置']")
action.move_to_element(setting).perform()
#操作高级搜索
search = driver.find_element_by_xpath("//span[text()='高级搜索']")
action.click(search).perform()
示例中,笔者先定位“设置”按钮,然后使用ActionChains类中的move_to_element()方法将鼠标移动到“设置”按钮上,然后定位“高级搜索”选项,使用ActionChains类中的click()方法单击“高级搜索”选项。执行结果如图518所示。
图518百度高级搜索
值得注意的是,笔者在调用了move_to_element()后直接调用了perform()方法,原因是不调用perform()方法鼠标是不会移动到“设置”按钮上的,而鼠标不移动到“设置”按钮上就不会显示“高级搜索”按钮,从而会造成定位操作“高级搜索”按钮时报错。
5.4.7窗口切换操作
为了演示窗口切换操作,笔者对a标签的HTML代码进行修改,在a标签中添加一个属性target并将其值设置为blank,意思是单击a标签时打开一个新的窗口,代码如下:
当读者单击a标签中的文字“百度链接测试”时浏览器会在新窗口打开百度首页,效果如图519所示。
图519两个窗口
接下来笔者将演示直接操作新窗口和窗口切换后操作新窗口,读者可以从中理解窗口切换的作用。
1. 直接操作
按照前面学习过的知识,如果要操作百度窗口的输入框,则只需定位后操作,笔者依据此思路编写百度输入框操作代码,代码如下:
//第5章/new_selenium/my_sel_html_12.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test7.html')
driver.maximize_window()
#操作
time.sleep(1)
driver.find_element_by_xpath("//a[text()='百度链接测试']").click()
time.sleep(1)
driver.find_element_by_xpath("//input[@id='kw']").send_keys("测试")
#执行结果
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//input[@id='kw']"}
示例中,打开百度首页后笔者使用代码直接向百度输入框中输入内容,从执行结果可以看出,Selenium根本找不到百度输入框这个元素。
2. 窗口切换后操作
找不到百度输入框的原因是百度输入框存在于一个新的窗口中,如果想对新窗口的标签元素进行操作,则需要先切换到新窗口。浏览器中每个窗口都有一个窗口句柄,由于该句柄是窗口的唯一标识,所以笔者将使用窗口句柄进行窗口切换,代码如下:
//第5章/new_selenium/my_sel_html_13.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test7.html')
driver.maximize_window()
#操作
time.sleep(1)
driver.find_element_by_xpath("//a[text()='百度链接测试']").click()
time.sleep(1)
#获取所有窗口句柄
handles = driver.window_handles
#遍历窗口句柄
for handle in handles:
#切换窗口
driver.switch_to.window(handle)
#判断窗口title是否包含"百度一下"
if "百度一下" in driver.title:
time.sleep(1)
driver.find_element_by_xpath("//input[@id='kw']").send_keys("测试")
break
else:
continue
示例中,笔者先调用WebDriver的window_handles()方法获取所有的窗口句柄,然后对所有窗口句柄进行遍历。遍历过程中笔者先调用WebDriver的switch_to.window()方法切换窗口,然后判断窗口的title中是否包含“百度一下”字样,如果包含,则操作百度输入框并终止循环,如果不包含,则继续循环。代码的执行效果如图520所示。
图520多窗口操作百度输入框
5.4.8iframe切换操作
当前端开发人员想要在一个页面中包含另一个页面时会使用iframe标签,当Selenium自动化测试要操作iframe标签包含的网页中的内容时需要切换到iframe之内。
1. iframe前端代码
为了让读者更加容易地理解iframe标签,笔者修改了之前的iframe前端代码,新增1个input标签,用于输入,iframe标签的src属性重新赋值为必应首页,代码如下:
我的输入框:
iframe标签在浏览器中的效果如图521所示。
图521iframe标签在浏览器中的效果
2. 不切换iframe操作
从浏览器的效果来看,笔者编写的输入框和必应的输入框在同一个窗口,按理应该可以直接定位操作,接下来笔者尝试直接操作iframe标签中的输入框,代码如下:
//第5章/new_selenium/my_sel_html_14.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test9.html')
driver.maximize_window()
#操作
time.sleep(2)
driver.find_element_by_xpath("//input[@id='my_baidu']").send_keys("栗子")
driver.find_element_by_xpath("//input[@id='sb_form_q']").send_keys("测试")
#执行结果
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//input[@id='sb_form_q']"}
示例中,笔者操作自定义input标签没有报错,操作iframe标签中的输入框时提示定位不到元素,证明iframe标签中的标签不可以直接操作。代码的执行效果如图522所示。
图522不切换iframe操作
3. 切换iframe操作
既然无法直接操作iframe中的标签,那么就应该先切换到iframe标签,然后对其内部的标签进行操作。切换到iframe标签需要先调用WebDriver的switch_to()方法,通过方法返回调用frame()方法,代码如下:
//第5章/new_selenium/my_sel_html_15.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test9.html')
driver.maximize_window()
#操作
time.sleep(2)
driver.find_element_by_xpath("//input[@id='my_baidu']").send_keys("栗子")
#切换iframe
my_iframe = driver.find_element_by_xpath("//iframe[@id='my_iframe']")
driver.switch_to.frame(my_iframe)
driver.find_element_by_xpath("//input[@id='sb_form_q']").send_keys("测试")
示例中,笔者先定位iframe标签元素,然后调用WebDriver的switch_to.frame()方法切换到iframe标签,最后操作必应输入框时不再报错。浏览器的效果如图523所示。
图523切换iframe操作
4. 退出iframe操作
当笔者切换到iframe标签操作完必应的输入框后,如果笔者想再次操作自定义输入框,就需要切换回来,不然代码还是会报错,即定位不到元素。从iframe标签切换回来需要调用WebDriver的switch_to.default_content()方法,代码如下:
//第5章/new_selenium/my_sel_html_16.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test9.html')
driver.maximize_window()
#操作我的输入框
time.sleep(2)
driver.find_element_by_xpath("//input[@id='my_baidu']").send_keys("栗子")
#切换iframe
my_iframe = driver.find_element_by_xpath("//iframe[@id='my_iframe']")
driver.switch_to.frame(my_iframe)
driver.find_element_by_xpath("//input[@id='sb_form_q']").send_keys("测试")
#切换回默认
driver.switch_to.default_content()
driver.find_element_by_xpath("//input[@id='my_baidu']").send_keys("666")
示例中,笔者先调用WebDriver的switch_to.frame()方法,以此切换到iframe标签,再操作其中的内容,然后调用WebDriver的switch_to.default_content()方法切换回默认窗口,在自定义输入框中输入666,执行结果在浏览器中的效果如图524所示。
图524退出iframe操作
5.4.9JavaScript弹框操作
JavaScript是Web编程语言,简称JS。在HTML页面中可以使用JavaScript实现警告弹框或二次确认弹框。与iframe操作相同,如果读者想使用Selenium处理JavaScript的弹框,则需要先切换到JavaScript弹框,然后才能对其进行操作。
1. JavaScript警告框
笔者写了一段简单的前端代码,代码包括一个输入框和一个手机号校验按钮,当读者单击手机号校验按钮时会弹出警告框,代码如下:
//第5章/new_selenium/test13.html
我的窗口
示例中,input标签的onclick属性表示单击操作,当用户单击“手机号校验”按钮时,代码会调用myFunction()方法,该方法会弹出一个警告框,提示用户输入正确的手机号,浏览器的效果如图525所示。
图525JavaScript警告框
2. 警告框操作
从浏览器的效果来看,JavaScript警告框也是在窗口内部,按理可以直接单击“确定”按钮,但实际上警告框操作需要切换到警告框,然后调用Alert类的accept()方法进行确认,代码如下:
//第5章/new_selenium/my_sel_html_17.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test13.html')
driver.maximize_window()
#单击"手机号校验"按钮
time.sleep(1)
driver.find_element_by_xpath("//input[@id='phone_num']").send_keys('123')
driver.find_element_by_xpath("//input[@value='手机号校验']").click()
#处理消息提示框
time.sleep(1)
driver.switch_to.alert.accept()
示例中,笔者先调用WebDriver的switch_to.alert切换到警告框,然后调用Alert类accept()方法单击警告框的“确定”按钮。
3. 再次操作输入框
根据iframe标签切换经验,从iframe A切换到iframe B完成操作后,需要再次切换到默认iframe A才可以继续对iframe A的元素进行操作。那么读者可以思考一下,切换到警告框完成操作后,是否还需要再次切换回来?答案是不需要。接下来笔者演示操作完消息提示框后直接操作输入框,代码如下:
//第5章/new_selenium/my_sel_html_18.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test13.html')
driver.maximize_window()
#单击"手机号校验"按钮
time.sleep(1)
driver.find_element_by_xpath("//input[@id='phone_num']").send_keys('123')
driver.find_element_by_xpath("//input[@value='手机号校验']").click()
#操作消息提示框
time.sleep(1)
driver.switch_to.alert.accept()
#直接操作输入框
driver.find_element_by_xpath("//input[@id='phone_num']").send_keys('456')
图526JavaScript弹框无须切回到默认状态
示例中,笔者先在输入框中输入“123”,然后单击“手机号校验”按钮、单击消息提示框中的“确认”按钮,然后在没有进行任何切换操作的情况下直接在输入框内输入“456”,操作成功后代码没有报错。浏览器的效果如图526所示。
4. JavaScript二次确认框
JavaScript除了可以实现警告框外,还可以实现二次确认框。笔者模拟系统中删除手机号时二次提示是否删除的功能,新建一个删除按钮,当用户单击“删除”按钮时,JavaScript弹出二次确认提示框,询问是否删除,代码如下:
//第5章/new_selenium/test14.html
我的窗口
示例中,笔者还是使用input标签的onclick属性。当用户单击“删除”按钮时,代码会调用myFunction()方法,该方法会弹出一个二次确认框,让用户确认是否删除手机号,浏览器的效果如图527所示。
图527JavaScript二次确认框
5. 二次确认框操作
有了JavaScript警告框的处理经验后,笔者认为JavaScript二次确认框的处理方式应该相同,所以笔者直接编写代码以查看执行结果是否正确,代码如下:
//第5章/new_selenium/my_sel_html_19.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test14.html')
driver.maximize_window()
#单击"删除"按钮
time.sleep(1)
driver.find_element_by_xpath("//input[@value='删除']").click()
#处理删除弹框
time.sleep(1)
#driver.switch_to.alert.accept() #确定
driver.switch_to.alert.dismiss() #取消
示例中,笔者先调用WebDriver的switch_to.alert切换到二次确认框,然后调用Alert类dismiss()方法单击二次确认框的取消按钮,表示不删除手机号。读者可以参考警告框的操作,尝试单击二次确认框的确定按钮。
5.4.10JavaScript操作
Selenium WebDriver除了可以处理JavaScript弹框外,还可以执行JavaScript命令。笔者先带领读者了解一下JavaScript如何定位标签元素,然后带领读者使用JavaScript处理下只读属性的标签元素。
1. JavaScript基础
读者可以通过JavaScript中的document对象来定位、操作标签元素,所以接下来笔者将重点介绍document对象。
1) JavaScript对象
JavaScript的主要对象包括两个,一个是window对象; 另一个是document对象。
(1) window对象: 在JavaScript中,一个浏览器窗口就是一个window对象。
(2) document对象: window对象存放了这个页面的所有信息,为了更好地分类处理这些信息,window对象下面又分为很多对象,其中document对象包含了整个HTML文档,可以访问文档中的内容及其所有页面元素。JavaScript通过document对象获取标签元素。
2) document定位元素
笔者编写了一个简单的页面,页面中只包含一个输入框,代码如下:
//第5章/new_selenium/test15.html
图528只读输入框
示例中,input标签元素为只读模式,输入框中的值为电话号“13611111111”,浏览器的效果如图528所示。
仔细观察input标签元素的属性,笔者发现input标签元素包含id属性,所以笔者决定使用id属性进行定位。首先需要按F12快捷键打开开发者工具,然后进入控制台页面并调用了document对象的getElementById()方法,参数传入的是input标签的id属性值,代码如下:
document.getElementById('phone_num')
在控制台页面输入示例代码后,按Enter键后就可以看到控制台中打印出input标签元素,表示定位input标签元素成功,如图529所示。
图529JavaScript的id定位
3) document修改属性值
定位到标签元素后,读者可以通过value属性获取标签的值,代码如下:
document.getElementById('phone_num').value
如果读者想要修改标签元素的值,则可以先获取标签元素的值,然后对其进行重新赋值,代码如下:
document.getElementById('phone_num').value = 13622222222
示例中,笔者对电话号码输入框进行了重新赋值,如图530所示。
图530JavaScript重新赋值
2. JavaScript中xpath定位
前面示例中笔者讲解了如何使用JavaScript进行id定位、赋值标签元素,但笔者在后面的代码中需要统一使用xpath定位,所以读者需要了解如何使用JavaScript进行xpath定位。
1) JavaScript的xpath定位方法
JavaScript中的xpath定位需要用到document的evaluate()方法,格式如下:
document.evaluate(
xpathExpression,
contextNode,
namespaceResolver
resultType,
result)
document的evaluate()方法可以根据传入的xpath表达式及其他参数返回一个xpathResult对象,读者可以通过这个xpathResult对象来定位和操作元素。evaluate()方法的参数的具体含义如下。
(1) xpathExpression: 需要定位的元素的xpath路径。
(2) contextNode: 本次查询的上下文节点,通常传入document。
(3) namespaceResolver: 通常用来解析xpath内的前缀,以便对文档进行匹配。通常传入null。
(4) resultType: 指定所返回的 xpathResult的类型。具体类型如下。
XPathResult.ANY_TYPE: 适合于xpath表达式的数据类型。
XPathResult.ANY_UNORDERED_NODE_TYPE: 返回匹配节点的集合,顺序可能和文档中的不一样。
XPathResult.BOOLEAN_TYPE: 返回boolean类型。
XPathResult.FIRST_ORDERED_NODE_TYPE: 返回文档中匹配节点的第1个节点。
XPathResult.NUMBER_TYPE: 返回num类型。
XPathResult.ORDERED_NODE_ITERATOR_TYPE: 返回匹配节点的集合,顺序和文档中的一样。
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: 返回一个节点集合片段,在文档外捕获节点,这样将来对文档的任何修改不会影响节点集合。节点集合中的顺序要和文档中的一样。
XPathResult.STRING_TYPE: 返回一个string类型。
XPathResult.UNORDERED_NODE_ITERATOR_TYPE: 返回匹配节点的集合,顺序可能和文档中的不一样。
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: 返回一个节点集合片段,在文档外捕获节点,这样将来对文档的任何修改不会影响节点集合。节点集合中的顺序没有必要和文档中的一样。
(5) result: 用于存储查询结果,通常传入 null。此时将创建新的 xpathResult对象。
2) JavaScript的xpath定位方法实战
以上介绍了document的evaluate()方法和参数,为了让读者更好地认识该方法如何使用,笔者还是使用手机号输入框来演示xpathResult对象获取、xpath对应的标签获取和xpath对应的标签的值的获取。
(1) 获取xpathResult对象。
笔者想在文档中根据xpath获取输入框标签元素,并且如果文档中有多个相同的输入框,则按顺序获取第1个,所以将resultType参数传入XPathResult.FIRST_ORDERED_NODE_TYPE,代码如下:
#获取xpathResult对象
document.evaluate("//input[@id='phone_num']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
示例中,读者只需关注evaluate()方法的xpathExpression参数和resultType参数,其他参数按照笔者的写法传入。XPathResult.FIRST_ORDERED_NODE_TYPE表示按顺序匹配文档中节点的第1个节点。控制台的效果如图531所示。
图531获取xpathResult对象
(2) 获取xpath对应的标签。
获取xpathResult对象后,读者可以使用该对象获得input标签元素,代码如下:
#获取xpath对应的标签
document.evaluate("//input[@id='phone_num']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
示例中,singleNodeValue用于匹配文档中的第1个节点,如果没有匹配到节点,则返回 null。控制台的效果如图532所示。
图532获取input标签元素
(3) 获取xpath对应标签的value值。
获取input标签元素后,可以使用value属性获取input标签元素的值,代码如下:
#获取xpath对应标签的value值
document.evaluate("//input[@id='phone_num']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value
笔者将input标签的value属性的默认值设置为“13611111111”,所以使用JavaScript命令获取的值也应该是该值,控制台的效果如图533所示。
图533获取value值
3. JavaScript处理只读标签元素
笔者已经将JavaScript如何操作标签元素讲解了一遍,相信读者也有了一定的了解。接下来笔者将举例说明在WebDriver中如何使用JavaScript的xpath定位来处理只读标签的问题。
1) 只读标签
为了让读者记忆深刻,笔者再次对input标签进行介绍,代码如下:
手机号:
示例中,input标签元素的disabled属性表示标签只读,即用户不能修改其值。
2) WebDriver操作只读标签
上述input标签元素如果使用WebDriver操作,则直接进行操作时代码会报错,代码如下:
//第5章/new_selenium/my_sel_html_21.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test15.html')
driver.maximize_window()
#修改只读输入框
time.sleep(1)
driver.find_element_by_xpath("//input[@id='phone_num']").send_keys("136222222")
#执行结果
selenium.common.exceptions.ElementNotInteractableException: Message: element not interactable
示例中,执行结果提示元素不可交互,即元素为只读,表示不能被修改,所以如果读者想修改只读属性的元素,则需要借助JavaScript脚本进行操作。
3) WebDriver执行JavaScript命令操作只读标签方法(1)
前面学习过如何使用JavaScript对标签元素进行定位和操作,但当时的操作都是在开发者工具的控制台中完成的。如果想使用Selenium WebDriver调用JavaScript命令,则需要用到WebDriver的execute_script()方法,该方法的参数传入JavaScript命令即可,代码如下:
//第5章/new_selenium/my_sel_html_22.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test15.html')
driver.maximize_window()
#执行JavaScript代码
time.sleep(1)
js_command = "document.evaluate({}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)" \
".singleNodeValue" \
".removeAttribute('disabled')"\
.format("\"//input[@id='phone_num']\"")
print(js_command)
driver.execute_script(js_command)
#修改只读输入框
driver.find_element_by_xpath("//input[@id='phone_num']").clear()
driver.find_element_by_xpath("//input[@id='phone_num']").send_keys("13622222222")
#执行结果
document.evaluate("//input[@id='phone_num']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.removeAttribute('disabled')
图534删除disabled属性后操作
示例中,笔者将JavaScript命令作为字符串赋值给js_command变量。值得注意的是,笔者在JavaScript命令中使用了removeAttribute()方法,表示删除标签元素的属性。笔者的目的是删除disabled属性,所以在removeAttribute()方法中传入disabled参数。
有了删除标签元素disabled属性的JavaScript命令后,笔者调用WebDriver的execute_script()方法并传入该JavaScript命令,执行后即可删除input标签元素的disabled属性了。删除了input标签元素的只读属性之后就可以正常对其进行操作了,浏览器的效果如图534所示。
4) WebDriver执行JavaScript命令操作只读标签方法(2)
在开发过程中,前端开发人员可能不会设置disabled属性,而是设置了readonly属性,该属性也可以让输入框变为只读,代码如下:
手机号:
如果此时读者在JavaScript脚本的removeAttribute()方法中仍然传入disabled参数,则执行代码后会报错,报错信息如下:
selenium.common.exceptions.InvalidElementStateException: Message: invalid element state
如果想要执行代码后不报错,则读者只需向JavaScript脚本的removeAttribute()方法中传入readonly参数,其他代码无须修改,代码如下:
js_command = "document.evaluate({}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)" \
".singleNodeValue" \
".removeAttribute('readonly')"\
.format("\"//input[@id='phone_num']\"")
5.4.11获取属性值与断言
在UI自动化测试过程中,如果读者进行了登录操作,则该如何判断登录是否成功呢?此时就需要找到系统登录后的一些特有内容,在自动化脚本执行登录后判断这些内容是否存在,在这个过程中涉及如何获取内容、如何进行判断,本节中笔者将对此一一进行介绍。
1. 获取属性值
读者可以根据需要,通过WebElement的get_attribute()方法获取不同的属性值,如属性值、文本值、标签元素内部的标签等。笔者编写了一个简单的HTML代码,用于演示如何获取这些属性值,代码如下:
打篮球
1) 获取文本属性值
获取标签元素的文本内容,需要先获取标签元素,再使用WebElement的get_attribute()方法,向方法中传入参数名innerText即可,代码如下:
//第5章/new_selenium/my_sel_html_23.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test16.html')
driver.maximize_window()
#获取标签元素
time.sleep(1)
hobby = driver.find_element_by_xpath("//div[@id='hobby']")
#获取文本信息
div_innerText = hobby.get_attribute("innerText")
print(div_innerText)
#执行结果
打篮球
示例中,笔者想获取div标签元素中包含的文本信息,首先定位div标签元素,然后使用该标签元素调用get_attribute()方法,并传入innerText,获取div标签中的文本内容“打篮球”。
2) 获取属性值
获取标签元素属性值也需要先获取标签元素,再使用WebElement的get_attribute()方法进行获取,但此时get_attribute()方法的参数需要传入属性名,例如笔者想获取id属性值,则传入id即可,代码如下:
//第5章/new_selenium/my_sel_html_23.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test16.html')
driver.maximize_window()
#获取标签元素
time.sleep(1)
hobby = driver.find_element_by_xpath("//div[@id='hobby']")
#获取id属性值
div_id = hobby.get_attribute("id")
print(div_id)
#执行结果
hobby
示例中,笔者想获取div标签元素的id属性值,还是先定位div标签元素,然后使用该标签元素调用get_attribute()方法,并传入id。获取id的属性值hobby。
3) 获取标签内部的HTML
获取标签元素内部的HTML,使用WebElement的get_attribute()方法且参数需要传入innerHTML,获取的内容包含内部的标签、内部标签的属性、内部的文本,代码如下:
//第5章/new_selenium/my_sel_html_23.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test16.html')
driver.maximize_window()
#获取标签元素
time.sleep(1)
hobby = driver.find_element_by_xpath("//div[@id='hobby']")
#获取div标签中的HTML
div_innerHTML = hobby.get_attribute("innerHTML")
print(div_innerHTML)
#执行结果
打篮球
示例中,笔者想获取div标签元素内部的HTML,首先定位div标签元素,然后使用该标签元素调用get_attribute()方法,并传入innerHTML。获取的内容包括span标签和内部的文本信息。
2. 断言
断言的意思就是判断是否符合预期结果,Python中使用assert进行断言,如果断言的结果为True,则代码不打印任何信息; 如果断言结果为False,则程序会触发异常并打印错误信息,代码如下:
#断言成功,即断言结果为True
assert 1==1
#断言失败,即断言结果为False
assert 1==2, "1不等于2"
#执行结果
AssertionError: 1不等于2
示例中,笔者先使用assert断言1等于1,执行结果没有打印错误信息,说明断言1等于1的结果为True; 接着笔者又使用assert断言1等于2,并设置在断言结果为False时打印“1不等于2”,从执行结果可以看出打印了自定义的错误信息,即断言结果为False。
3. 获取属性值并断言
有了对Python断言的基本了解之后,笔者将UI自动化测试代码和断言相结合,用例执行完成后对结果进行断言操作。
1) 页面跳转断言
在UI自动化测试中经常需要进行页面跳转,每次页面跳转之后读者可以断言跳转是否成功。
(1) 百度首页代码。
笔者首先展示百度首页的HTML代码,由于笔者将使用title进行断言,所有主要关注的也是head标签中的title标签,代码如下:
百度一下,你就知道
(2) 页面跳转断言。
要使用title进行断言,首先需要调用WebDriver的title()方法获取页面的title值,然后使用assert将获取的title值与预期title值进行比较,代码如下:
//第5章/new_selenium/my_sel_html_25.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.maximize_window()
#页面跳转断言
time.sleep(1)
assert driver.title == "百度一下,你就知道", "跳转百度首页失败!"
print(driver.title)
#执行结果
百度一下,你就知道
示例中,笔者断言时添加了错误返回内容,当断言结果为False时打印“跳转百度首页失败!”,当断言结果为True时不打印任何内容。
读者可以思考下,如果断言页面跳转失败,则断言后边的打印语句是否还会继续执行?为了演示,笔者修改断言预期以让断言结果失败,代码如下:
//第5章/new_selenium/my_sel_html_25.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.maximize_window()
#页面跳转断言
time.sleep(1)
#assert driver.title == "百度一下,你就知道", "跳转百度首页失败!"
assert driver.title == "百度一下", "跳转百度首页失败!"
print(driver.title)
#执行结果
Traceback (most recent call last):
File "F:/a-lizi-workspace/lizitest-1/new_selenium_html/my_sel_html_25.py", line 10, in
assert driver.title == "百度一下", "跳转百度首页失败!"
AssertionError: 跳转百度首页失败!
示例中,断言失败,并且只打印了笔者自定义错误信息,没有再执行断言后的打印语句,所以读者需要记住断言失败时其后面的语句不会继续执行。
2) 文本断言
除了页面跳转可以断言外,新增用户的场景同样可以断言。在实际工作中新增用户单击“确定”按钮后,系统会跳转到用户列表页并重新请求列表数据,新增数据一般会放在列表的第1条。接下来以此场景为例,笔者演示新增用户后的断言操作。
(1) 表格示例代码。
笔者写了一段简单的HTML表格,表格中包含一条数据,即代表新增的一条数据,代码如下:
//第5章/new_selenium/test17.html
(2) 新增后断言。
假设需要规定用户名是唯一的,此时读者就可以获取表格中的用户名,然后将其与UI自动化测试时输入的用户名进行比较,代码如下:
//第5章/new_selenium/my_sel_html_26.py
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('F:\\a-lizi-workspace\\lizitest-1\\new_html\\test17.html')
driver.maximize_window()
#操作
time.sleep(1)
first_user = driver.find_element_by_xpath("//table[@id='user_table']//td[1]")
#断言
assert first_user.text == '栗子测试', '第1条数据不是栗子测试'
print(first_user.text)
#执行结果
栗子测试
示例中,笔者调用WebDriver的title()方法获取标签元素的文本,然后将该文本与UI自动化输入内容进行比较,执行结果没有报错且打印了第1条数据的用户名,表示断言成功。
5.4.12下载文件操作
笔者在实际工作中很少使用UI自动化测试进行下载操作,但各个公司要求不同,所以笔者在这里简单介绍下自动下载文件操作,其实使用Selenium WebDriver下载文件只需配置浏览器的一些参数,然后到下载文件页面单击需要下载的文件。接下来笔者将使用不同浏览器演示如何在http://chromedriver.storage.googleapis.com/网站下载指定的chromedriver。
1. Chrome浏览器下载文件
当使用Chrome浏览器下载文件时,需要调用WebDriver的ChromeOptions()方法获取Options对象,然后调用Options的add_experimental_option()方法对浏览器进行设置,设置内容包括禁止下载弹窗,以及设置文件下载路径,设置完成后就可以通过单击下载链接直接下载了,代码如下:
//第5章/new_selenium/my_sel_html_27.py
import time
from selenium import webdriver
#浏览器设置
options = webdriver.ChromeOptions()
prefs = {'profile.default_content_settings.popups': 0, #禁止弹出下载窗口
'download.default_directory': os.getcwd()} #设置文件下载路径
options.add_experimental_option('prefs', prefs)
#实例化驱动时传入设置参数
driver = webdriver.Chrome(chrome_options=options)
driver.get('http://chromedriver.storage.googleapis.com/index.html?path=111.0.5563.19/')
driver.maximize_window()
#下载Windows的驱动
time.sleep(1)
driver.find_element_by_xpath("//a[text()='chromedriver_win32.zip']").click()
2. Firefox浏览器下载文件
当使用Firefox浏览器下载文件时,需要调用WebDriver的FirefoxProfile()方法获取FirefoxProfile对象,然后调用FirefoxProfile的set_preference()方法对浏览器进行设置,设置内容包括设置下载路径和下载文件类型,设置完成后就可以通过单击下载链接直接下载了,代码如下:
//第5章/new_selenium/my_sel_html_28.py
import os
import time
from selenium import webdriver
#浏览器设置
fp = webdriver.FirefoxProfile()
fp.set_preference("browser.download.folderList", 2) #可以自定义下载目录
fp.set_preference("browser.download.dir", os.getcwd()) #指定下载目录
fp.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/zip") #指定下载
#文件类型
#实例化驱动时传入设置参数
driver = webdriver.Firefox(Firefox_profile=fp)
driver.get('http://chromedriver.storage.googleapis.com/index.html?path=111.0.5563.19/')
driver.maximize_window()
#下载Windows的驱动
time.sleep(1)
driver.find_element_by_xpath("//a[text()='chromedriver_win32.zip']").click()
5.5WebDriver元素等待
在实际工作中有时在页面上明明可以看到控件,但代码依然报错并提示找不到元素。此时读者可以考虑是否代码运行得太快,即在控件还没有加载完成就开始定位元素,从而导致定位不到元素并报错。由此引出一个概念叫作元素等待,意思是等待元素加载完成后再进行操作。元素等待有3种方式: 强制等待、隐式等待、显式等待。
5.5.1强制等待
强制等待方式笔者已经在前面的代码中使用过了很多次,即time.sleep()方法。之所以叫作强制等待是因为使用sleep()方法设置3s等待时间后,代码就会等待3s,不会智能地去判断到底应该等待几秒,所以叫作强制等待。以操作百度首页输入框为例,假设笔者设置打开首页3s后操作输入框,可能会发生如下问题。
(1) 如果百度首页输入框在1s之内加载出来,则强制等待就会多等待2s,导致脚本执行速度变慢。
(2) 如果百度首页输入框在10s后才加载出来,则强制等待3s显然不够,脚本一定会报元素不存在。
虽然强制等待有缺点,但也不是说就一定不能使用。如果读者的测试场景是在新增数据提交后必须等待2s才能数据同步成功,就可以用sleep()方法等待2s,然后去做断言,读者需要记住所有的事物存在即合理。
5.5.2隐式等待
隐式等待是全局性的等待,只需设置一次就可以在WebDriver的生命周期内一直生效。隐式等待设置了一个等待时间,当被操作元素在等待时间内加载完成时,可以正常操作该元素; 当被操作元素在等待时间内未加载完成时,操作该元素时代码会报错。
1. 等待时间内可以找到元素
以操作百度首页输入框为例,假设需求是最多等待10s,笔者调用WebDriver的implicitly_wait()方法进行隐式等待,代码如下:
//第5章/new_selenium/my_sel_html_29.py
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.maximize_window()
print(time.strftime("%Y-%m-%d %X", time.localtime()))
driver.implicitly_wait(10)
driver.get("https://www.baidu.com/")
driver.find_element_by_xpath("//input[@id='kw']").send_keys("栗子测试")
print(time.strftime("%Y-%m-%d %X", time.localtime()))
#执行结果
2023-02-17 23:47:00
2023-02-17 23:47:03
示例中,笔者先使用driver.implicitly_wait()方法设置隐式等待,将超时时间设置为10s。从执行结果中可以看出,从打开百度到操作百度输入框用时3s,说明隐式等待很智能,不需要等待10s,定位到元素后就可以直接对其进行操作。
2. 等待时间内找不到元素
还是以操作百度首页输入框为例,为了让隐式等待超时,笔者故意将输入框的xpath改成错误的值,代码如下:
//第5章/new_selenium/my_sel_html_30.py
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.maximize_window()
print(time.strftime("%Y-%m-%d %X", time.localtime()))
driver.implicitly_wait(10)
driver.get("https://www.baidu.com/")
driver.find_element_by_xpath("//input[@id='kw2']").send_keys("栗子测试")
print(time.strftime("%Y-%m-%d %X", time.localtime()))
#执行结果
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//input[@id='kw2']"}
示例中,笔者故意将百度输入框的xpath写错,目的是让代码定位不到元素。此种情况下,隐式等待会在10s内不停地轮询寻找元素,到超时时间10s后,报错并提示定位不到元素。
5.5.3显式等待
跟隐式等待不同,显式等待需要在每个需要操作的元素的前面进行声明。显式等待需要调用WebDriverWait类和expected_conditions模块。
1. WebDriverWait类
WebDriverWait类的主要作用是设置等待超时时间,在WebDriverWait类的使用过程中,笔者一般只传入驱动driver和超时时间timeout两个参数,其他使用默认值即可。格式如下:
WebDriverWait(driver,timeout,poll_frequency=0.5,ignored_exceptions=None)
- driver:浏览器驱动。
- timeout:超时时间,单位:秒。
- poll_frequency:检测的间隔步长,默认为0.5s。
- ignored_exceptions:超时后抛出的异常信息,默认抛出NoSuchElementExeception。
2. expected_conditions模块
expected_conditions模块的主要作用是设置预期判断条件,在expected_conditions模块的使用过程中,判断条件可以有很多个,笔者在这里只列出了两个,这两个条件都是判断元素是否可见,只不过参数不同而已,一个是传定位器locator; 另一个是传元素element,代码如下:
expected_conditions模块
- visibility_of_element_located类:判断元素是否可见,参数为定位器locator。
- visibility_of类:判断元素是否可见,参数为元素element。
3. 显式等待应用
接下来笔者以百度输入框为例,分别使用定位器和元素进行显式等待操作。
1) 定位器显式等待
定位器locator的获取需要调用WebDriver的By类,然后通过locator定位元素,代码如下:
#定位器locator
locator = (By.XPATH, "//input[@id='kw']")
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of_element_located(locator))
2) 元素显式等待
元素的获取笔者已经讲过很多次,只需调用WebDriver的find_element_by_xpath()方法,代码如下:
#元素element
element = driver.find_element_by_xpath("//input[@id='kw']")
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of(element))
4. 等待时间内可以找到元素
还是以百度首页输入框为例,笔者使用元素为参数进行显式等待,代码如下:
//第5章/new_selenium/my_sel_html_31.py
import time
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.maximize_window()
print(time.strftime("%Y-%m-%d %X", time.localtime()))
driver.get("https://www.baidu.com/")
#显式等待
element = driver.find_element_by_xpath("//input[@id='kw']") #定位到元素
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of(bd_input))
#显式等待,条件是直到元素可见
#操作
bd_input.send_keys("栗子测试")
print(time.strftime("%Y-%m-%d %X", time.localtime()))
#执行结果
2023-02-19 08:53:10
2023-02-19 08:53:13
示例中,笔者使用WebDriverWait将超时时间设置为10s,使用expected_conditions模块的visibility_of类将条件设置为元素可见。从执行结果可以看出,代码用了3s的时间完成了操作,说明显式等待也是智能等待。
5. 等待时间内找不到元素
笔者还是故意将百度首页输入框xpath写错,想以此来验证显式等待超时的情况,代码如下:
//第5章/new_selenium/my_sel_html_32.py
import time
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.maximize_window()
print(time.strftime("%Y-%m-%d %X", time.localtime()))
driver.get("https://www.baidu.com/")
#显式等待
element = driver.find_element_by_xpath("//input[@id='kw2']") #定位到元素
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of(bd_input))
#显式等待,条件是直到元素可见
#操作
bd_input.send_keys("栗子测试")
print(time.strftime("%Y-%m-%d %X", time.localtime()))
#执行结果
Traceback (most recent call last):
File "F:/a-lizi-workspace/lizitest-1/new_selenium_html/my_sel_html_32.py", line 11, in
element = driver.find_element_by_xpath("//input[@id='kw2']")
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//input[@id='kw2']"}
示例中,当代码执行时很快就提示不能定位到元素,这个提示是find_element_by_xpath()方法提示的,所以代码并没有运行到WebDriverWait,也就不能验证超时是否生效。
接下来笔者改用定位器显式等待的方式进行测试,代码如下:
//第5章/new_selenium/my_sel_html_33.py
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.maximize_window()
print(time.strftime("%Y-%m-%d %X", time.localtime()))
driver.get("https://www.baidu.com/")
#显式等待
locator = (By.XPATH, "//input[@id='kw2']")
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of_element_located(locator)) #显式等待,条件是直到元素可见
bd_input.send_keys("栗子测试")
print(time.strftime("%Y-%m-%d %X", time.localtime()))
#执行结果
Traceback (most recent call last):
File "F:/a-lizi-workspace/lizitest-1/new_selenium_html/my_sel_html_33.py", line 13, in
bd_input = WebDriverWait(driver, 10).until(expected_conditions.visibility_of_element_located(locator))
selenium.common.exceptions.TimeoutException: Message:
示例中,使用定位器进行显式等待执行时,代码运行到WebDriverWait后一直在不停地定位元素,直到10s后报错并提示TimeoutException表示定位元素超时,证明显式等待生效。
在实际工作中页面上的每个标签元素的加载时间是不同的,而显式等待是针对每个标签元素进行等待的,那么当读者知道某个标签元素加载时间较长时,可以将等待时间设置得长一些,这样会比隐式等待更加灵活。
5.6WebDriver鼠标操作
对于鼠标操作,笔者在自动化测试代码编写过程中不经常使用,这里做一些简单介绍。鼠标操作一般包括单击、双击、右击、拖动、移动到元素上、按下左键等,这些操作方法都包含在ActionChains类中。
笔者在前面的小节中已经对ActionChains类介绍过多次,以鼠标悬停为例回顾如何使用鼠标操作单击百度首页设置中的“高级搜索”项,代码如下:
//第5章/new_selenium/my_sel_html_34.py
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.maximize_window()
#实例化动作链
action = ActionChains(driver)
time.sleep(3)
#将鼠标移动到设置按钮上
setting = driver.find_element_by_xpath("//span[text()='设置']")
action.move_to_element(setting).perform()
#鼠标单击高级搜索
search = driver.find_element_by_xpath("//span[text()='高级搜索']")
action.click(search).perform()
示例中,将鼠标移动到指定标签元素需要调用ActionChains类的move_to_element()方法; 鼠标单击操作需要调用ActionChains类的click()方法。需要注意的是,如果想让与鼠标操作相关的方法生效,则一定要调用perform()方法。
为了方便记忆,笔者对鼠标操作的方法进行了总结。
1. 基本操作
单击、双击、右击等都是鼠标的基本操作,代码如下:
action.move_to_element(元素).perform() #移动到元素上
action.click(元素).perform() #单击元素
action.double_click(元素).perform() #双击元素
action.context_click(元素).perform() #在元素上右击
action.click_and_hold(元素).perform() #在元素上单击并按下
2. 将元素1拖曳到元素2的位置
如果想将元素1拖曳到元素2的位置,则读者可以使用不同的方式实现。方式一,直接调用action.drag_and_drop()方法; 方式二,先调用action.click_and_hold()方法实现鼠标左键单击元素1不放,然后调用release()方法在元素2的位置释放鼠标; 方式三,先调用action.click_and_hold()方法实现鼠标左键单击元素1不放,然后调用move_to_element()方法将鼠标移动到元素2处,最后调用release()方法释放鼠标,代码如下:
action.drag_and_drop(元素1, 元素2).perform()
action.click_and_hold(元素1).release(元素2).perform()
action.click_and_hold(元素1).move_to_element(元素2).release().perform()
3. 将元素移动到指定位置
如果想将元素移动到指定位置,则有两种不同的方式。方式一,可以调用ActionChains类的drag_and_drop_by_offset()方法,传入元素和指定坐标; 方式二,可以先调用ActionChains类的click_and_hold()方法按住元素,再调用drag_and_drop_by_offset()方法移动到指定坐标,最后调用release()方法释放鼠标,代码如下:
action.drag_and_drop_by_offset(元素, 400, 150).perform()
action.click_and_hold(元素).move_by_offset(400, 150).release().perform()
5.7WebDriver键盘操作
在Selenium中提供了Keys类,在该类中定义了不同的按键属性,读者可以通过调用属性来完成键盘操作或键盘组合操作。读者可以使用WebElement类的send_keys()方法来模拟键盘上的所有按键操作。例如笔者想用键盘组合键实现全选、复制、粘贴等功能,代码如下:
//第5章/new_selenium/my_sel_html_35.py
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
driver = webdriver.Chrome()
driver.maximize_window()
#操作百度
driver.get('http://www.baidu.com')
time.sleep(2)
bd_input = driver.find_element_by_xpath("//input[@id='kw']")
bd_input.send_keys("栗子测试")
bd_input.send_keys(Keys.CONTROL, 'a') #全选
bd_input.send_keys(Keys.CONTROL, 'c') #复制
bd_input.send_keys(Keys.ENTER) #按Enter键
#为了显示效果等待5s
time.sleep(5)
#操作必应
driver.get('https://cn.bing.com/')
time.sleep(2)
by_input = driver.find_element_by_xpath("//input[@id='sb_form_q']")
by_input.send_keys(Keys.CONTROL, 'v') #粘贴
by_input.send_keys(Keys.BACK_SPACE) #删除最后一个字
by_input.send_keys(Keys.ENTER) #按Enter键
示例中,当笔者想实现全选操作时,调用send_keys()方法并传入两个参数Keys.CONTROL和'a',表示按组合键Ctrl+A,其他键盘操作读者可以查看Keys类自行尝试。
5.8本章总结
本章将WebDriver API中的重点内容一一进行了举例讲解,虽然还有一些内容没有讲到,但所讲解内容已经足够应对日常工作,对于没有讲解的内容如果读者在工作过程中遇到,则可以随时在网上进行查找分析。
学习了本章后,读者应该能够熟练地使用Firefox插件RutoXPath Finder找到每个元素的xpath,并根据实际情况使用基本操作小节中的内容对元素进行操作。在元素操作过程中,读者需要注意使用显示等待来增加代码的稳定性和执行效率。对于定位不到的元素读者可以从三方面进行思考,第一考虑元素的xpath是否正确; 第二考虑该元素是否在iframe中; 第三考虑元素是否在另一个窗口。相信读者仔细学习本章内容后,不会再对WebDriver API感到陌生,遇到问题也能自己尝试解决。