[TOC]

本文是学习了《Selenium自动化测试完全指南》和其他网络上的教程写出的学习笔记

selenium的等待机制

人们在手工测试网站时,总会自然而然地等待网页加载完毕再进行操作。如果某个局部区域是由Ajax加载的内容,人们要等待局部加载完毕才能进行下一步操作,而不是在空白或残缺的界面上操作。另一方面,如果等待超过一定时间,但页面或局部区域依然没有加载完毕,那么人们很可能将其标记为一处程序错误,要求开发人员去排查这个问题。

Selenium拥有丰富的等待机制,将原本人为的等待转换为由机器去等待,并判断什么时候该进行下一步操作

页面级等待机制

等待页面加载完毕的超时时间,默认为0

1
2
3
4
5
wd.set_page_load_timeout(最长等待秒数)
# 全局设置,会在整个WebDriver实例的生命周期内生效

# 未在规定时间内加载完毕,会抛出异常
selenium.common.exceptions.TimeoutException: Message: timeout

元素级等待机制

现代网站大多数是Ajax型网站,很多页面元素都不再是页面加载完毕就能显示出来,而是要触发特定区域的操作,然后等待目标元素出现在页面上才能进行下一步操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 方法一:强制等待
time.sleep(3)
# 毫无弹性,降低测试效率

# 方法二:隐式等待
wd.implicitly_wait(秒)
# 全局,给find_element...函数增加一个宽限时间

# 方法三:显式等待
# 指定条件判断函数,selenium会每个一定时间检查该条件
from selenium.webdriver.support.wait import WebDriverWait

WebDriverWait(WebDriver实例, 超时秒数, 检测时间间隔[可选], 可忽略异常集合[可选])
# 检测时间间隔:调用until或until_not传入的条件判断函数的间隔时间,默认为0.5s
# 可忽略异常集合:在调用until或until_not中传入的条件判断函数时,如果抛出的是这个集合中定义的异常,代码就不会执行失败,会继续正常执行。默认在集合中只有NoSuchElementException异常。

# webdriver执行等待函数
wdw.until(条件判断函数, ["超时后的自定义异常消息"]) # 等待直到条件判断函数的返回值不为False(且没有抛出可忽略的异常)。
wdw.until_not(条件判断函数, ["超时后的自定义异常消息"]) # 等待直到条件判断函数的返回值为False(如果抛出可忽略的异常,也会当作False处理),和until恰好相反

Selenium预定义等待条件函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from selenium.webdriver.support import expected_conditions

expected_conditions.visibility_of_element_located(目标元素定位)

# 例如
targetLocator = (By.LINK_TEXT,"立即注册")
wdw.until(expected_conditions.visibility_of_element_located(targetLocator)) # 判断该元素是否已经显示

# 预定义中常见的条件函数
visibility_of_element_located(目标元素定位) # 判断该元素是否已经显示
visibility_of(目标元素) # 判断该元素是否已经显示
visibility_of_all_elements_located(目标元素定位) #判断页面上是否存在一个或多个符合定位的元素,且是否全部显示
visibility_of_any_elements_located(目标元素定位) #至少有一个
invisibility_of_element_located(目标元素定位) # 是否未显示

脚本级等待机制

在Selenium中,可以使用execute_async_script函数来执行异步JavaScript脚本,如果异步JavaScript脚本没有指定回调函数或者超过时间期限仍然没有调用回调函数,那么JavaScript脚本可能超时

1
2
# 异步超时时间
wd.set_script_timeout(秒)

对键盘和鼠标进行精准模拟

ActionChains操作链

ActionChains操作链:一种偏向底层的自动化交互方式,它可以实现鼠标移动、单击、右击、双击、鼠标按下或松开、悬停拖曳、按键按下或松开、按组合键等更复杂的操作。

1
2
3
from selenium.webdriver.common.action_chains import ActionChains

ActionChains(webdriver实例)

操作链中包含两种类型的函数,第一种类型是操作设置函数,第二种类型是操作链执行函数(只有一个perform函数)。在调用perform时,将连续执行之前在操作链中设置的操作,然后结束操作链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ActionChains(wd).click(webElement).perform()

# .click()并不执行单击操作,而是对操作进行设置,相当于预约,在操作链中预约了一个单击操作
# .perform():执行操作链中的所有操作(之前预约的click操作此时才会真正执行)

# 链式操作方式,即每个函数都会返回ActionChains实例,以便继续调用ActionChains的操作设置函数,将多个操作组合在一起,形成一个完整的操作链,然后通过perform函数一起执行。
ActionChains(wd)\
.click(webElement)\
.click(webElement)\
.click(webElement)\
.pause(3)\
.perform()

.reset_actions() # 清空操作链中的所有设置

ps: 操作链中涉及的所有WebElement元素在操作链执行时必须同时存在,且处于可操作状态,否则无法执行操作链

ActionChains支持的全部鼠标与键盘操作

