Selenium 教程

Selenium 教程

Selenium 是目前最流行的 Web 自动化测试框架,支持多种编程语言(Python、Java、C#、JavaScript 等)和主流浏览器(Chrome、Firefox、Edge、Safari),用于浏览器自动化、UI 测试、爬虫等场景。

Selenium 组件

组件用途
Selenium WebDriver核心 API,驱动浏览器自动化
Selenium IDE浏览器插件,录制回放测试
Selenium Grid分布式测试,多机并行

支持的语言

Python   from selenium import webdriver
Java     import org.openqa.selenium.WebDriver;
C#       using OpenQA.Selenium;
JavaScript  require("selenium-webdriver")
Ruby     require "selenium-webdriver"

第一个自动化脚本(Python)

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.example.com")
title = driver.title
print("页面标题:", title)
driver.quit()

学习路线

  1. 安装与环境配置
  2. WebDriver 与元素定位
  3. 元素操作与等待机制
  4. 浏览器、鼠标键盘高级操作
  5. 文件上传下载、Grid 分布式
  6. 测试框架集成与实战项目

Selenium 简介

Selenium 简介

1. 什么是 Selenium

Selenium 是一套用于 Web 应用自动化的工具,最初由 Jason Huggins 于 2004 年在 ThoughtWorks 开发,现已发展为开源社区的明星项目,广泛用于功能测试、回归测试和网页数据采集。

2. 核心组件

  • WebDriver:直接驱动浏览器,模拟真实用户操作(点击、输入、滚动)
  • IDE:Chrome/Firefox 扩展,录制操作并导出脚本,适合快速入门
  • Grid:把测试分发到多台机器/多个浏览器并行执行,加速大型测试套件

3. 为什么选择 Selenium

  • 开源免费,社区庞大
  • 多语言多浏览器支持
  • 模拟真实用户,测试真实度高
  • 与其他测试框架(TestNG、JUnit、pytest)无缝集成

4. 适用场景

  • Web UI 自动化测试
  • 回归测试与冒烟测试
  • 网页数据抓取(爬虫)
  • 重复性网页操作自动化

5. 限制

  • 无法测试桌面应用(需 Appium 等)
  • 对动态内容依赖等待机制
  • 浏览器升级可能导致兼容性问题

Selenium 安装

Selenium 安装

1. Python 环境安装

# 安装 selenium 库
pip install selenium

# 验证
python -c "import selenium; print(selenium.__version__)"

2. 浏览器驱动(WebDriver)

Selenium 需要浏览器驱动才能控制浏览器:

# Chrome → ChromeDriver
# 下载:https://chromedriver.chromium.org/downloads
# 版本必须与 Chrome 浏览器大版本一致

# Firefox → geckodriver
# https://github.com/mozilla/geckodriver/releases

# Edge → msedgedriver

3. 驱动放置与配置

# 方法一:把驱动放到系统 PATH
mv chromedriver /usr/local/bin/

# 方法二:显式指定路径
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

service = Service("/path/to/chromedriver")
driver = webdriver.Chrome(service=service)

# 方法三(推荐):自动管理驱动
pip install webdriver-manager

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

4. Java 环境

# Maven 依赖
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.18.0</version>
</dependency>

5. 验证安装

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
print(driver.title)   # 百度一下,你就知道
driver.quit()
print("Selenium 安装成功!")

Selenium WebDriver

Selenium WebDriver

1. 创建 WebDriver

from selenium import webdriver

# Chrome
driver = webdriver.Chrome()

# Firefox
driver = webdriver.Firefox()

# Edge
driver = webdriver.Edge()

# Safari(macOS)
driver = webdriver.Safari()

2. 浏览器选项(Options)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless")          # 无头模式
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
options.add_argument("--user-agent=Mozilla/5.0 ...")
options.add_experimental_option("excludeSwitches", ["enable-logging"])

driver = webdriver.Chrome(options=options)

3. 常用 API

driver.get("https://example.com")   # 打开网页
driver.title                        # 页面标题
driver.current_url                  # 当前 URL
driver.page_source                  # 页面源码
driver.back()                       # 后退
driver.forward()                    # 前进
driver.refresh()                    # 刷新
driver.quit()                       # 关闭浏览器并退出
driver.close()                      # 关闭当前标签页
driver.maximize_window()            # 最大化窗口
driver.get_window_size()            # 窗口尺寸

4. 窗口与标签管理

# 打开新标签页
driver.switch_to.new_window("tab")
driver.get("https://example.com")

# 切换窗口
windows = driver.window_handles
driver.switch_to.window(windows[-1])

# 切换 iframe
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe)
# 切回主文档
driver.switch_to.default_content()

