[TOC]
简介
Selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。测试系统功能——创建回归测试检验软件功能和用户需求。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。
上述内容是百度百科上对 Selenium 的介绍。
简单来说,Selenium 是一个 Web 应用自动化测试工具,它可以驱动浏览器执行特定动作,完全模拟真实用户对网页进行的操作。
使用 Selenium 甚至可以抓取浏览器 实时 的页面源码,这对于网页中存在异步渲染的页面获取是非常有用的...
环境搭建
Selenium 支持多种主流浏览器的自动化操作,但不同的浏览器必须由其对应的 WebDriver 进行驱动。
Selenium WebDriver 其实代表两层含义:
-
WebDriver 提供了多种语言的编程接口:
其支持的语言有:C#、JavaScript、Java、Python、Ruby...
注:本篇文章主要介绍使用 Selenium 提供的 Python 版本的 API 来操作浏览器。
-
WebDriver 实现了对应浏览器的自动化操作代码
WebDriver 可以驱动本地浏览器进行自动化操作 ,它使用浏览器厂商提供的自动化操作 API 来操控浏览器和进行自动化测试,这其实就相当于一个真实的用户在操作浏览器。
更确切地说,WebDriver 会和各个浏览器的驱动进程进行交互,传递控制行为给到本地浏览器进行执行,并将结果返回给我们。
下面介绍下 Selenium 具体的环境搭建流程:
注:这里我们使用 Chrome 浏览器进行测试
下载 Chrome 浏览器
-
下载 Chrome 浏览器对应的 WebDriver,并将其设置到系统环境变量上,具体设置方法如下:
- Windows 平台:以管理者权限打开一个控制台,输入以下内容永久设置环境变量:
#
表示下载 WebDriver 的目录 $ setx /m path "%path%; " - Mac、Linux 平台:打开控制台,输入以下内容:
#
表示下载 WebDriver 的目录 $ export PATH=$PATH: >> ~/.profile 最后在控制台中输入
chromedriver
测试下是否设置成功。注:下载的 WebDriver 要和 Chrome 浏览器的版本一致。
Chrome 浏览器的版本可通过在 URL 输入框中输入如下内容进查看:chrome://settings/help
-
安装 Selenium WebDriver 的 Python 库(提供通用编程接口):
# 创建虚拟环境 $ python -m venv venv # 开启虚拟环境(Unix 平台使用:source venv/bin/activate) $ venv\Scripts\activate.bat # 安装 Seleninum $ pip install selenium
-
输入以下代码进行测试:
from selenium import webdriver driver = webdriver.Chrome() driver.get('https://www.baidu.com')
执行该文件,可以看到 Chrome 浏览器弹出来并显示百度页面。
以上,Selenium 的环境搭建就完成了。
下面具体介绍下 Selenium 的自动操作接口功能。
创建浏览器驱动对象
因为 Selenium 支持多种主流浏览器的自动化操作,因此其可创建多种不同的浏览器驱动对象,如下所示:
# 创建 Chrome 浏览器驱动对象
from selenium.webdriver import Chrome
# WebDriver 配置在系统环境变量中
driver = Chrome()
# 或者直接指定 WebDriver 路径
driver = Chrome(executable_path='/path/to/chromedriver')
# 创建 Firefox 浏览器驱动对象
from selenium.webdriver import Firefox
with Firefox() as driver:
#your code inside this indent
# ...
访问网址
Selenium 中模拟浏览器访问网址的接口为:webdriver.get()
,如下所示:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
# 查看当前网址
print(f'current_url: {driver.current_url}')
driver.quit()
WebDriver.get(url)
方法可以加载访问目标url
,比如上述代码,就可以对百度页面进行访问。
注:使用WebDriver.get(url)
加载网页时,Selenium WebDriver 默认使用的加载策略为normal
模式,即WebDriver.get(url)
会阻塞直到页面全部加载完毕后,才会执行下去。
更具体来讲,WebDriver.get(url)
会等到页面设置document.readyState='complete'
且触发load
事件后,才会执行下去。
更多详细内容请查看后文:页面加载策略
延时等待
前面我们已经讲过,默认情况下,Selenium WebDriver 使用的页面加载策略为normal
模式,也即会阻塞等待直到页面完全加载,这种情况下,页面中的任何元素我们都是可以获取得到的。
但是,如果页面存在元素异步生成的情况,那么我们是存在可能无法直接获取得到生成的元素的。比如:
对于上述页面,如果我们直接获取p
元素,如下所示:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
driver = webdriver.Chrome()
driver.get('http://127.0.0.1:5500/index.html')
try:
el = driver.find_element(By.TAG_NAME, "p")
print(f'locate tag p: {el.text}')
except NoSuchElementException:
print('failed to locate p element!!')
finally:
driver.quit()
实际上,我们是无法直接定位到p
元素的,因为p
元素是使用 JavaScript 在页面完全加载 2 秒后才动态生成的,在我们查找的时间点时还未存在,因此会抛出NoSuchElementException
异常。
另一方面,当前主流的 Web 应用架构是 前后端分离,这种架构下,前端涉及到的数据渲染基本上都是采用 Ajax 请求后端数据,然后手动渲染到页面上,因此,我们是无法在页面刚加载完成时,就获取到这些数据的。
针对上述问题,Selenium 采用的解决方案为:延时等待。
在 Selenium 中,延时等待 机制具体方案有如下三种:
-
Implicit wait:在隐式等待模式下,WebDriver 在进行元素定位时,如果找不到该定位元素,就会每隔 500ms 轮询该元素,直至找到或者超出隐式等待时间。
默认情况下,隐式等待时间为0
,表示失能隐式等待模式。针对上文示例页面,隐式等待解决方案的代码如下所示:
from selenium import webdriver from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By driver = webdriver.Chrome() # 设置隐式等待 10 秒 driver.implicitly_wait(10) driver.get('http://127.0.0.1:5500/index.html') try: el = driver.find_element(By.TAG_NAME, "p") print(f'locate tag p: {el.text}') except NoSuchElementException: print('failed to locate p element!!') finally: driver.quit()
-
Explicit wait:显示等待模式是定时(每隔 500ms)轮询给定条件,直到条件为真时才进行元素查找,或者等到
WebDriverWait
设置的超时时间后结束轮询。注:不要同时使用 隐式等待 和 显示等待,否则可能会造成无法预测的延时等待时间。
针对上文示例页面,显式等待解决方案的代码如下所示:
from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait driver = webdriver.Chrome() driver.get('http://127.0.0.1:5500/index.html') try: # 设置 WebDriverWait 的超时时间为 10 秒 wait = WebDriverWait(driver, timeout=10) # 条件为 lambda 表达式 el = wait.until(lambda d: d.find_element_by_tag_name("p")) print(f'locate tag p: {el.text}') except TimeoutException: print('failed to locate p element!!') finally: driver.quit()
WebDriver.until(method, message='')
会轮询method
执行返回值,在规定时间内,method
返回真就结束轮询,并把method
返回值返回给WebDriver.until(method,message='')
方法,所以上述代码中,我们直接让method
定位我们需要的元素,成功找到时,until(..)
方法就可以直接返回该元素。
如果超出规定时间,method
仍返回false
,那么until(..)
方法会抛出TimeoutException
异常。由于显示等待是针对 条件 的判断,在日常使用中,我们的很多操作都需要同步 DOM,因此 Selenium 针对这些常用的操作,预定义了其对应的 预期条件(Expected conditions),对 Python 接口来说,具体包含如下内容:
预期条件 释义 alert_is_present 预期出现提示框 element_located_selection_state_to_be 预期节点元素选择状态 element_selection_state_to_be 预期节点元素选择状态 element_located_to_be_selected 预期节点元素为选择状态 element_to_be_clickable 预期元素可点击(可见+使能) element_to_be_selected 预期元素处于选中状态 frame_to_be_available_and_switch_to_it 预期 frame 可用,同时切换到该 frame 中 visibility_of 预期节点可见(节点必须已经加载到当前 DOM 上) visibility_of_element_located 预期节点可见 visibility_of_all_elements_located 预期指定的所有节点可见 visibility_of_any_elements_located 预期至少一个指定的节点可见 invisibility_of_element 预期节点元素不可见或不存在 invisibility_of_element_located 预期节点元素不可见或不存在 new_window_is_opened 预期新开窗口,同时窗口数量增加 number_of_windows_to_be 预期窗口数量 presence_of_all_elements_located 预期所有节点加载完成 presence_of_element_located 预期节点元素加载完成(无需为可见状态) staleness_of 等待直到预期元素脱离 DOM text_to_be_present_in_element 预期节点文本 text_to_be_present_in_element_value 预期节点元素 value 值 title_contains 预期标题包含相关子串(区分大小写) title_is 预期标题(完全匹配) url_changes 预期 URL 更改 url_contains 预期当前 URL 包含相关子串(区分大小写) url_matches 预期当前 URL 匹配指定模式 url_to_be 预期当前 URL 内容(完全匹配) 更多详细内容,请参考:expected_conditions
采用 预期条件 改写我们上述示例,如下所示:
from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get('http://127.0.0.1:5500/index.html') try: # 设置 WebDriverWait 的超时时间为 10 秒 wait = WebDriverWait(driver, timeout=10) # 条件为 lambda 表达式 el = wait.until( # 预期条件:元素加载完成 EC.presence_of_element_located( (By.TAG_NAME, 'p') ) ) print(f'locate tag p: {el.text}') except TimeoutException: print('failed to locate p element!!') finally: driver.quit()
-
FluentWait:使用 FluentWait,我们可以自定义条件等待超时时间,以及条件轮询间隔时间。具体如下:
driver = Firefox() driver.get("http://somedomain/url_that_delays_loading") wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException]) element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))
其实从上述代码中,可以看到,Python 中的 FluentWait 和 显示等待 没有区别,这是因为不同语言的接口有些许不同,像
FluentWait
在 Python 接口中并没有提供,因为使用WebDriver
就可以设置超时时间和轮询时间,而 Java 接口就提供了FluentWait
类...
元素操作
-
元素定位:对网页页面的操作,通常操作的都是特定元素,因此对 Html 页面元素的定位是一个十分重要的操作。
元素定位可分为如下两种定位类型:
- 单元素定位:获取单个标签元素
- 多元素定位:获取多个标签元素
Selenium 提供的元素定位接口如下表所示:
单元素定位 多元素定位 释义 find_element_by_id find_elements_by_id 通过元素 id 定位 find_element_by_class_name find_elements_by_class_name 通过元素类名定位 find_element_by_tag_name find_elements_by_tag_name 通过元素标签定位 find_element_by_css_selector find_elements_by_css_selector 通过 css 选择器定位 find_element_by_xpath find_elements_by_xpath 通过元素 xpath 定位 find_element_by_name find_elements_by_name 通过元素 name 属性定位 find_element_by_link_text find_elements_by_link_text 通过元素完整超链接定位 find_element_by_partial_link_text find_elements_by_partial_link_text 通过元素部分连接定位 find_element find_elements 通用元素定位接口 注:
find_element(..)
/find_elements(..)
是其他所有元素定位方式的通用接口,具体使用请查看以下示例:# index.html #
Hello Selenium# # from selenium import webdriver from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.get('http://127.0.0.1:5500/index.html') # 单元素定位 try: ele = driver.find_element_by_id('myId') print(f'find_element_by_id ==> {ele.text}') ele = driver.find_element_by_class_name('myClass') print(f'find_element_by_class_name ==> {ele.text}') ele = driver.find_element_by_tag_name('div') print(f'find_element_by_tag_name ==> {ele.text}') ele = driver.find_element_by_css_selector('div#myId') print(f'find_element_by_css_selector ==> {ele.text}') ele = driver.find_element_by_xpath(r'//*[@id="myId"]') print(f'find_element_by_xpath ==> {ele.text}') ele = driver.find_element_by_name('myName') print(f'find_element_by_name ==> {ele.get_attribute("value")}') ele = driver.find_element_by_link_text('selected by link text') print(f'find_element_by_link_text ==> {ele.text}') ele = driver.find_element_by_partial_link_text('link text') print(f'find_element_by_partial_link_text ==> {ele.text}') ele = driver.find_element(By.ID, 'myId') print(f'find_element(By.ID) ==> {ele.text}') ele = driver.find_element(By.CLASS_NAME, 'myClass') print(f'find_element(By.CLASS_NAME) ==> {ele.text}') ele = driver.find_element(By.CSS_SELECTOR, 'div#myId') print(f'find_element(By.CSS_SELECTOR) ==> {ele.text}') ele = driver.find_element(By.XPATH, r'//*[@id="myId"]') print(f'find_element(By.XPATH) ==> {ele.text}') except NoSuchElementException as e: print(e) # 多元素获取 for ele in driver.find_elements_by_css_selector('div.myClass'): print(f'find_elements_by_... ===> {ele.text}') for ele in driver.find_elements(By.CSS_SELECTOR, 'div.myClass'): print(f'find_elements ===> {ele.text}')注:Selenium WebDriver 中元素类型为
WebElement
,对元素的操作都封装到该类里面。 -
属性获取:定位到元素之后,就可以对元素的属性进行获取。
Selenium WebDriver 中对元素属性获取的通用方式为:
WebElement.get_attribute(name)
,比如:# Check if the "active" CSS class is applied to an element. is_active = "active" in target_element.get_attribute("class")
另外,对于一些常见的标签属性,Selenium WebDriver 提供了一些更加简便的获取方式,如下所示:
-
WebElement.text
:获取元素文本内容 -
WebElement.tag_name
:获取元素标签名 -
WebElement.size
:获取元素大小 -
WebElement.location
:获取元素位置
-
-
iframe 切换:当前页面嵌套最常用的方式就是 iframe 标签,Selenium 中无法直接获取 iframe 内部页面内容,因此,如果想操作 iframe 内部页面,则需要将驱动切换到要进行操作的 iframe 元素上,切换的方式为:
webdriver.switch_to.frame
,具体使用方式如下所示:-
定位 iframe 元素并进行切换:如下代码所示:
#
# #from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time driver = webdriver.Chrome() driver.get('http://127.0.0.1:5500/index.html') iframe = driver.find_element(By.CSS_SELECTOR, '#myFrameID') # 切换到 iframe driver.switch_to.frame(iframe) inputKeyWord = driver.find_element(By.CSS_SELECTOR, '#kw') inputKeyWord.send_keys('Selenium') time.sleep(0.5) searchBtn = driver.find_element(By.CSS_SELECTOR, '#su') searchBtn.click() -
通过 ID/名称进行切换:如果 iframe 设置了
id
获取name
属性,则可以直接进行切换,如下所示:#
# #from selenium.common.exceptions import NoSuchFrameException try: # 根据 id 进行切换 driver.switch_to.frame('myFrameID') # 根据 name 进行切换 driver.switch_to.frame('myFrameName') except NoSuchFrameException: pass -
通过索引进行切换:在 JavaScript 中,
window.frames
是一个类数组对象,其存放了当前页面中所有的框架元素,每个元素都是一个Window
对象,通过该数组我们可以很方便获取到对应索引的框架对象。同样,Selenium 也支持我们通过索引对框架进行切换,如下所示:#
# ## ... try: # 切换到第 1 个 iframe driver.switch_to.frame(0) # ... except NoSuchFrameException: pass- 退出 iframe:对子页面操作完毕后,就可以退出子页面,回到主页面,退出方式如下所示:
# switch back to default content driver.switch_to.default_content()
-
鼠标操作
Selenium WebDriver 中,将有关鼠标操作的接口都封装到 ActionChains 类中,其鼠标操作相关接口如下表所示:
鼠标操作 | 释义 |
---|---|
click | 鼠标左键单击 |
click_and_hold | 鼠标左键保持按下状态 |
context_click | 鼠标右键单击 |
double_click | 鼠标左键双击 |
drag_and_drop | 拖放 |
drag_and_drop_by_offset | 拖放到指定偏移位置 |
move_by_offset | 鼠标移动到指定偏移位置 |
move_to_element | 鼠标移动到指定元素中间位置(也起悬停作用) |
move_to_element_with_offset | 鼠标移动到指定元素上的偏移位置 |
release | 释放鼠标按下状态(即松开鼠标) |
以下对几个常用的鼠标操作进行简介:
-
context_click(on_element=None)
:表示鼠标右击操作。
当参数on_element
为None
时,表示右击鼠标所在位置。
当参数on_element
指定相关元素时,表示右击指定元素。from selenium.webdriver.common.action_chains import ActionChains driver = webdriver.Chrome() driver.get('http://127.0.0.1:5500/index.html') # 最大话窗口 driver.maximize_window() # 获取窗口大小 width, height = driver.get_window_size().values() print(f'[{width},{height}]') # 构造动作链 actions = ActionChains(driver) # 移动到窗口中间 actions.move_by_offset(width/2, height/2) # 鼠标右击 actions.context_click() # 执行动作链 actions.perform()
注:
ActionChains
表示一系列动作的组合,前面介绍的接口调用只是将相应的动作描述存储进ActionChains
实例内部的一个队列之中,因此最后必须调用perform()
函数依序触发实际动作。 -
move_to_element
:表示将鼠标移动到指定元素上,实际鼠标此时会其 悬停 作用,这对于那些会响应鼠标悬停的元素十分有用。如下所示:# # # # #
# #