函数名称 参数 功能
click element(目标元素可选) 单击,如果没传入目标元素参数,则会单击当前位置
context_click element(目标元素,可选) 右键单击
double_click element(目标元素,可选) 双击
click_and_hold element(目标元素,可选) 单击按住
release 释放
move_to_element element 移动到目标元素

键盘操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from selenium.webdriver.common.keys import Keys

baiduSearchInput = driver.find_element(By.ID, "kw")
baiduSearchInput.send_keys("hello world")
baiduSearchInput.send_keys(Keys.CONTROL, "a") # 全选
baiduSearchInput.send_keys(Keys.CONTROL, "x") # 剪切
baiduSearchInput.send_keys(Keys.CONTROL, "v") # 粘贴
baiduSearchInput.send_keys(Keys.BACKSPACE) # 退格
baiduSearchInput.send_keys(Keys.ENTER)

ActionChains(wd)\
.key_down(Keys.CONTROL)\
.send_keys("a")\
.key_up(Keys.CONTROL)\
.pause(3)\
.perform()

ps: send_keys就足以胜任组合键操作,但它只支持按下操作,如果要实现精准控制,例如对某个键需要按下多少秒再松开,则只能使用ActionChains进行模拟,通过key_down、pause、key_up来精准控制某个键需要按下多少秒。

操作浏览器Cookie

Selenium支持操作浏览器Cookie,包括Cookie的读取、新增和删除。

1
2
3
4
5
6
7
8
9
10
11
# 读取
wd.get_cookies() #获取所有的cookie对象集合
wd.get_cookie(cookie名称) #根据名称获取单个cookie

# 新增
wd.add_cookie(cookie对象) # 传入的cookie对象必须包含name和value两个属性
# 还有四个可选属性,分别为path、domain、secure、expiry

# 删除
wd.delete_all_cookies() #删除全部cookie
wd.delete_cookie(cookie名称) #按名称删除指定cookie

对浏览器窗口或元素截图

1
2
3
4
5
# 对浏览器窗口截图
wd.save_screenshot(截图文件保存路径)

# 对元素截图
wd.find_element(By.ID,"su").screenshot(路径)

为Selenium操作附加自定义事件

EventFiringWebDriver

EventFiringWebDriver,它可以为各类操作添加事件

1
2
3
4
5
6
7
8
9
10
11
12
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver

EventFiringWebDriver(WebDriver实例, AbstractEventListener实例)
# AbstractEventListener是一个抽象类,要使用EventFiringWebDriver,还需要设定自定义事件监听器类,该类必须继承并实现Selenium中的AbstractEventListener类

# 举例
class MyListener(AbstractEventListener):
def before_navigate_to(self, url, driver):
print("Before navigate to %s" % url)
def after_navigate_to(self, url, driver):
print("After navigate to %s" % url)
...

AbstractEventListener

函数 参数 作用
before_navigate_to(self,url,driver) url: 跳转的目标地址 导航前事件,页面在发生跳转前执行的代码
after_navigate_to(self,url,driver) driver: wd实例 导航后事件,页面跳转后
before_navigate_back(self,driver) driver: wd实例 浏览器后退事件,后退操作前执行的代码
after_navigate_back(self,driver) driver: wd实例 浏览器后退事件,后退操作后执行的代码
before_navigate_forward(self,driver) driver: wd实例 浏览器前进事件,前进操作前执行的代码
after_navigate_forward(self,driver) driver: wd实例 浏览器前进事件,前进操作后执行的代码
before_close(self,driver) 浏览器关闭事件,关闭前
after_close(self,driver) 浏览器关闭事件,关闭后
before_find(self,by,value,driver) by表示查找条件类型 查找元素前事件
after_find(self,by,value,driver) value表示查找值 找到元素后事件
before_click(self,element,driver) element表示操作的元素 单击元素前事件
after_click(self,element,driver) 单击元素后事件
before_change_value_of(self,element,driver) 元素值变更前事件
after_change_value_of(self,element,driver) 元素中变更后事件
before_excute_script(self,script,driver) script表示需要执行的脚本 脚本执行前事件
after_excute_script(self,script,driver) 脚本执行后事件

EventFiringWebElement

元素级自定义事件

1
2
3
4
5
6
7
8
9
10
11
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement

# EventFiringWebElement(WebElement实例, EventFiringWebDriver实例)
class MyCustomListener(AbstractEventListener):
...

# 追踪搜索按钮的操作
eventDriver = EventFiringWebDriver(driver, MyCustomListener())
search_btn = EventFiringWebElement(driver.find_element(By.ID, "su"), eventDriver)
search_btn.click()
# 该元素被操作时触发

浏览器启动参数设置

在创建WebDriver实例时,可以配置它的启动参数以进行一些初始设置。这些设置将会在WebDriver的整个生命周期内生效。