5. 生命周期最佳实践

try:
    driver = webdriver.Chrome()
    driver.get("https://example.com")
    # 测试逻辑
finally:
    driver.quit()   # 确保释放资源

# 或用上下文管理器
with webdriver.Chrome() as driver:
    driver.get("https://example.com")
    print(driver.title)

Selenium 元素定位

Selenium 元素定位

定位元素是自动化的基础,Selenium 提供多种定位策略(By)。

1. 定位方式

from selenium.webdriver.common.by import By

# 按 ID(最快,优先使用)
driver.find_element(By.ID, "username")

# 按 Class
driver.find_element(By.CLASS_NAME, "btn-primary")

# 按 Name
driver.find_element(By.NAME, "password")

# 按标签名
driver.find_element(By.TAG_NAME, "h1")

# 按链接文本(精确)
driver.find_element(By.LINK_TEXT, "点击登录")

# 按部分链接文本
driver.find_element(By.PARTIAL_LINK_TEXT, "登录")

# 按 CSS 选择器
driver.find_element(By.CSS_SELECTOR, "#nav > ul > li:first-child")

# 按 XPath
driver.find_element(By.XPATH, "//input[@name='email']")

2. 查找多个元素

# 返回列表
links = driver.find_elements(By.TAG_NAME, "a")
for link in links:
    print(link.text, link.get_attribute("href"))

# 存在性判断
if driver.find_elements(By.ID, "error-msg"):
    print("页面有错误提示")

3. XPath 常用语法

//div               所有 div 元素
//div[@class='a']   按属性
//div[contains(text(), '登录')]  文本包含
//div[@id='a']/span  子元素
//input[starts-with(@name, 'user')]
//a[@href and @title]  多条件
//ul/li[position()=3]  第3个 li
//span[normalize-space()='确定']  文本精确匹配

4. CSS 选择器常用语法

#id            ID
.class         类
input[name=x]  属性
div > p        子元素
ul li          后代
a:hover        伪类
input[type=text]  属性选择

5. 定位失败处理

from selenium.common.exceptions import NoSuchElementException

try:
    el = driver.find_element(By.ID, "not-exist")
except NoSuchElementException:
    print("元素不存在,降级处理")

# 元素不存在返回空列表(不抛异常)
els = driver.find_elements(By.ID, "not-exist")
if not els:
    print("无此元素")

Selenium 元素操作

Selenium 元素操作

1. 基本操作

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# 输入文本
username = driver.find_element(By.ID, "username")
username.send_keys("admin")

# 清除输入
username.clear()

# 点击
btn = driver.find_element(By.ID, "login-btn")
btn.click()

# 提交表单
form = driver.find_element(By.TAG_NAME, "form")
form.submit()

2. 获取信息

el = driver.find_element(By.CLASS_NAME, "card")

el.text              # 元素文本
el.get_attribute("href")   # 属性值
el.get_attribute("value")  # 输入框值
el.get_attribute("class")
el.is_displayed()    # 是否可见
el.is_enabled()      # 是否可用
el.is_selected()     # 是否选中(checkbox/radio)
el.size              # 尺寸 {"width":..,"height":..}
el.location          # 坐标

3. 下拉框操作(Select)

from selenium.webdriver.support.ui import Select

select = Select(driver.find_element(By.ID, "city"))

select.select_by_index(1)        # 按索引
select.select_by_value("beijing") # 按 value
select.select_by_visible_text("北京") # 按可见文本
select.deselect_all()            # 取消所有(多选)

