一、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的请求并作出响应。

具体的工作流程:

  1. webDriver打开浏览器并绑定到指定端口。启动的浏览器作为远程服务器remote server
  2. client通过CommandExecuter发送http请求给远程服务器的侦听端口(the wire protocal)
  3. 远程服务器根据原生的浏览器组件来转化为浏览器的本地(native)调用

web Driver用到的协议

  1. 打开浏览器时:HTTP协议
  2. 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的请求并作出响应。

具体的工作流程:

  1. webDriver打开浏览器并绑定到指定端口。启动的浏览器作为远程服务器remote server
  2. client通过CommandExecuter发送http请求给远程服务器的侦听端口(the wire protocal)
  3. 远程服务器根据原生的浏览器组件来转化为浏览器的本地(native)调用

所以web Driver用到的协议

  1. 打开浏览器时:HTTP协议
  2. 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)

 

 五、测试模型

python selenium 不能引用其它文件_测试用例

 

3. selenium中如何保证操作元素的成功率?也就是说如何保证我点击的元素一定是可以点击的?

  1. 首先通过封装find方法,实现wait_for_element_ispresent,这样在对元素进行操作之前保证元素被找到,进而提高成功率。(WebDriverWait)
  2. 在对页面进行click之前,先滚动到该元素(通过Js封装),避免在页面未加在完成前或是在下拉之后才能显示。

 

4. Selenium有几种定位方式?你最偏爱哪一种,为什么?

Selenium有八种定位方式

  1. 与name有关的有三种:name、class_name、tag_name
  2. 与link相关的有两种:link_text、partitial_link_text
  3. 与id有关:id
  4. 全能选手:xpath、css_selector

如果存在id,我一定使用Id,因为简单方便,定位最快。其次是Xpath,因为很多情况下html标签的属性不够规范,无法唯一定位。Xpath是通过相对位置定位

 

5. 如何去定位页面上动态加载的元素?

首先触发动态事件,然后再定位。如果是动态菜单,则需要层级定位。——JS实现(对动态事件封装)

 

6. 如何去定位属性动态变化的元素?

属性动态变化也就是指该元素没有固定的属性值,可以通过:

  1. JS实现,
  2. 通过相对位置来定位,比如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);