前置知识: 软件工程与测试

Selenium

3 min中级

Selenium Web自动化测试:WebDriver、定位策略、框架设计与最佳实践详解。

1. Selenium 概述

Selenium 是历史最悠久的浏览器自动化框架,核心是 WebDriver 协议(W3C 标准): 测试代码通过它驱动真实浏览器完成点击、输入、断言。适合遗留系统回归、 跨浏览器兼容验证;新建 Web 项目则建议优先评估 Playwright 或 Cypress (见「自动化测试框架对比」一文)。

前置知识:HTML/CSS 基础(选择器)、Python 基础、pytest 基本用法。

1.1 组件

组件描述
WebDriver浏览器自动化 API
IDE录制回放插件
Grid分布式测试执行

1.2 WebDriver 架构

测试代码 → WebDriver API → Browser Driver → 浏览器

每个浏览器由对应的 Driver 进程代理(ChromeDriver、GeckoDriver 等), 这也是 Selenium 比 Playwright 慢的原因之一——每次指令都走一次 HTTP 往返。

2. 环境搭建

2.1 Python + Selenium

pip install selenium pytest

Selenium 4.6 起内置 Selenium Manager,会自动下载并缓存匹配的浏览器驱动, 不再需要手工维护 chromedriver 或引入 webdriver-manager 这类第三方包。

2.2 基础示例

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

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

# 查找元素
element = driver.find_element(By.ID, "username")
element.send_keys("admin")

# 点击
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

# 断言
assert "Dashboard" in driver.title

driver.quit()

3. 元素定位

3.1 定位策略

策略By 常量示例
IDBy.IDfind_element(By.ID, "login-btn")
NameBy.NAMEfind_element(By.NAME, "email")
ClassBy.CLASS_NAMEfind_element(By.CLASS_NAME, "btn-primary")
CSSBy.CSS_SELECTORfind_element(By.CSS_SELECTOR, "#login .btn")
XPathBy.XPATHfind_element(By.XPATH, "//button[@type='submit']")
TagBy.TAG_NAMEfind_element(By.TAG_NAME, "input")
LinkBy.LINK_TEXTfind_element(By.LINK_TEXT, "Login")
PartialBy.PARTIAL_LINK_TEXTfind_element(By.PARTIAL_LINK_TEXT, "Log")

3.2 推荐优先级

ID > CSS Selector > XPath > 其他

4. 等待机制

4.1 显式等待

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

# 等待元素可见
element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "result"))
)

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

4.2 常用 Expected Conditions

条件描述
visibility_of_element_located元素可见
element_to_be_clickable元素可点击
presence_of_element_located元素存在于 DOM
text_to_be_present_in_element文本出现
title_contains标题包含
url_containsURL 包含

4.3 隐式等待

driver.implicitly_wait(10)  # 全局等待 10 秒

注意:不要混用显式和隐式等待。

5. Page Object 模式

5.1 页面对象

# pages/login_page.py
from selenium.webdriver.common.by import By

class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.username_input = (By.ID, "username")
        self.password_input = (By.ID, "password")
        self.login_button = (By.CSS_SELECTOR, "button[type='submit']")

    def login(self, username, password):
        self.driver.find_element(*self.username_input).send_keys(username)
        self.driver.find_element(*self.password_input).send_keys(password)
        self.driver.find_element(*self.login_button).click()

5.2 测试用例

# tests/test_login.py
import pytest
from selenium import webdriver
from pages.login_page import LoginPage

class TestLogin:
    @pytest.fixture
    def driver(self):
        driver = webdriver.Chrome()
        driver.get("https://example.com/login")
        yield driver
        driver.quit()

    def test_successful_login(self, driver):
        login_page = LoginPage(driver)
        login_page.login("admin", "password123")
        assert "Dashboard" in driver.title

    def test_invalid_password(self, driver):
        login_page = LoginPage(driver)
        login_page.login("admin", "wrong")
        assert "Invalid credentials" in driver.page_source

6. 高级操作

6.1 多窗口

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

# 切换回主窗口
driver.switch_to.window(driver.window_handles[0])

6.2 iframe

driver.switch_to.frame("iframe-id")
# 操作 iframe 内元素
driver.switch_to.default_content()

6.3 下拉选择

from selenium.webdriver.support.select import Select

select = Select(driver.find_element(By.ID, "country"))
select.select_by_visible_text("China")
select.select_by_value("CN")
select.select_by_index(0)

6.4 截图

driver.save_screenshot("screenshot.png")
element.screenshot("element.png")

6.5 Alert 弹窗处理

# 原生 alert/confirm/prompt 必须先切换到弹窗才能操作
alert = driver.switch_to.alert
print(alert.text)      # 读取弹窗文本
alert.accept()         # 点击「确定」
# alert.dismiss()     # 点击「取消」
# alert.send_keys("输入内容")  # 针对 prompt

6.6 ActionChains 鼠标与键盘动作

from selenium.webdriver.common.action_chains import ActionChains

menu = driver.find_element(By.ID, "menu")
submenu = driver.find_element(By.ID, "submenu")

actions = ActionChains(driver)
actions.move_to_element(menu)   # 悬停展开菜单
actions.click(submenu)          # 点击子项
actions.perform()               # 真正执行动作队列

7. 最佳实践

实践描述
Page Object封装页面元素和操作
显式等待避免硬编码 sleep
数据驱动分离测试数据
截图失败失败时自动截图
并行执行Grid/多线程
CI 集成Headless 模式
优先 CSSCSS 选择器优于 XPath

小结

  • 初学者要点:Selenium 的组件模型是「WebDriver 驱动浏览器、IDE 录制、 Grid 并行」;定位优先 ID,其次 CSS 选择器;等待一律用显式等待 (WebDriverWait + expected_conditions),sleep 是 flaky 的头号来源。
  • 进阶注意:Page Object 只封装「定位与操作」,不要把断言埋进页面对象; Selenium Manager(4.6+)接管了驱动管理,不要再手工维护 chromedriver; 每条指令一次 HTTP 往返的协议特性决定了它比 Playwright 慢——选型时用 真实约束(遗留浏览器、团队语言)而非习惯做决定(对比详见「自动化测试 框架对比」)。