# 获取所有选项
options = select.options
for opt in options:
    print(opt.text, opt.get_attribute("value"))

4. 复选框与单选

# 勾选
checkbox = driver.find_element(By.ID, "agree")
if not checkbox.is_selected():
    checkbox.click()

# 单选按钮
radio = driver.find_element(By.XPATH, "//input[@value='male']")
radio.click()

5. JS 执行(高级)

# 执行 JavaScript
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")

# 滚动到元素
el = driver.find_element(By.ID, "target")
driver.execute_script("arguments[0].scrollIntoView();", el)

# 修改属性
driver.execute_script("arguments[0].value = '设置值';", input_el)

Selenium 等待机制

Selenium 等待机制

页面元素动态加载,需要等待机制保证元素可用。等待机制是自动化稳定性的关键。

1. 强制等待(不推荐)

import time
time.sleep(3)   # 固定等 3 秒,慢且脆弱

2. 隐式等待(Implicit Wait)

from selenium import webdriver

driver = webdriver.Chrome()
# 全局等待:查找元素时最多等 10 秒
driver.implicitly_wait(10)

# 之后所有 find_element 都会等待元素出现
driver.get("https://example.com")
el = driver.find_element(By.ID, "dynamic-content")

3. 显式等待(Explicit Wait,推荐)

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

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

# 等待元素可点击,最多 10 秒
element = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submit-btn"))
)
element.click()

# 等待元素可见
WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.CLASS_NAME, "loading"))
)

# 等待元素消失
WebDriverWait(driver, 10).until_not(
    EC.visibility_of_element_located((By.CLASS_NAME, "loading"))
)

# 等待文本出现
WebDriverWait(driver, 10).until(
    EC.text_to_be_present_in_element((By.ID, "msg"), "成功")
)

4. 常用 Expected Conditions

EC.presence_of_element_located     元素出现在 DOM
EC.visibility_of_element_located   元素可见
EC.element_to_be_clickable         元素可点击
EC.text_to_be_present_in_element   文本出现
EC.alert_is_present                弹窗出现
EC.frame_to_be_available_and_switch_to_it   iframe 可切换
EC.number_of_windows_to_be         窗口数量变化
EC.staleness_of_element            元素被移除

5. 自定义等待条件

from selenium.webdriver.support.ui import WebDriverWait

# 自定义条件:等待元素文本等于某值
def text_is(driver, element_id, expected):
    el = driver.find_element(By.ID, element_id)
    return el.text == expected

WebDriverWait(driver, 10).until(
    lambda d: text_is(d, "status", "完成")
)

6. 等待策略建议

  • 页面加载用隐式等待兜底
  • 关键交互用显式等待(指定元素条件)
  • 避免 sleep 固定等待
  • 等待超时捕获 TimeoutException 处理

Selenium 浏览器操作

Selenium 浏览器操作

1. 导航与标签

from selenium import webdriver

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

# 后退 / 前进 / 刷新
driver.back()
driver.forward()
driver.refresh()

# 打开新标签页并切换
driver.switch_to.new_window("tab")
driver.get("https://another.com")

# 关闭当前标签页
driver.close()
# 切回第一个标签
driver.switch_to.window(driver.window_handles[0])

2. 窗口管理

# 最大化
driver.maximize_window()

# 全屏
driver.fullscreen_window()

# 设置尺寸
driver.set_window_size(1280, 800)

# 获取/设置位置
driver.get_window_position()
driver.set_window_position(100, 100)

# 获取所有窗口句柄
print(driver.window_handles)

3. Cookie 管理

# 获取所有 cookie
cookies = driver.get_cookies()
for c in cookies:
    print(c["name"], c["value"])

# 添加 cookie(登录态)
driver.add_cookie({"name": "session_id", "value": "abc123"})

# 删除
driver.delete_cookie("session_id")
driver.delete_all_cookies()

4. 弹窗处理

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 触发弹窗
driver.find_element(By.ID, "show-alert").click()

