PYPI官网
本文是作者跟着selenium官方文档学习记录的笔记
selenium官网
概述
Selenium 组件
术语:
- API:应用程序编程接口。
- 库:包含 API 和实现它们所需的代码的代码模块。库特定于每种语言绑定。
- Driver:负责控制实际的浏览器。特定于浏览器。
- Framework:一个额外的库,用于支持 WebDriver 套件。这些框架可能是 JUnit 或 NUnit 等测试框架。
直接通信
RemoteWebDriver远程通信
Selenium Server 或 Selenium Grid远程通信
框架
本文只考虑直接通信
网络驱动程序
入门
安装Selenium库
安装浏览器驱动程序,chrome内置了浏览器驱动程序
Hello World
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_eight_components():
driver = webdriver.Chrome()
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
title = driver.title
assert title == "Web form"
driver.implicitly_wait(0.5)
text_box = driver.find_element(by=By.NAME, value="my-text")
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")
text_box.send_keys("Selenium")
submit_button.click()
message = driver.find_element(by=By.ID, value="message")
value = message.text
assert value == "Received!"
driver.quit()
|
Driver 会话
启动和停止会话是为了打开和关闭浏览器。
启动会话可以配置:
- 描述您想要的会话类型的Options;默认值用于本地,但这对于远程是必需的
- 某种形式的CommandExecutor(实现因语言而异)
- Listeners
Options
- browserName 浏览器名称
- browserVersion 浏览器版本:在仅安装了 80 的系统上请求 Chrome 版本 75,则会话创建将失败
- pageLoadStrategy 页面加载策略:normal(默认使用,等待所有资源下载完成),eager(DOM 访问已准备就绪,但图像等其他资源可能仍在加载),none(完全不阻止 WebDriver)。document.readyState属性描述了当前文档的加载状态
1
2
3
4
5
6
7
8
9
|
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# xxx设置页面加载策略
options.page_load_strategy = 'xxx'
driver = webdriver.Chrome(options=options)
driver.get("http://www.google.com")
driver.quit()
|
- platformName 平台名称:远程端的操作系统
- acceptInsecureCerts:false不信任不安全证书,true则信任
- timeouts 超时:
- Script Timeout 脚本超时:指定何时中断当前浏览上下文中正在执行的脚本。默认300,000。
- Page Load Timeout 页面加载超时:指定在当前浏览上下文中需要加载网页的时间间隔。默认300,000。超时抛出TimeoutException。
- Implicit Wait Timeout 隐式等待超时
- unhandledPromptBehavior 未处理的提示行为:指定当前会话的状态user prompt handler。默认关闭并通知状态
User Prompt Handler 用户提示处理程序:在远程端遇到用户提示时必须采取的操作:dismiss,accept,dismiss and notify,accept and notify,ignore
- setWindowRect 设置窗口大小
- proxy 代理:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from selenium import webdriver
PROXY = "<HOST:PORT>"
webdriver.DesiredCapabilities.CHROME['proxy'] = {
"httpProxy": PROXY,
"ftpProxy": PROXY,
"sslProxy": PROXY,
"proxyType": "MANUAL",
}
with webdriver.Chrome() as driver:
driver.get("https://selenium.dev")
|
Chrome 特定功能
Options
1
2
3
4
|
from selenium.webdriver.chrome.options import Options
options = Options()
driver = webdriver.Chrome(options=options)
|
- Arguments:args参数 用于启动浏览器时使用的命令行开关列表。常用的参数包括开启最大化–start-maximized和无界面模式–headless=new,禁用gpu –disable-gpu。详细arguments列表
1
2
|
chrome_options = Options()
chrome_options.add_argument("--headless=new")
|
- Add extensions 添加扩展:extensions参数接受 crx 文件
- Keeping browser open 保持浏览器打开:
将detach参数设置为 true 将使浏览器在驱动程序进程退出后保持打开状态。
1
2
|
chrome_options = Options()
chrome_options.add_experimental_option("detach", True)
|
Waits
WebDriver是有阻塞API。为了克服浏览器和 WebDriver 脚本之间的竞争条件问题,大多数 Selenium 客户端都附带了一个等待包。
Explicit wait显式等待
Selenium 客户端可以使用显式等待来暂停程序执行或冻结线程,直到您传递给它的条件得到解决。以特定频率调用条件,直到等待超时结束。这意味着只要条件返回一个假值,它就会继续尝试和等待。
用等待让findElement调用等待,直到脚本中动态添加的元素被添加到 DOM 中:
1
2
3
4
5
6
7
8
9
|
from selenium.webdriver.support.wait import WebDriverWait
def document_initialised(driver):
return driver.execute_script("return initialised")
driver.navigate("file:///race_condition.html")
WebDriverWait(driver, timeout=10).until(document_initialised)
el = driver.find_element(By.TAG_NAME, "p")
assert el.text == "Hello from JavaScript!"
|
将条件作为函数引用传入,等待将重复运行,直到其返回值为真。“真实的”返回值是任何在手头语言中计算为布尔值 true 的值,例如字符串、数字、布尔值、对象(包括WebElement)或填充的(非空)序列或列表。
使用lambda表达式简化:
1
2
3
4
5
|
from selenium.webdriver.support.wait import WebDriverWait
driver.navigate("file:///race_condition.html")
el = WebDriverWait(driver, timeout=3).until(lambda d: d.find_element(By.TAG_NAME,"p"))
assert el.text == "Hello from JavaScript!"
|
预期条件:因为必须同步 DOM 和您的指令是很常见的事情,所以大多数客户端还带有一组预定义的预期条件。顾名思义,它们是为频繁等待操作预定义的条件。
- 存在警报
- 元素存在
- 元素可见
- 标题包含
- 标题是
- 元素陈旧
- 可见文字
python详尽列表
Implicit wait隐式等待
WebDriver 在尝试查找任何元素时轮询 DOM 一段时间。当网页上的某些元素不能立即使用并且需要一些时间加载时,这会很有用。
默认情况下,隐式等待元素出现是禁用的,需要在每个会话的基础上手动启用。
不要混合使用隐式等待和显示等待,会导致不可预测的等待时间。
1
2
3
4
|
driver = Chrome()
driver.implicitly_wait(10)
driver.get("http://somedomain/url_that_delays_loading")
my_dynamic_element = driver.find_element(By.ID, "myDynamicElement")
|
FluentWait
FluentWait 实例定义等待条件的最长时间,以及检查条件的频率。
用户可以配置等待以在等待时忽略特定类型的异常,例如在页面上搜索元素时抛出NoSuchElementException。
1
2
3
4
|
driver = Chrome()
driver.get("http://somedomain/url_that_delays_loading")
wait = WebDriverWait(driver, timeout=10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException])
element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))
|
python中其实就是显式等待添加额外的参数
Element
上传文件
模拟操作,比如对于下面代码中的页面
1
2
3
4
5
6
7
8
9
10
11
12
|
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(10)
driver.get("https://the-internet.herokuapp.com/upload");
driver.find_element(By.ID,"file-upload").send_keys("selenium-snapshot.jpg")
driver.find_element(By.ID,"file-submit").submit()
if(driver.page_source.find("File Uploaded!")):
print("file upload success")
else:
print("file upload not successful")
driver.quit()
|
Locator 定位器
识别页面上元素的方法。
传统定位器;
- class name:定位类名包含搜索值的元素(不允许使用复合类名)
- css selector:定位与 CSS 选择器匹配的元素
- id:定位 ID 属性与搜索值匹配的元素
- name:定位 NAME 属性与搜索值匹配的元素
- link text:定位其可见文本与搜索值匹配的锚元素
- partial link text:定位其可见文本包含搜索值的锚元素。如果匹配多个元素,则只会选择第一个。
- tag name:定位标签名称与搜索值匹配的元素
- xpath:定位与 XPath 表达式匹配的元素
相对定位器:
- Above:获取页面空间上相对目标元素“上方”符合条件的元素
- Below:下方
- Left of:左边
- Right of:右边
- Near :相对目标元素最多50px像素的元素
1
2
3
4
5
|
email_locator = locate_with(By.TAG_NAME, "input").above({By.ID: "password"})
password_locator = locate_with(By.TAG_NAME, "input").below({By.ID: "email"})
cancel_locator = locate_with(By.TAG_NAME, "button").to_left_of({By.ID: "submit"})
submit_locator = locate_with(By.TAG_NAME, "button").to_right_of({By.ID: "cancel"})
email_locator = locate_with(By.TAG_NAME, "input").near({By.ID: "lbl-email"})
|
Finder 查找元素
根据提供的定位器值定位元素。
只查第一个匹配元素
评估整个 DOM
1
|
vegetable = driver.find_element(By.CLASS_NAME, "tomatoes")
|
评估 DOM 的子集
1
2
|
fruits = driver.find_element(By.ID, "fruits")
fruit = fruits.find_element(By.CLASS_NAME,"tomatoes")
|
优化定位器:嵌套查找可能不是最有效的定位策略,因为它需要向浏览器发出两个单独的命令。使用 CSS 或 XPath 在单个命令中查找此元素。
1
|
fruit = driver.find_element(By.CSS_SELECTOR,"#fruits .tomatoes")
|
所有匹配元素
find_elements返回一个列表
1
|
plants = driver.find_elements(By.TAG_NAME, "li")
|
获取元素:遍历列表
从元素中查找元素
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.example.com")
# Get element with tag name 'div'
element = driver.find_element(By.TAG_NAME, 'div')
# Get all the elements available with tag name 'p'
elements = element.find_elements(By.TAG_NAME, 'p')
for e in elements:
print(e.text)
|
获取活动元素
跟踪(或)查找在当前浏览上下文中具有焦点的 DOM 元素
1
2
3
4
5
6
7
8
9
10
|
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.google.com")
driver.find_element(By.CSS_SELECTOR, '[name="q"]').send_keys("webElement")
# Get attribute of current active element
attr = driver.switch_to.active_element.get_attribute("title")
print(attr)
|
互动
可以在一个元素上执行的基本命令有 5 个:
- 单击(适用于任何元素):元素点击命令 执行在 元素中央
- 发送键(仅适用于文本字段和内容可编辑元素)
- 清除(仅适用于文本字段和内容可编辑元素)
- 提交(仅适用于表单元素)
- 选择(请参阅选择列表元素)
1
2
3
4
5
|
# 发送按键
driver.find_element(By.NAME, "q").send_keys("webdriver" + Keys.ENTER)
# 清除 具有 文本 类型的表单的输入元素或具有 内容可编辑 属性的元素
element.clear()
|
获取元素信息
是否显示
是否启用
是否被选定
获取元素标签名
位置和大小:返回四个值,元素左上角的X轴位置,元素左上角的y轴位置,元素的高度,元素的宽度
1
|
res = driver.find_element(By.CSS_SELECTOR, "h1").rect
|
获取元素CSS值
1
|
cssValue = driver.find_element(By.LINK_TEXT, "More information...").value_of_css_property('color')
|
文本内容
1
|
text = driver.find_element(By.CSS_SELECTOR, "h1").text
|
获取特性或属性
1
|
value_info = email_txt.get_attribute("value")
|
交互
获取浏览器信息
获取标题
获取当前 URL
导航
打开网站
1
|
driver.get("https://selenium.dev")
|
后退
前进
刷新
警告框
Alerts 警告框:显示一条自定义消息, 以及一个用于关闭该警告的按钮
1
2
3
|
alert = wait.until(expected_conditions.alert_is_present())
text = alert.text
alert.accept()
|
Confirm 确认框 :用户还可以选择取消消息
1
2
3
4
5
|
driver.find_element(By.LINK_TEXT, "See a sample confirm").click()
wait.until(expected_conditions.alert_is_present())
alert = driver.switch_to.alert
text = alert.text
alert.dismiss()
|
Prompt 提示框:包括文本输入
1
2
3
4
5
|
driver.find_element(By.LINK_TEXT, "See a sample prompt").click()
wait.until(expected_conditions.alert_is_present())
alert = Alert(driver)
alert.send_keys("Selenium")
alert.accept()
|
Cookie
添加 Cookie
1
|
driver.add_cookie({"name": "key", "value": "value"})
|
获取命名的 Cookie
1
|
driver.get_cookie("foo")
|
获取全部 Cookies
删除 Cookie
1
|
driver.delete_cookie("test1")
|
删除所有 Cookies
1
|
driver.delete_all_cookies()
|
Same-Site Cookie属性
- Strict:cookie不会与来自第三方网站的请求一起发送
- Lax:cookie将与第三方网站发起的GET请求一起发送.
1
|
driver.add_cookie({"name": "foo", "value": "value", 'sameSite': 'Strict'})
|
Frames
使用 WebElement
使用 WebElement 进行切换是最灵活的选择
1
2
3
4
5
6
7
8
|
# 获取frame元素
iframe = driver.find_element(By.CSS_SELECTOR, "#modal > iframe")
# 切换到选择的 iframe
driver.switch_to.frame(iframe)
# 单击frame里的按钮
driver.find_element(By.TAG_NAME, 'button').click()
|
frame 或 iframe 具有 id 或 name 属性,则可以使用该属性直接切换进去。如果名称或 id 在页面上不是唯一的, 那么将切换到找到的第一个。
1
2
3
4
5
|
# 通过 id 切换框架
driver.switch_to.frame('buttonframe')
# 单击按钮
driver.find_element(By.TAG_NAME, 'button').click()
|
使用索引
1
2
3
4
5
|
# 基于索引切换到第 2 个 iframe
iframe = driver.find_elements(By.TAG_NAME,'iframe')[1]
# 切换到选择的 iframe
driver.switch_to.frame(iframe)
|
离开框架
1
2
|
# 切回到默认内容
driver.switch_to.default_content()
|
窗口
窗口和标签页
WebDriver 没有区分窗口和标签页。每个窗口都有一个唯一的标识符,该标识符在单个会话中保持持久性。获得当前窗口的窗口句柄:
1
|
driver.current_window_handle
|
所有打开的窗口句柄
切换窗口或标签页
1
|
driver.switch_to.window(window_handle)
|
创建新窗口(或)新标签页并且切换
1
2
3
4
5
|
# 打开新标签页并切换到新标签页
driver.switch_to.new_window('tab')
# 打开一个新窗口并切换到新窗口
driver.switch_to.new_window('window')
|
关闭窗口或标签页:如果在关闭一个窗口后忘记切换回另一个窗口句柄,WebDriver 将在当前关闭的页面上执行,并触发一个 No Such Window Exception 无此窗口异常。必须切换回有效的窗口句柄才能继续执行。
1
2
3
4
5
|
# 关闭标签页或窗口
driver.close()
# 切回到之前的标签页或窗口
driver.switch_to.window(original_window)
|
在会话结束时退出浏览器
Python 的 WebDriver 现在支持 Python 上下文管理器,当使用 with 关键字时,可以在执行结束时自动退出驱动程序。
1
2
3
4
|
with webdriver.Firefox() as driver:
# WebDriver 代码…
# 在此缩进位置后 WebDriver 会自动退出
|
窗口管理
获取窗口大小
1
2
3
4
5
6
7
8
|
# 分别获取每个尺寸
width = driver.get_window_size().get("width")
height = driver.get_window_size().get("height")
# 或者存储尺寸并在以后查询它们
size = driver.get_window_size()
width1 = size.get("width")
height1 = size.get("height")
|
设置窗口大小
1
|
driver.set_window_size(1024, 768)
|
得到窗口的位置:获取浏览器窗口左上角的坐标
1
2
3
4
5
6
7
8
|
# 分别获取每个尺寸
x = driver.get_window_position().get('x')
y = driver.get_window_position().get('y')
# 或者存储尺寸并在以后查询它们
position = driver.get_window_position()
x1 = position.get('x')
y1 = position.get('y')
|
设置窗口位置
1
2
|
# 将窗口移动到主显示器的左上角
driver.set_window_position(0, 0)
|
最大化窗口
1
|
driver.maximize_window()
|
最小化窗口
1
|
driver.minimize_window()
|
全屏窗口
1
|
driver.fullscreen_window()
|
屏幕截图
1
|
driver.save_screenshot('./image.png')
|
元素屏幕截图
1
|
element.screenshot('./image.png')
|
执行脚本
1
2
3
|
header = driver.find_element(By.CSS_SELECTOR, "h1")
driver.execute_script('return arguments[0].innerText', header)
|
打印页面
1
|
base64code = driver.print_page(print_options)
|
Actions接口
除了高级元素交互之外, Actions 接口 还提供了对指定输入设备 可以执行的确切操作的精细控制. Selenium为3种输入源提供了接口: 键盘设备的键输入, 鼠标, 笔或触摸设备的输入, 以及滚轮设备的滚轮输入
暂停
1
2
3
4
|
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.pause(1)\
.perform()
|
释放所有Actions
1
|
ActionBuilder(driver).clear_actions()
|
键盘操作
按键,完整按键列表
1
2
3
|
ActionChains(driver)\
.key_down(Keys.SHIFT)\
.perform()
|
释放按键
1
2
3
4
|
ActionChains(driver)\
.key_down(Keys.SHIFT)\
.key_up(Keys.SHIFT)\
.perform()
|
键入活跃元素
1
2
3
|
ActionChains(driver)\
.send_keys("abc")\
.perform()
|
键入指定元素
1
2
3
4
|
text_input = driver.find_element(By.ID, "textInput")
ActionChains(driver)\
.send_keys_to_element(text_input, "abc")\
.perform()
|
复制粘贴:模仿CV
Mouse actions
Click and hold
1
2
3
4
|
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.click_and_hold(clickable)\
.perform()
|
单击并释放
1
2
3
4
|
clickable = driver.find_element(By.ID, "click")
ActionChains(driver)\
.click(clickable)\
.perform()
|
交替按钮点击:0 — 左键(默认),2 — 右键,3 — X1(后退)按钮,4 — X2(前进)按钮
上下文点击:移动到元素的中心与按下和释放鼠标右键(按钮 2)
1
2
3
4
|
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.context_click(clickable)\
.perform()
|
后退点击
1
2
3
4
|
action = ActionBuilder(driver)
action.pointer_action.pointer_down(MouseButton.BACK)
action.pointer_action.pointer_up(MouseButton.BACK)
action.perform()
|
向前点击
1
2
3
4
|
action = ActionBuilder(driver)
action.pointer_action.pointer_down(MouseButton.FORWARD)
action.pointer_action.pointer_up(MouseButton.FORWARD)
action.perform()
|
双击
1
2
3
4
|
clickable = driver.find_element(By.ID, "clickable")
ActionChains(driver)\
.double_click(clickable)\
.perform()
|
移动到元素
1
2
3
4
|
hoverable = driver.find_element(By.ID, "hover")
ActionChains(driver)\
.move_to_element(hoverable)\
.perform()
|
按偏移量移动
从元素偏移(左上原点):当元素不完全在视口内时,此方法无法正常工作
1
2
3
4
|
mouse_tracker = driver.find_element(By.ID, "mouse-tracker")
ActionChains(driver)\
.move_to_element_with_offset(mouse_tracker, 8, 0)\
.perform()
|
从视口偏移
1
2
3
|
action = ActionBuilder(driver)
action.pointer_action.move_to_location(8, 0)
action.perform()
|
当前指针位置的偏移量
1
2
3
|
ActionChains(driver)\
.move_by_offset( 13, 15)\
.perform()
|
拖放元素:在源元素上执行单击并按住,移动到目标元素的位置,然后释放鼠标
1
2
3
4
5
|
draggable = driver.find_element(By.ID, "draggable")
droppable = driver.find_element(By.ID, "droppable")
ActionChains(driver)\
.drag_and_drop(draggable, droppable)\
.perform()
|
按偏移拖放
1
2
3
4
5
6
|
draggable = driver.find_element(By.ID, "draggable")
start = draggable.location
finish = driver.find_element(By.ID, "droppable").location
ActionChains(driver)\
.drag_and_drop_by_offset(draggable, finish['x'] - start['x'], finish['y'] - start['y'])\
.perform()
|
滚轮动作
滚动到元素
1
2
3
4
|
iframe = driver.find_element(By.TAG_NAME, "iframe")
ActionChains(driver)\
.scroll_to_element(iframe)\
.perform()
|
按给定数量滚动
1
2
3
4
5
|
footer = driver.find_element(By.TAG_NAME, "footer")
delta_y = footer.rect['y']
ActionChains(driver)\
.scroll_by_amount(0, delta_y)\
.perform()
|
双向协议
Chrome开发工具协议
模拟地理位置
1
2
3
4
5
6
7
8
9
10
11
12
|
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
def geoLocationTest():
driver = webdriver.Chrome()
Map_coordinates = dict({
"latitude": 41.8781,
"longitude": -87.6298,
"accuracy": 100
})
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", Map_coordinates)
driver.get("<your site url>")
|
收集性能指标
1
2
3
4
5
6
7
8
9
|
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.duckduckgo.com')
driver.execute_cdp_cmd('Performance.enable', {})
t = driver.execute_cdp_cmd('Performance.getMetrics', {})
print(t)
driver.quit()
|