一、Selenium基本知识
1. 什么是Selenium?
Selenium是浏览器自动化工具,主要用来Web的自动化测试,以及基于Web的任务管理自动化。它支持的语言有:python、Java、ruby、JavaScript等,并且几乎能在主流的浏览器上运行。
Selenium2.0、Selenium3.0主要由三大部分组成:SeleniumIDE、Selenium WebDriver、Selenoium Grid。
- Selenium IDE:录制和回放脚本,可以模拟用户对页面的真实操作,区别于其他工具:是通过拦截http请求。
- 一般只把录制脚本当作一个辅助功能,因为一个UI节点的细微变化,都可能导致自动化测试工具无法识别,当测试项目项目大时,定位、更新十分困难。
- 其次,录制的脚本有时候人工难以理解。
- Selenium Grid:实现在多台机器上、和异构环境中并行执行测试用例。并行执行不仅节省时间,而且可以同时在不同的浏览器、平台上运行自动化测试脚本。
- Selenium Web Driver:针对各个浏览器而开发,通过原生浏览器支持或者扩展(Chrome webDrive、FireFox WebDriver)直接控制浏览器
VS Selenium RC(Selenium1.0):在浏览器中运行javaScript,使用浏览器内置的JavaScript来翻译和执行selense
Web Driver原理
webDriver是按照client/server模式设计的。client是我们的测试脚本,发送请求;server就是打开的浏览器,用来接收client的请求并作出响应。
具体的工作流程:
- webDriver打开浏览器并绑定到指定端口。启动的浏览器作为远程服务器remote server
- client通过CommandExecuter发送http请求给远程服务器的侦听端口(the wire protocal)
- 远程服务器根据原生的浏览器组件来转化为浏览器的本地(native)调用
以web Driver用到的协议:
- 打开浏览器时:HTTP协议
- client端发送http请求到远程服务器的侦听端口:the wire protocol
其中:
- 有线协议:指的是从点到点获取数据的方式,是应用层的协议。
- HTTP协议:是用于从服务器传输超文本标记语言HTML到客户端的通信协议。是一个应用层协议,由请求/响应构成,是一个标准的客户/服务器模式。是一个无状态的协议。(无状态:对事务没有记忆能力,不会保存这次传输的信息——节约内存)
2. Selenium的特点有:
- 支持录制和回放(Selenium IDE)
- 通过WebDriver,直接控制浏览器,而不是通过拦截HTTP请求,实现真正模仿了用户的操作;同时使用WebDriver能够灵活的获取页面元素(WebDriver),并且提供执行JS的接口
- 能够分布式运行在不同机器和异构环境中(不同浏览器)
3. Selenium的内部运行机制?如何能够跨浏览器使用?——WebDriver原理(&RC原理)
1)RC原理
在Selenium1.0中,是通过Selenium RC服务器作为代理服务器去访问应用从而达到测试的目的。
Selenium RC分为三个部分,Launcher、HttpProxy、Core。
- Launcher用于启动浏览器,把Selenium Core加载到浏览器中,并且把浏览器的代理设置为Selenium Server的Http Proxy。
- Core是一堆JavaScript的集合,所以本质相当于运行这些JavaScript函数来实现对Html页面的操作。——这也是为什么可以运行在几乎所有主流的浏览器上。
然而直接运行JavaScript会有极大的安全漏洞,所以会受到“同源限制”,在这个基础上,Selenium2.0引入了WebDriver。
2)Web Driver原理
webDriver是按照client/server模式设计的。client是我们的测试脚本,发送请求;server就是打开的浏览器,用来接收client的请求并作出响应。
具体的工作流程:
- webDriver打开浏览器并绑定到指定端口。启动的浏览器作为远程服务器remote server
- client通过CommandExecuter发送http请求给远程服务器的侦听端口(the wire protocal)
- 远程服务器根据原生的浏览器组件来转化为浏览器的本地(native)调用
所以web Driver用到的协议:
- 打开浏览器时:HTTP协议
- client端发送http请求到远程服务器的侦听端口:the wire protocol
其中:
- 有线协议:指的是从点到点获取数据的方式,是应用层的协议。
- HTTP协议:是用于从服务器传输超文本标记语言HTML到客户端的通信协议。是一个应用层协议,由请求/响应构成,是一个标准的客户/服务器模式。是一个无状态的协议。(无状态:对事务没有记忆能力,不会保存这次传输的信息——节约内存)
4. 如何提高selenium脚本的执行速度?
1)优化测试用例。
- 尽可能不用sleep、减少使用implicityWait,而使用WebDriverWait/FluentWait,这样可以优化等待时间
- 减少不必要的操作步骤。
2)使用Selenium grid,通过testNG实现并发执行。
说到这里,在编写测试用例的时候,一定要实现松耦合,然后再服务器允许的情况下,尽量设置多线程实现并发运行。
3)设置等待时间、中断页面加载。如果页面加载内容太多,我们可以查看一下加载缓慢的原因,在不影响测试的情况下,可以设置超时时间,中断页面加载。
5. 提高自动化脚本稳定性——减少误报
1. 误报问题。我们一旦测试用例没有通过,则无法完成每日自动构建,但是其实这些测试用例是正确,也不存在BUG。
2. 主要的原因:页面还没有加载完成,我们就开始进行元素定位。
3. 解决方法:重试机制。利用递归封装了一个等待元素的方法。其中,设置最大等待时间为1s,轮询时间为50ms,这个方法会不断轮询,直到方法执行成功或者超过设置的最大等待时间。在我们最好的一次实践中,我们把一个测试用例的误报率从10%降低到0,并且执行时间从原先的45秒降低到33秒。
6. 如何设计高质量自动化脚本
1. 使用四层结构实现业务逻辑、脚本、数据分离。
2. 使用PO设计模式,将一个页面用到的元素和操作步骤封装在一个页面类中。如果一个元素定位发生了改变,我们只用修改这个页面的元素属性
3. 对于页面类的方法,我们尽量从客户的正向逻辑去分析,方法中是一个独立场景,例如:登录到退出,而且不要想着把所有的步骤都封装在一个方法中。
4. 测试用例设计中,减少测试用例之间的耦合度。
7. 你觉得自动化测试最大的缺陷是什么?
1. 一旦项目发生变化,测试用例就需要改进,工作量大。
2. 验证的范围有限,操作更加复杂,比如说简单的一个验证验证码,如果是人工识别很快就可以输入,但是自动化测试中会增添很多困难。那么这个时候速度也不如人工。
3. 不稳定
4. 可靠性不强
5. 成本与收益
二、元素定位
1. ElementNotVisible
1. selenium中hidden或者是display = none的元素是否可以定位到?——用Js修改display = block
1)区分:display= none VS hidden
共同点:都把网页中的元素给隐藏起来了;在selenium中无法直接定位
区别:none:不为隐藏的对象保留其物理空间 看不见/摸不着
hidden:仍占有空间
1. 处理 display:none
页面主要通过"dislay:none"来控制整个下拉框不见。如果直接操作:
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
from selenium.webdriver.support.select import Select
import os
driver = webdriver.Firefox(executable_path="/Users/lesley/Downloads/geckodriver")
file_path = 'file:///' + os.path.abspath('test.html')
driver.get(file_path)
select = driver.find_elements('select')
Select(select).select_by_value('volvo')
WebDriverWait()
driver.quit()
报错:ElementNotVisible
我们可以通过JavaScript来修改display的值
js = 'document.querySelectortAll('select')[0]'.style.display='block';'
select = driver.find_element_by_tag_name('select')
Select(select).select_by_value('Opel')
document.querySelectAll('select'):选择所有的select;[0]表示第几个
style.display='block':修改display=block,表示可见
2. NoSuchElementException
首先,判断一个元素是否显示:is_displayed()
1. Frame/IFrame原因定位不到元素——switch_to_iframe
frame是指:页面中嵌入另一个页面,而webdriver每次只能在一个页面识别,因此需要先定位到相应的frame,对那个页面里的元素进行定位。
此时,有两种方式:
1. iframe存在id 或者name。
首先用switch_to_frame('x-URS-iframe')定位到这个iframe,然后再定位这个iframe中的元素
driver=webdriver.Firefox()
driver.get(r'http://www.126.com/')
driver.switch_to_frame('x-URS-iframe') #需先跳转到iframe框架
username=driver.find_element_by_name('email')
username.clear()
2. iframe不存在name/id。
先定位到iframe,再swith_to_frame
#先定位到iframe
elementIframe= driver.find_element_by_class_name('APP-editor-iframe')
#再将定位对象传给switch_to_frame()方法
driver.switch_to_frame(elementIframe)
如果完成操作后,可以通过switch_to_parent_content()方法跳出当前iframe,或者还可以通过switch_to_default_content()方法跳回最外层的页面。
2. 页面没有加载出来,就对页面中元素进行操作。
——设置等待时间直到元素出现(WebDriveWait(driver,10).until(lambda x:x.find_elemetn_by_id('someId').is_displayed)
获取页面加载状态:
document.readyState
例如:当Selenium点击一个按钮打开一个弹窗,弹窗还没有打开的时候,我们就要使用弹窗上一个按钮。
——>解决:设置等待最大等待时间
1)sleep():设置固定休眠时间
2)implicity_wait():是webDriver提供的一个超时等待,隐的等待一个元素被发现,或者一个命令完成
3)WebDriverWait():同样也是WebDriver提供的方法。在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在,如果超出指定时间检测不到则抛出异常。
WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
# driver:WebDriver的驱动程序
# timeout:最长超时时间,默认以秒为单位
# poll_frequency:休眠时间的间隔时间
# ignore_exception():超时后的异常信息,默认情况下抛出NoSuchElementException
通常与until()或者until_not()方法配合使用
until(method, message="")
# 调用该方法提供的驱动程序作为一个参数,直到返回值不为FALSE
until_not(method, message="")
# 调用该方法提供的驱动程序作为一个参数,直到返回值为FALSE
举例:
1 from selenium.webdriver.support.ui import WebDriverWait
2
3 from selenium import webdriver
4 import time
5
6 driver = webdriver.Firefox(executable_path="/Users/lesley/Downloads/geckodriver")
7 driver.get("https://www.baidu.com/")
8
9 # 添加WebDriverWait
10 element = WebDriverWait(driver, 10).until(lambda driver:driver.find_element_by_id("kw"))
11 is_disappeared = WebDriverWait(driver, 5).until_not(lambda x: x.find_element_by_id("someId").is_displayed())
12 element.send_keys("sbw")
13
14 # 添加智能等待
15 driver.implicitly_wait(5)
16 driver.find_element_by_id("su").click()
17
18 # 添加固定时间等待
19 time.sleep(5)
20
21 driver.quit()
4)WaitFor:配合setTimeout,设置最大等待时间,然后轮询查看是否在指定时间内找到该元素。
1 def waitfor(getter, timeout=3, interval=0.5, *args):
2 starttime = datetime.datetime.now()
3 while True:
4 if getter(args):
5 return
6 else:
7 runtime = datetime.datetime.now() - starttime
8 print runtime
9 if runtime.seconds >= timeout:
10 raise Exception
11 time.sleep(interval)
12
13 current_value = 1
14 def testgetval(args):
15 wanted_value = args[0]
16 global current_value
17 current_value += 1
18 print '%d, %d' % (wanted_value, current_value)
19 return current_value > wanted_value
20
21 if __name__ == '__main__':
22 waitfor(testgetval, 1, 0.3, 2)
23 print '======================='
24 waitfor(testgetval, 1, 0.3, 8)
3. 动态ID无法定位元素——1)直接使用Xpath相对路径;2)根据部分元素定位
如何判断是动态ID?
简单,一般看到元素属性里有拼接一串数字的,就很有可能是动态的。想要分辨,刷新一下浏览器再看该元素,属性值中的数字串改变了,即是动态属性了。
<div id="btn-attention_2030295">...</div>
方式(一)根据相对路径
方式(二)根据部分元素属性定位
driver.find_element_by_xpath("//div[contains(@id, 'btn-attention')]")
driver.find_element_by_xpath("//div[starts-with(@id, 'btn-attention')]")
driver.find_element_by_xpath("//div[ends-with(@id, 'btn-attention')]") # 这个需要结尾是‘btn-attention’
4. 二次定位,如弹出登陆框
——层级定位
# 点击打开菜单栏
driver.find_element_by_xpath("//*[@id='sidebar-collapse']/i").click();
# 点击菜单块
driver.find_element_by_xpath("//*[@id='sidebar']/div[1]/ul/li[2]/a").click();
# 点击“待办中心”
driver.find_element_by_linkText("待办案件").click();
5. 有两个属性相同的元素,但是其中一个是不可见的。——找到符合这个属性且style属性中display=none的元素
driver.find_element_by_xpath("//span[contains(@id, 'sbw')] and not(contains[@style, 'display:none'])")
6. Xpath描述错误
1)通过属性定位元素
find_element_by_xpath("//标签名[@属性='属性值']")
例如:
# id属性:
driver.find_element_by_xpath("//input[@id='kw']")
# class属性:
driver.find_element_by_xpath("//input[@class='s_ipt']")
# name属性:
driver.find_element_by_xpath("//input[@name='wd']")
# maxlength属性:
driver.find_element_by_xpath("//input[@maxlength='255']")
2)通过标签名
driver.find_elment_by_xpath('//input')
3)父子定位元素
查找有父亲元素的标签名为span,它的所有标签名叫input的子元素
driver.find_element_by_xpath("//span/input")
4)通过元素内容
例如:
<p id="jgwab">
<i class="c-icon-jgwablogo"></i>
京公网安备11000002000001号
</p>
则我们可以定位:
# 根据text()
driver.find_elment_by_xpath('//p[contains(text(), '京公网')]')
# 根据class
driver.find_elment_By_xpath('//p[contains(@class, '京公网')]')
5. 组合定位元素
//父元素标签名/标签名的属性值:指的是span下的input标签下class属性为s_ipt的元素
driver.find_element_by_xpath("//span/input[@class='s_ipt']")
6. 多个属性组合定位
指的是input标签下id属性为kw且name属性为wd的元素
driver.find_element_by_xpath("//input[@class='s_ipt' and @name='wd']")
指的是p标签下内容包含“京公网”且id属性为jgwab的元素
find_element_by_xpath("//p[contains(text(),'京公网') and @id='jgwab']")
三、常见控件使用
1. link、button
element.click()
2. Textbox
element.send_keys('test')
3. Upload
element.send_keys('D\test.txt')
4. Mouse Event——ActionChains()
#双击
ActionChains(driver).double_click(element).perform()
#右击
ActionChains(driver).context_click(element).perform()
#拖动
ActionChains(driver).drag_and_drop(element).perform()
#悬停
ActionChains(driver).move_to_element(element).perform()
5. DropDown:
1)<Select>标签的下拉菜单
from selenium.webdriver.support.ui import Select
Select(driver.find_element_by_id('gender')).select_by_value('2')
Select(driver.find_element_by_id('gender')).select_by_index(1)
Select(driver.find_element_by_id('gender')).select_by_visible_text('Male')
2)非<select>标签——层级定位
Dropdown1 = driver.find_element_by_id(‘id’) #先定位到dropdown
Dropdown1.find_element_by_id(“li2_input_2”) #再定位到dropdown中的值
3)使用js实现:
6. Alert
driver.switch_to_alert().accept() # 接收弹窗
driver.switch_to_alert().dismiss() # 取消弹窗
# 获取弹窗的文本消息
Message = driver.switch_to_alert().text
7. Window
driver.refresh() # 刷新
driver.back() # 后退
driver.forward() # 前进
driver.maximize_window() # 最大化
driver.set_window_size(100,200) # 设置窗口大小
driver.switch_to.window(searchwindow)
8. frame
driver.switch_to.frame(ReferenceFrame)
driver.switch_to.parent_frame() # frame需要一级一级切
driver.switch_to.default_content() # 返回最外层
四、等待
1. 显示等待——WebDriverWait()
:等到某个条件成立时继续执行。每隔一段时间检测,超出最大时间则抛出异常
is_disappeared = WebDriverWait(driver, 5).until_not(lambda x: x.find_element_by_id("someId").is_displayed())
2. 隐式等待——implicitly_wait()
隐式等待中的时间并非一个固定的等待时间,它并不影响脚本的执行速度。比如进行某元素的定位时,如果元素可以定位就继续执行,如果目前定位不到就以轮询的方式持续判断该元素是否被定位到,如果超过规定的时间还没定位到就抛出异常。
driver.implicitly_wait(20)
3. 强制等待——sleep()
from time import sleep
sleep(5)
五、测试模型
3. selenium中如何保证操作元素的成功率?也就是说如何保证我点击的元素一定是可以点击的?
- 首先通过封装find方法,实现wait_for_element_ispresent,这样在对元素进行操作之前保证元素被找到,进而提高成功率。(WebDriverWait)
- 在对页面进行click之前,先滚动到该元素(通过Js封装),避免在页面未加在完成前或是在下拉之后才能显示。
4. Selenium有几种定位方式?你最偏爱哪一种,为什么?
Selenium有八种定位方式:
- 与name有关的有三种:name、class_name、tag_name
- 与link相关的有两种:link_text、partitial_link_text
- 与id有关:id
- 全能选手:xpath、css_selector
如果存在id,我一定使用Id,因为简单方便,定位最快。其次是Xpath,因为很多情况下html标签的属性不够规范,无法唯一定位。Xpath是通过相对位置定位
5. 如何去定位页面上动态加载的元素?
首先触发动态事件,然后再定位。如果是动态菜单,则需要层级定位。——JS实现(对动态事件封装)
6. 如何去定位属性动态变化的元素?
属性动态变化也就是指该元素没有固定的属性值,可以通过:
- JS实现,
- 通过相对位置来定位,比如xpath的轴,paren/following-sibling/percent-sibling
8. 点击链接以后,selenium是否会自动等待该页面加载完毕?
不会的。所以有的时候,当selenium并未加载完一个页面时再请求页面资源,则会误报不存在此元素。所以首先我们应该考虑判断,selenium是否加载完此页面。其次再通过函数查找该元素。
11. 如何在定位元素后高亮元素(以调试为目的)?
12. 什么是断言?VS 验证
1)断言(assert):测试将会在检查失败时停止,并不运行后续的检查
优点:可以直截了当的看到检查是否通过
缺点:检查失败后,后续检查不会执行,无法收集那些检查结果状态
2)验证(vertify):将不会终止测试
缺点:你必须做更多的工作来检查测试结果:查看日志——>耗时多,所以更偏向于断言
# 断言验证:百度搜索的标题是否为:百度搜索
# import unittest
try:
self.assertEqual(u"百度搜素", driver.title)
except AssertionError as e:
print("Cannot find this title")
3)Waitfor:用于等待某些条件变为真。可用于AJAX应用程序的测试。
如果该条件为真,他们将立即成功执行。如果该条件不为真,则将失败并暂停测试。直到超过当前所设定的超时时间。 一般跟setTimeout时间一起用。
常用的断言:
1 assertLocation # 判断当前是在正确的页面
2 assertTitle #检查当前页面的title是否正确
3 assertValue # 检查input的值, checkbox或radio,有值为”on”无为”off”
4 assertSelected # 检查select的下拉菜单中选中是否正确
5 assertSelectedOptions # 检查下拉菜单中的选项的是否正确
6 assertText # 检查指定元素的文本
7 assertTextPresent # 检查在当前给用户显示的页面上是否有出现指定的文本
8 assertTextNotPresent # 检查在当前给用户显示的页面上是否没有出现指定的文本
9 assertAttribute # 检查当前指定元素的属性的值
10 assertTable # 检查table里的某个cell中的值
11 assertEditable # 检查指定的input是否可以编辑
12 assertNotEditable # 检查指定的input是否不可以编辑
13 assertAlert # 检查是否有产生带指定message的alert对话框
14 waitForElementPresent # 等待检验某元素的存在。为真时,则执行。
13. 如果有一个按钮,点击该按钮后发出一个ajax call,然后得到返回结果后内容显示到新弹出的一个layer中。在写脚本的时候,点击这个按钮动作是否可以用clickAndWait命令?如果不行,怎么解决?
不能够。Ajax是一种支持动态改变用户界面元素的技术。在Ajax驱动的应用程序中,数据可以从应用服务器检索,然后显示在页面上,而不需要加载整个页面,只有一小部分页面或者元素本身被重新加载。
所以不能够使用ClickAndWait,因为Ajax call不会刷新整个页面,clickAndWait命令会因为等待页面重新加载而出现time out。
也就是说最大的麻烦是判断Ajax调用是否结束。可以用click + pause完成
使用JQuery进行辅助测试:
其中,去哪儿网的题目如下:
一、 UI自动化测试
Qunar机票搜索场景
1) 访问Qunar机票首页http://flight.qunar.com,选择“单程”,输入出发、到达城市,选择today+7日后的日期,点“搜索”,跳转到机票单程搜索列表页。
2) 在列表页停留1分钟,至到页面上出现“搜索结束”。
3) 如果出现航班列表,对于出现“每段航班均需缴纳税费”的行随机点选“订票”按钮,在展开的列表中会出现“第一程”、 “第二程”;
对于没有出现“每段航班均需缴纳税费”的行随机点选“订票”按钮,在展开的列表底部中会出现“报价范围”
4) 如果不出现航班列表,则页面会出现“该航线当前无可售航班”
请使用maven创建java工程,引入Selenium框架,编写WebUI代码,实现上述人工操作和验证。要求能随机验证100个城市对的3个月内的任意搜索条件。
参见答案:
很多人可能第一步就卡住了,怎么选择7天以后的日期呢?
实际上很简单,直接在输入框里输入就好了。因为selenium支持的语言很多,这里就用js写一下。大家用selenium执行这段js就可以搞定了。
var date = new Date();
date.setDate(date.getDate() + 7);
var a_week_later = date.getFullYear() + '-' (date.getMonth()+1) + '-' + date.getDate();
$('input[name=fromDate]').val(a_week_later);