# 切换并处理
alert = WebDriverWait(driver, 5).until(EC.alert_is_present())
print("弹窗文本:", alert.text)
alert.accept()          # 确认
# alert.dismiss()       # 取消
# alert.send_keys("text")  # 输入(仅 prompt)

5. 页面截图

# 整页截图
driver.save_screenshot("screenshot.png")

# 元素截图
el = driver.find_element(By.CLASS_NAME, "card")
el.screenshot("element.png")

# 带尺寸截图
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
driver.save_screenshot("full.png")

6. 下载文件配置

options = Options()
prefs = {
    "download.default_directory": "/path/to/downloads",
    "download.prompt_for_download": False
}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)

Selenium 鼠标和键盘操作

Selenium 鼠标和键盘操作

1. ActionChains 鼠标操作

from selenium.webdriver.common.action_chains import ActionChains

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

el = driver.find_element(By.ID, "target")
actions = ActionChains(driver)

# 悬停
actions.move_to_element(el).perform()

# 双击
actions.double_click(el).perform()

# 右键
actions.context_click(el).perform()

# 拖拽(拖到目标)
source = driver.find_element(By.ID, "drag-source")
target = driver.find_element(By.ID, "drop-target")
actions.drag_and_drop(source, target).perform()

# 按住不放
actions.click_and_hold(el).perform()

# 链式操作
actions.move_to_element(el).pause(1).double_click().perform()

2. 键盘操作

from selenium.webdriver.common.keys import Keys

input_box = driver.find_element(By.ID, "search")

# 输入
input_box.send_keys("python")

# 回车
input_box.send_keys(Keys.ENTER)

# 组合键 Ctrl+A / Ctrl+C / Ctrl+V
input_box.send_keys(Keys.CONTROL, "a")
input_box.send_keys(Keys.CONTROL, "c")
input_box.send_keys(Keys.CONTROL, "v")

# 常用键
Keys.BACK_SPACE   # 退格
Keys.TAB          # Tab
Keys.ESCAPE       # Esc
Keys.SPACE        # 空格
Keys.ARROW_DOWN   # 下箭头
Keys.F5           # 刷新
Keys.HOME         # Home

3. 滚动操作

# 滚动到元素
el = driver.find_element(By.ID, "bottom")
actions = ActionChains(driver)
actions.move_to_element(el).perform()

# JS 滚动
driver.execute_script("window.scrollTo(0, 500);")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# 键盘滚动
driver.find_element(By.TAG_NAME, "body").send_keys(Keys.END)
driver.find_element(By.TAG_NAME, "body").send_keys(Keys.HOME)

4. 文件上传

# input[type=file] 直接 send_keys 路径
upload = driver.find_element(By.ID, "file-upload")
upload.send_keys("/path/to/file.txt")

# 拖拽上传(需要 JS 辅助,复杂场景)
# 用 AutoIT / pywinauto 处理 Windows 系统弹窗

5. 滑动验证码处理思路

# 拖拽滑块
slider = driver.find_element(By.ID, "slider")
btn = driver.find_element(By.ID, "slider-btn")
actions = ActionChains(driver)
actions.click_and_hold(btn).move_by_offset(300, 0).release().perform()

Selenium 文件上传和下载

Selenium 文件上传和下载

1. 文件上传

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/upload")

# 方式一:input[type=file] 直接 send_keys(最常用)
upload_input = driver.find_element(By.ID, "file")
upload_input.send_keys("/absolute/path/to/file.pdf")

# 方式二:多个文件
upload_input.send_keys("/path/a.txt", "/path/b.txt")

# 方式三:非 input 元素的拖拽上传(需要 JS)
driver.execute_script("""
    var input = document.createElement('input');
    input.type = 'file';
    document.body.appendChild(input);
""")

2. 上传验证

# 等待上传完成
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.CLASS_NAME, "upload-success"))
)
print("上传成功提示:", driver.find_element(By.CLASS_NAME, "upload-success").text)

3. 文件下载

from selenium.webdriver.chrome.options import Options

options = Options()
prefs = {
    "download.default_directory": "/path/to/downloads",  # 下载目录
    "download.prompt_for_download": False,  # 不弹确认
    "download.directory_upgrade": True,
    "safebrowsing.enabled": True
}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)

