1. 环境准备:搭建Python+Selenium+Edge测试三件套
1.1 Python环境配置
作为自动化测试的基础运行环境,我强烈推荐使用Python 3.8+版本。这个版本区间既稳定又兼容绝大多数测试库。安装时务必勾选"Add Python to PATH"选项,这是后续操作顺畅的关键。验证安装是否成功只需在CMD执行:
bash复制python --version
pip --version
新手常犯的错误是使用系统自带的Python 2.7或未配置环境变量,这会导致后续包管理混乱。我建议通过官方下载页面获取安装包,避免第三方修改版带来的兼容性问题。
1.2 Selenium库安装
通过pip安装时建议使用清华镜像源加速下载:
bash复制pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后可以执行以下验证代码:
python复制from selenium import webdriver
print(webdriver.__version__)
注意不要同时安装多个版本的Selenium,这会导致WebDriver API调用异常。如果之前有旧版,先用pip uninstall selenium彻底卸载。
1.3 EdgeDriver配置
微软Edge浏览器需要匹配的WebDriver版本。获取方式有两种:
- 通过Edge浏览器设置→关于Microsoft Edge查看版本号
- 访问Microsoft Edge WebDriver官网下载对应版本
将下载的msedgedriver.exe放在以下任一位置:
- Python安装目录的Scripts文件夹
- 系统PATH包含的任意目录
- 项目根目录(需要在代码中指定路径)
验证驱动是否生效:
python复制from selenium.webdriver import Edge
driver = Edge() # 如果能启动浏览器即配置成功
重要提示:Edge浏览器和WebDriver必须版本完全匹配,否则会出现无法预料的兼容性问题。建议关闭浏览器自动更新或建立版本锁定机制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心API实战:从元素定位到断言验证
2.1 八种元素定位策略对比
Selenium提供多种元素定位方式,根据我的项目经验,它们的适用场景如下:
| 定位方式 | 示例代码 | 适用场景 | 稳定性 |
|---|---|---|---|
| ID | find_element(By.ID, "username") | 唯一静态元素 | ★★★★★ |
| CSS选择器 | find_element(By.CSS_SELECTOR, "#login .btn") | 复杂样式元素 | ★★★★☆ |
| XPath | find_element(By.XPATH, "//input[@name='email']") | 动态ID元素 | ★★★☆☆ |
| 链接文本 | find_element(By.LINK_TEXT, "忘记密码") | 纯文本链接 | ★★★★☆ |
| 部分链接文本 | find_element(By.PARTIAL_LINK_TEXT, "密码") | 模糊匹配链接 | ★★★☆☆ |
| 类名 | find_element(By.CLASS_NAME, "submit-btn") | 样式类元素 | ★★☆☆☆ |
| 标签名 | find_element(By.TAG_NAME, "textarea") | 表单元素 | ★★☆☆☆ |
| Name属性 | find_element(By.NAME, "captcha") | 传统表单 | ★★★★☆ |
实际项目中我推荐优先使用ID和CSS选择器,它们的性能最好且不易受页面结构调整影响。XPath虽然强大但执行效率较低,适合处理动态生成的元素。
2.2 常用交互操作封装
以下是我在多个项目中提炼的通用操作封装:
python复制from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
class EdgeAutomator:
def __init__(self, driver):
self.driver = driver
def safe_click(self, locator, timeout=10):
"""带等待的安全点击"""
element = WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable(locator)
)
element.click()
def input_text(self, locator, text, clear=True):
"""文本输入最佳实践"""
element = self.driver.find_element(*locator)
if clear:
element.send_keys(Keys.CONTROL + 'a')
element.send_keys(Keys.BACKSPACE)
element.send_keys(text)
def hover_and_click(self, hover_locator, click_locator):
"""悬浮菜单操作链"""
actions = ActionChains(self.driver)
actions.move_to_element(self.driver.find_element(*hover_locator))
actions.click(self.driver.find_element(*click_locator))
actions.perform()
这些封装方法解决了原生API的三个痛点:
- 直接click()可能遇到元素不可点击异常
- 简单send_keys()不会清空原有内容
- 二级菜单需要先hover才能点击
2.3 等待机制深度解析
Selenium的三种等待方式在实际项目中的使用比例应该是:显式等待(70%) > 隐式等待(20%) > 固定等待(10%)。来看具体实现:
python复制from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 最佳实践:显式等待
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "dynamic-content"))
)
# 备用方案:隐式等待(全局生效)
driver.implicitly_wait(5) # 每次查找元素最多等5秒
# 不得已方案:固定等待(慎用)
import time
time.sleep(3) # 会阻塞整个线程
经验之谈:Ajax加载的内容必须使用显式等待,配合EC.visibility_of_element_located判断元素可见性而不仅是存在。我曾遇到元素已存在DOM但不可点击导致测试失败的案例。
3. 测试框架集成:unittest与Pytest实战
3.1 unittest标准模板
基于unittest的测试类标准结构:
python复制import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Edge()
cls.driver.maximize_window()
def setUp(self):
self.driver.get("https://example.com/login")
def test_valid_login(self):
self.driver.find_element(By.ID, "username").send_keys("admin")
self.driver.find_element(By.ID, "password").send_keys("123456")
self.driver.find_element(By.CSS_SELECTOR, ".login-btn").click()
self.assertIn("Dashboard", self.driver.title)
def test_invalid_password(self):
# 测试错误密码场景
pass
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
关键点说明:
- setUpClass/tearDownClass:整个测试类只执行一次
- setUp/tearDown:每个测试方法前后执行
- 测试方法必须以test_开头
- 断言使用unittest内置的assert系列方法
3.2 Pytest高级用法
Pytest相比unittest更灵活,这是我的推荐配置:
- 安装必要插件:
bash复制pip install pytest pytest-html pytest-xdist
- conftest.py配置共享fixture:
python复制import pytest
from selenium import webdriver
@pytest.fixture(scope="session")
def browser():
driver = webdriver.Edge()
driver.implicitly_wait(10)
yield driver
driver.quit()
@pytest.fixture
def login_page(browser):
browser.get("https://example.com/login")
return LoginPage(browser) # 假设有PageObject封装
- 测试用例示例:
python复制def test_search_functionality(browser):
browser.get("https://example.com")
search_box = browser.find_element(By.NAME, "q")
search_box.send_keys("Selenium" + Keys.RETURN)
assert "Selenium" in browser.title
@pytest.mark.parametrize("username,password", [
("admin", "123456"),
("test", "test123")
])
def test_parametrized_login(login_page, username, password):
login_page.enter_credentials(username, password)
assert login_page.is_logged_in()
Pytest的优势在于:
- fixture依赖注入机制
- 参数化测试支持
- 丰富的插件生态(如pytest-html生成报告)
- 并行测试支持(pytest-xdist)
4. 企业级实战技巧与排坑指南
4.1 常见异常处理方案
在我的自动化测试实践中,这些异常出现频率最高:
-
NoSuchElementException
- 根本原因:元素定位策略失效
- 解决方案:
python复制try: element = driver.find_element(By.ID, "non-existent") except NoSuchElementException: print("元素未找到,尝试备用定位方案") element = driver.find_element(By.NAME, "alternative")
-
ElementNotInteractableException
- 根本原因:元素被遮挡或不可见
- 解决方案:
python复制element = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "button")) ) driver.execute_script("arguments[0].click();", element)
-
StaleElementReferenceException
- 根本原因:DOM重新渲染导致元素引用失效
- 解决方案:
python复制def safe_click(driver, locator, retries=3): for i in range(retries): try: driver.find_element(*locator).click() return except StaleElementReferenceException: if i == retries - 1: raise time.sleep(1)
4.2 测试数据管理策略
根据项目规模推荐不同的测试数据方案:
小型项目(JSON文件)
python复制import json
with open("testdata/login.json") as f:
test_cases = json.load(f)
for case in test_cases:
# 使用case["username"], case["password"]等字段
中型项目(Python类封装)
python复制class TestData:
@staticmethod
def valid_credentials():
return {"username": "qa", "password": "Secure123"}
@staticmethod
def invalid_combinations():
return [
{"username": "", "password": "123", "error": "请输入用户名"},
{"username": "admin", "password": "", "error": "请输入密码"}
]
大型项目(数据库集成)
python复制import pymysql
def get_test_data(scenario):
conn = pymysql.connect(host='test-db', user='qa')
try:
with conn.cursor() as cursor:
sql = "SELECT * FROM test_cases WHERE scenario=%s"
cursor.execute(sql, (scenario,))
return cursor.fetchall()
finally:
conn.close()
4.3 跨平台执行方案
在CI/CD环境中运行时需要考虑:
-
无头模式配置
python复制from selenium.webdriver.edge.options import Options options = Options() options.add_argument("--headless") options.add_argument("--disable-gpu") driver = webdriver.Edge(options=options) -
Selenium Grid分布式执行
python复制from selenium.webdriver.remote.webdriver import WebDriver capabilities = { "browserName": "MicrosoftEdge", "platform": "WINDOWS" } driver = WebDriver( command_executor="http://grid-hub:4444/wd/hub", desired_capabilities=capabilities ) -
Docker容器化方案
dockerfile复制FROM selenium/standalone-edge COPY . /tests WORKDIR /tests RUN pip install -r requirements.txt CMD ["pytest", "--html=report.html"]
4.4 性能优化技巧
通过多年项目积累,这些优化措施能显著提升执行效率:
-
浏览器配置调优
python复制options = Options() options.set_capability("pageLoadStrategy", "eager") # 不等待完整加载 options.add_argument("--blink-settings=imagesEnabled=false") # 禁用图片 -
智能等待策略
python复制def wait_for_ajax(driver): WebDriverWait(driver, 10).until( lambda d: d.execute_script("return jQuery.active == 0") ) -
并行测试执行
bash复制pytest -n 4 # 使用4个worker并行执行 -
缓存登录状态
python复制def login_once_and_save_cookie(driver): if not os.path.exists("cookies.pkl"): # 正常登录流程 with open("cookies.pkl", "wb") as f: pickle.dump(driver.get_cookies(), f) else: driver.get("about:blank") with open("cookies.pkl", "rb") as f: for cookie in pickle.load(f): driver.add_cookie(cookie)
这些实战经验来自我参与的多个企业级测试项目,每一条技巧背后都是解决实际问题的积累。特别是在处理动态内容加载和跨平台兼容性时,上述方案能节省大量调试时间。
