Contents

Python-第三方库-Selenium

PYPI官网

本文是作者跟着selenium官方文档学习记录的笔记

selenium官网

概述

Selenium 组件

术语:

  • API:应用程序编程接口。
  • 库:包含 API 和实现它们所需的代码的代码模块。库特定于每种语言绑定。
  • Driver:负责控制实际的浏览器。特定于浏览器。
  • Framework:一个额外的库,用于支持 WebDriver 套件。这些框架可能是 JUnit 或 NUnit 等测试框架。

直接通信

https://www.selenium.dev/images/documentation/webdriver/basic_comms.png

RemoteWebDriver远程通信

https://www.selenium.dev/images/documentation/webdriver/remote_comms.png

Selenium Server 或 Selenium Grid远程通信

https://www.selenium.dev/images/documentation/webdriver/remote_comms_server.png

框架

https://www.selenium.dev/images/documentation/webdriver/test_framework.png

本文只考虑直接通信

网络驱动程序

入门

安装Selenium库

1
pip install 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

  1. browserName 浏览器名称
  2. browserVersion 浏览器版本:在仅安装了 80 的系统上请求 Chrome 版本 75,则会话创建将失败
  3. 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()
  1. platformName 平台名称:远程端的操作系统
  2. acceptInsecureCerts:false不信任不安全证书,true则信任
  3. timeouts 超时:
  • Script Timeout 脚本超时:指定何时中断当前浏览上下文中正在执行的脚本。默认300,000。
  • Page Load Timeout 页面加载超时:指定在当前浏览上下文中需要加载网页的时间间隔。默认300,000。超时抛出TimeoutException。
  • Implicit Wait Timeout 隐式等待超时
  1. unhandledPromptBehavior 未处理的提示行为:指定当前会话的状态user prompt handler。默认关闭并通知状态 User Prompt Handler 用户提示处理程序:在远程端遇到用户提示时必须采取的操作:dismiss,accept,dismiss and notify,accept and notify,ignore
  2. setWindowRect 设置窗口大小
  3. 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)
  1. Arguments:args参数 用于启动浏览器时使用的命令行开关列表。常用的参数包括开启最大化–start-maximized和无界面模式–headless=new,禁用gpu –disable-gpu。详细arguments列表
1
2
chrome_options = Options()
chrome_options.add_argument("--headless=new")
  1. Add extensions 添加扩展:extensions参数接受 crx 文件
  2. 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 表达式匹配的元素

相对定位器:

  1. Above:获取页面空间上相对目标元素“上方”符合条件的元素
  2. Below:下方
  3. Left of:左边
  4. Right of:右边
  5. 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()

获取元素信息

是否显示

1
element.is_displayed()

是否启用

1
element.is_enabled()

是否被选定

1
element.is_selected()

获取元素标签名

1
element.tag_name

位置和大小:返回四个值,元素左上角的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")

交互

获取浏览器信息

获取标题

1
driver.title

获取当前 URL

1
driver.current_url

导航

打开网站

1
driver.get("https://selenium.dev")

后退

1
driver.back()

前进

1
driver.forward()

刷新

1
driver.refresh()

警告框

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

1
driver.add_cookie({"name": "key", "value": "value"})

获取命名的 Cookie

1
driver.get_cookie("foo")

获取全部 Cookies

1
driver.get_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.window_handles

切换窗口或标签页

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)

在会话结束时退出浏览器

1
driver.quit()

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()
 |