# 点击下载按钮
driver.get("https://example.com/files")
driver.find_element(By.ID, "download-btn").click()

# 等待文件出现
import os, time
download_dir = "/path/to/downloads"
for _ in range(30):  # 最多等 30 秒
    files = os.listdir(download_dir)
    if files and not any(f.endswith(".crdownload") for f in files):
        print("下载完成:", files)
        break
    time.sleep(1)

4. 下载验证与清理

import glob

# 检查文件大小大于 0
downloaded = glob.glob("/path/to/downloads/*.pdf")
if downloaded and os.path.getsize(downloaded[0]) > 0:
    print("文件有效:", downloaded[0])

# 清理
for f in downloaded:
    os.remove(f)

Selenium Grid

Selenium Grid

1. 什么是 Selenium Grid

Selenium Grid 用于分布式测试:一个 Hub 管理多个 Node,把测试任务分发到不同机器、不同浏览器上并行执行,大幅缩短测试时间。

2. 架构

┌──────────┐      ┌─────────┐
│  Test 代码 ├─────►│  Hub     │
└──────────┘      └────┬────┘
                       │ 分发
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
   ┌─────────┐    ┌─────────┐    ┌─────────┐
   │ Node 1  │    │ Node 2  │    │ Node 3  │
   │ Chrome  │    │ Firefox │    │ Edge    │
   └─────────┘    └─────────┘    └─────────┘

3. 启动 Grid(Selenium 4)

# 下载 selenium-server jar
# https://github.com/SeleniumHQ/selenium/releases

# 启动 Hub
java -jar selenium-server-4.18.0.jar hub

# 启动 Node(注册到 Hub)
java -jar selenium-server-4.18.0.jar node
  --hub http://localhost:4444

4. 配置 Node 浏览器

# 指定 Node 支持的浏览器和并发数
java -jar selenium-server-4.18.0.jar node   --hub http://localhost:4444   --detect-drivers true   --max-sessions 5

# 查看 Grid 状态
# http://localhost:4444/ui

5. 远程执行测试

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.set_capability("browserName", "chrome")
options.set_capability("platformName", "linux")

driver = webdriver.Remote(
    command_executor="http://localhost:4444/wd/hub",
    options=options
)
driver.get("https://example.com")
print(driver.title)
driver.quit()

6. 并行测试(pytest)

# 安装 pytest-xdist
# pip install pytest-xdist

# 多进程运行测试
pytest test_cases.py -n 4

# 测试代码中连接 Grid
import pytest
from selenium import webdriver

@pytest.fixture
def driver():
    options = webdriver.ChromeOptions()
    d = webdriver.Remote(
        command_executor="http://localhost:4444/wd/hub",
        options=options
    )
    yield d
    d.quit()

Selenium 测试框架集成

Selenium 测试框架集成

1. pytest 集成(Python)

# 安装
# pip install pytest

import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By

@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    driver.implicitly_wait(10)
    yield driver
    driver.quit()

def test_login(driver):
    driver.get("https://example.com/login")
    driver.find_element(By.ID, "username").send_keys("admin")
    driver.find_element(By.ID, "password").send_keys("123456")
    driver.find_element(By.ID, "submit").click()
    assert "欢迎" in driver.page_source

2. 参数化测试

@pytest.mark.parametrize("user, pwd, expected", [
    ("admin", "123456", "成功"),
    ("user1", "wrong", "密码错误"),
    ("", "123456", "用户名不能为空"),
])
def test_login_cases(driver, user, pwd, expected):
    driver.get("https://example.com/login")
    driver.find_element(By.ID, "username").send_keys(user)
    driver.find_element(By.ID, "password").send_keys(pwd)
    driver.find_element(By.ID, "submit").click()
    assert expected in driver.page_source

3. TestNG 集成(Java)