1
2
3
wd = webdriver.Chrome(executable_path='chromedriver', port=0, options=None, 
service_args=None, desired_capabilities=None, service_log_path=None, chrome_options=
None, keep_alive=True)
参数 作用 描述
options 启动选项,自定义浏览器选项 例如启用或禁用浏览器的特定功能,设置浏览器窗口的大小,配置代理服务器等。
desired_capabilities 类似于options 主要在早期版本的Selenium中使用
chrome_options 完全等同于options 该参数是早期版本Selenium使用的参数,现在的版本已不推荐使用
executable_path 浏览器驱动程序路径 chrome.exe,如果没有指定,则默认使用环境变量PATH中设置的路径。
service_args 浏览器驱动程序的参数 可以通过chromedriver –help命令查看驱动程序支持的参数
port 驱动程序启用的端口号 如果不填写,则自动使用任意闲置的端口号。等同–port参数。
service_log_path 驱动程序存放日志文件的地址 完全等同于chromedriver命令中的–log-path参数。
keep_alive 是否带上HTTP请求头 Connection: keep-alive,即是否使用长连接。(该参数为布尔类型,默认值为True)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from selenium.webdriver.chrome.options import Options

# 浏览器路径
customOptions.binary_location="浏览器.exe路径"
customOptions.headless = True #无界面运行
wd = webdriver.Chrome(options=customOptions)

# 向浏览器添加命令行参数
customOptions.add_argument("选项名称=选项值")
'''
--user-agent="客户端代理类型":设置请求头的User-Agent,多用于响应式站点或根据User-Agent判断是否移动设备而返回不同网页的场景。
--window-size=宽度值,高度值:设置浏览器的默认窗口大小。
--headless:无界面运行(无窗口),通常用于远程运行,在本地也可加上该参数,提升运行效率。
--start-maximized:设置浏览器默认以最大化窗口运行。
--incognito:设置浏览器以隐身模式(无痕模式)运行。
--disable-javascript:禁用JavaScript代码运行。
--disable-infobars:禁用浏览器正在被自动化程序控制的提示。
'''

# extension,它可以为浏览器添加扩展插件,Chrome专用设定
customOptions.add_extension("Chrome插件.crx文件路径")
customOptions.add_encoded_extension("Chrome插件文件经过Base64编码的字符串")

通过JavaScript执行器进行深度操作

虽然Selenium支持非常丰富的操作,但还是会遇到极少数无法处理的场景。此时可能需要使用JavaScript执行器来扩展Selenium。

1
2
3
4
5
6
7
8
9
# 执行
webdriver.execute_script("JavaScript脚本", 自定义参数集(可选)) #执行同步脚本
webdriver.execute_async_script("JavaScript脚本", 自定义参数集(可选)) #执行异步脚本

# 实际上,将传入的JavaScript脚本以匿名函数的方式在浏览器中执行,以上脚本类似于以下代码。
var anonymous = function () {
Selenium传入的JavaScript脚本...
};
anonymous();

执行同步脚本

返回值与类型转换

execute_script函数的返回值类型会随JavaScript脚本返回值动态改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>------返回值为None的情况------
>脚本没有返回值的情况下,函数返回值为None
>脚本返回值为null的情况下,函数返回值为None
>脚本返回值为undefined的情况下,函数返回值为None
>脚本返回值为NaN的情况下,函数返回值为None
>脚本产生异常的情况下,函数返回值为None
>------返回值为基础类型的情况------
>当脚本返回值为boolean时,函数返回值类型为<class 'bool'>,示例值为True
>当脚本返回值为int时,函数返回值类型为<class 'int'>,示例值为1
>当脚本返回值为float时,函数返回值类型为<class 'float'>,示例值为1.1
>当脚本返回值为string时,函数返回值类型为<class 'str'>,示例值为hello world
>------返回值为引用类型的情况------
>当脚本返回值为array时,函数返回值类型为<class 'list'>,示例值为[1, 2, 3, 4]
>当脚本返回值为object时,函数返回值类型为<class 'dict'>,示例值为{'a': 1, 'b': 2, 'c': 'cc'}
>当脚本返回值为function时,函数返回值类型为<class 'dict'>,示例值为{}
>------返回值为HTML元素的情况------
>当脚本返回值为HTML元素时,函数返回值类型为<class 'selenium.webdriver.remote.webelement.
WebElement'>

ps: 一般来说,不建议通过JavaScript来执行Selenium已经支持的功能。

传入参数

execute_script函数还支持输入参数,然后在JavaScript脚本中通过arguments[0]、arguments[1]等使用这些参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

keyword = "hello world";
baiduSearchInput = driver.find_element(By.ID, "kw")
baiduSearchInput.send_keys(keyword)
driver.find_element(By.ID, "su").click()

driver.execute_script("console.log('搜索关键字为' + arguments[0] + ',当前百度搜索框的内容
为' + arguments[1].value);", keyword, baiduSearchInput)

执行异步脚本

在使用该函数时,虽然JavaScript是异步处理的,但是Selenium脚本是同步执行的。如果Selenium回调函数没有执行,那么execute_async_script函数会处于阻塞状态,只有Selenium回调函数执行后才会执行下一行代码。