import org.testng.annotations.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class LoginTest {
    WebDriver driver;

    @BeforeMethod
    public void setup() {
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    @Test
    public void testLogin() {
        driver.get("https://example.com/login");
        driver.findElement(By.id("username")).sendKeys("admin");
        driver.findElement(By.id("password")).sendKeys("123456");
        driver.findElement(By.id("submit")).click();
        assertTrue(driver.getPageSource().contains("欢迎"));
    }

    @AfterMethod
    public void teardown() {
        driver.quit();
    }
}

4. 数据驱动(读取 CSV/JSON)

import json, csv

# CSV 数据源
def load_csv(path):
    with open(path) as f:
        return list(csv.DictReader(f))

@pytest.mark.parametrize("data", load_csv("cases.csv"))
def test_from_csv(driver, data):
    assert data["expected"] in login(driver, data["user"], data["pwd"])

5. 报告与截图

# pytest 报告
# pip install pytest-html
# pytest --html=report.html

import time

def test_failure_screenshot(driver):
    try:
        # 测试步骤
        pass
    except Exception:
        name = f"fail_{time.strftime('%Y%m%d_%H%M%S')}.png"
        driver.save_screenshot(name)
        raise

Selenium 高级功能

Selenium 高级功能

1. 网络请求拦截

# Chrome DevTools Protocol(CDP)
from selenium.webdriver.common.devtools.v119.network import RequestWillBeSent

# Selenium 4 支持网络监听
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
driver = webdriver.Chrome(options=options)

# 使用 CDP 监听请求
driver.execute_cdp_cmd("Network.enable", {})

# 模拟慢网速
driver.execute_cdp_cmd("Network.emulateNetworkConditions", {
    "offline": False,
    "latency": 300,
    "downloadThroughput": 100 * 1024,
    "uploadThroughput": 50 * 1024
})

2. 性能监控

# 页面加载性能
import time

start = time.time()
driver.get("https://example.com")
load_time = time.time() - start
print(f"页面加载: {load_time:.2f} 秒")

# JS 执行时间
nav_start = driver.execute_script("return performance.timing.navigationStart")
load_end = driver.execute_script("return performance.timing.loadEventEnd")
print(f"性能指标: {(load_end - nav_start) / 1000:.2f} 秒")

3. 页面对象模式(POM)

# 页面对象:封装页面元素与操作,提高可维护性
class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.username = (By.ID, "username")
        self.password = (By.ID, "password")
        self.submit = (By.ID, "submit")

    def open(self):
        self.driver.get("https://example.com/login")
        return self

    def login(self, user, pwd):
        self.driver.find_element(*self.username).send_keys(user)
        self.driver.find_element(*self.password).send_keys(pwd)
        self.driver.find_element(*self.submit).click()
        return self

# 使用
page = LoginPage(driver).open().login("admin", "123456")

4. 保存登录态复用

import json, pickle

# 保存 cookies
def save_cookies(driver, path="cookies.pkl"):
    with open(path, "wb") as f:
        pickle.dump(driver.get_cookies(), f)

# 恢复 cookies
def load_cookies(driver, path="cookies.pkl"):
    with open(path, "rb") as f:
        cookies = pickle.load(f)
    for c in cookies:
        driver.add_cookie(c)

# 使用:登录一次,之后直接恢复
driver.get("https://example.com")
load_cookies(driver)
driver.refresh()
# 已保持登录状态

5. 数据采集最佳实践

# 控制频率与请求头
options = Options()
options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
driver = webdriver.Chrome(options=options)

# 设置代理
options.add_argument("--proxy-server=http://127.0.0.1:7890")

# 等待与重试
from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def safe_find(driver, by, value):
    return driver.find_element(by, value)

Selenium 无头浏览器模式

Selenium 无头浏览器模式

1. 什么是无头模式

无头(headless)模式指浏览器在后台运行、不显示界面,适合服务器环境、CI/CD 管道和批量任务,资源占用小、速度快。

2. Chrome 无头模式

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")       # 新版无头模式
options.add_argument("--no-sandbox")         # Linux root 环境
options.add_argument("--disable-dev-shm-usage")  # 容器环境
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")

driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
print("标题:", driver.title)
driver.save_screenshot("headless.png")
driver.quit()

3. Firefox 无头模式

from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("-headless")
driver = webdriver.Firefox(options=options)
driver.get("https://example.com")
driver.quit()

4. 无头模式注意点

  • 无头模式下部分网站会检测并限制
  • JS 渲染仍需等待机制
  • 截图前确保窗口尺寸正确
  • Linux 服务器需安装浏览器依赖库

5. 反检测配置(尽量避免滥用)

options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(options=options)
# 移除 webdriver 标记
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")

6. CI/CD 集成示例

# GitHub Actions 中的无头测试
# .github/workflows/test.yml
name: UI Test
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install selenium pytest
      - run: |
          # 安装 Chrome
          wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
          sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list'
          sudo apt-get update && sudo apt-get install -y google-chrome-stable
      - run: pytest tests/ -m ui

Selenium 实战项目

Selenium 实战项目

1. 项目:登录-搜索-验证全流程

import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class SearchFlowTest:
    @pytest.fixture
    def driver(self):
        options = webdriver.ChromeOptions()
        options.add_argument("--headless=new")
        d = webdriver.Chrome(options=options)
        d.implicitly_wait(10)
        yield d
        d.quit()

    def test_search_flow(self, driver):
        # 1. 打开首页
        driver.get("https://www.baidu.com")
        assert "百度" in driver.title

        # 2. 输入关键词搜索
        box = driver.find_element(By.ID, "kw")
        box.send_keys("Selenium 教程")
        driver.find_element(By.ID, "su").click()

        # 3. 等待结果出现
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "result"))
        )

        # 4. 验证结果
        results = driver.find_elements(By.CLASS_NAME, "result")
        assert len(results) > 0
        first_text = results[0].text
        assert "Selenium" in first_text or "selenium" in first_text.lower()

        # 5. 截图
        driver.save_screenshot("search_result.png")

2. 项目:表单自动化填写

def test_form_fill(driver):
    driver.get("https://example.com/register")

    fields = {
        "username": "testuser",
        "email": "test@example.com",
        "phone": "13800001111"
    }
    for field_id, value in fields.items():
        driver.find_element(By.ID, field_id).send_keys(value)

    # 下拉选择
    from selenium.webdriver.support.ui import Select
    Select(driver.find_element(By.ID, "city")).select_by_visible_text("北京")

    # 勾选协议
    checkbox = driver.find_element(By.ID, "agree")
    if not checkbox.is_selected():
        checkbox.click()

    # 提交
    driver.find_element(By.ID, "submit").click()

    # 验证成功
    success = WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.CLASS_NAME, "success-msg"))
    )
    assert "注册成功" in success.text

3. 项目:数据采集

def test_scrape_list(driver):
    driver.get("https://example.com/products")

    # 滚动加载全部
    last_height = driver.execute_script("return document.body.scrollHeight")
    while True:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
        time.sleep(1)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height

    # 采集数据
    items = driver.find_elements(By.CLASS_NAME, "product-item")
    data = []
    for item in items:
        data.append({
            "name": item.find_element(By.CLASS_NAME, "name").text,
            "price": item.find_element(By.CLASS_NAME, "price").text,
        })

    # 保存 CSV
    import csv
    with open("products.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["name", "price"])
        writer.writeheader()
        writer.writerows(data)

    print(f"采集 {len(data)} 条数据")

4. 项目:监控与告警

def test_monitor(driver):
    driver.get("https://example.com/status")
    status = driver.find_element(By.ID, "service-status").text

    if "异常" in status:
        # 发送告警(邮件/钉钉/企业微信 webhook)
        import requests
        requests.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx",
                      json={"msgtype": "text", "text": {"content": f"服务异常: {status}"}})
        driver.save_screenshot("alert.png")
        assert False, f"服务状态异常: {status}"
    print("服务正常:", status)

5. 项目最佳实践总结

  • 使用页面对象模式(POM)组织代码
  • 显式等待代替固定 sleep
  • 测试数据与用例分离(CSV/JSON/YAML)
  • 失败自动截图并保留日志
  • 接入 CI 无头模式执行
  • 定期运行回归,关注元素变更
第 1 / 15 节