Selenium IDE导出的Python脚本太乱?手把手教你优化与封装,打造可复用的测试模块
·
Selenium IDE导出的Python脚本太乱?手把手教你优化与封装,打造可复用的测试模块
当Selenium IDE自动生成的Python脚本第一次出现在你眼前时,那种扑面而来的混乱感可能让你措手不及——冗余的导入、硬编码的URL、重复的操作逻辑,还有那个看起来像是从Java穿越过来的类结构。这就像收到一份未经整理的原材料清单,而非可以直接下厨的食谱。本文将带你从工程化角度,将这些"原材料"转化为可维护、可复用的测试代码库。
1. 解剖Selenium IDE生成的Python脚本
典型的Selenium IDE导出脚本包含几个明显的"代码异味":
# 原始生成的冗余代码示例
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities # 从未使用的导入
self.vars = {} # 未被利用的变量字典
WebDriverWait(driver, 10).until(expected_conditions.presence_of_element_located((By.ID, "foo"))) # 不必要的等待
安全删除指南 (保留核心功能的最小集合):
- 移除所有未实际使用的import语句
- 删除空的
self.vars字典及其相关操作 - 简化过度设计的类结构(除非确实需要setup/teardown)
- 合并重复的元素定位操作
注意:删除前务必确认代码功能不受影响,建议配合git进行版本控制
2. 参数化配置:从硬编码到可配置
将以下硬编码值提取为配置参数:
- 浏览器驱动路径
- 测试目标URL
- 窗口尺寸
- 隐式等待时间
推荐使用Python的 configparser 管理配置文件:
; config.ini
[DRIVER]
path = /usr/local/bin/msedgedriver
implicit_wait = 10
[TEST_SITE]
url = https://www.baidu.com
window_size = 1088,816
对应的读取代码:
from configparser import ConfigParser
config = ConfigParser()
config.read('config.ini')
driver_path = config.get('DRIVER', 'path')
target_url = config.get('TEST_SITE', 'url')
width, height = map(int, config.get('TEST_SITE', 'window_size').split(','))
3. 使用Pytest框架重构测试用例
原始脚本的测试组织方式存在几个问题:
- 缺乏断言验证
- 没有清晰的测试隔离
- 难以生成测试报告
改造后的Pytest实现:
import pytest
from selenium import webdriver
@pytest.fixture
def browser():
driver = webdriver.Edge()
driver.implicitly_wait(10)
yield driver
driver.quit()
def test_search_functionality(browser):
browser.get("https://www.baidu.com")
search_box = browser.find_element(By.ID, "kw")
search_box.send_keys("pytest selenium")
search_box.send_keys(Keys.RETURN)
# 验证搜索结果
assert "pytest" in browser.title
results = browser.find_elements(By.CSS_SELECTOR, ".result h3")
assert len(results) > 0
关键改进点:
- 使用fixture管理浏览器生命周期
- 添加了结果验证断言
- 支持并行测试执行
- 可生成HTML测试报告
4. 页面操作封装与复用
将常见操作抽象为独立函数/类:
class BaiduPage:
def __init__(self, driver):
self.driver = driver
self.search_input = (By.ID, "kw")
self.search_button = (By.ID, "su")
def search(self, keyword):
self.driver.find_element(*self.search_input).send_keys(keyword)
self.driver.find_element(*self.search_button).click()
return SearchResultsPage(self.driver)
class SearchResultsPage:
def __init__(self, driver):
self.driver = driver
self.results = (By.CSS_SELECTOR, ".result h3")
def get_result_titles(self):
return [el.text for el in self.driver.find_elements(*self.results)]
使用示例:
def test_search_flow(browser):
page = BaiduPage(browser)
results_page = page.search("自动化测试")
titles = results_page.get_result_titles()
assert any("selenium" in t.lower() for t in titles)
5. 高级封装技巧
等待策略优化 :
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def wait_for_element(driver, locator, timeout=10):
return WebDriverWait(driver, timeout).until(
EC.presence_of_element_located(locator)
)
# 使用示例
element = wait_for_element(driver, (By.ID, "dynamic-content"))
日志记录增强 :
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('test.log'),
logging.StreamHandler()
]
)
def log_action(func):
def wrapper(*args, **kwargs):
logging.info(f"Executing: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_action
def click_element(driver, locator):
driver.find_element(*locator).click()
跨浏览器支持 :
def create_driver(browser_name="edge"):
if browser_name.lower() == "chrome":
return webdriver.Chrome()
elif browser_name.lower() == "firefox":
return webdriver.Firefox()
else:
return webdriver.Edge()
6. 持续集成准备
将优化后的测试模块集成到CI流程中:
.github/workflows/test.yml 示例:
name: Selenium Tests
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install selenium pytest
- name: Run tests
run: |
python -m pytest tests/ --html=report.html
- name: Upload report
uses: actions/upload-artifact@v2
with:
name: test-report
path: report.html
7. 常见问题解决方案
元素定位失败处理 :
def safe_click(driver, locator, max_retries=3):
for attempt in range(max_retries):
try:
driver.find_element(*locator).click()
return True
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(1 * (attempt + 1))
return False
测试数据管理 :
import csv
def get_test_data(filename):
with open(filename, newline='') as csvfile:
return list(csv.DictReader(csvfile))
# 数据驱动测试示例
@pytest.mark.parametrize("data", get_test_data("search_terms.csv"))
def test_multiple_searches(browser, data):
page = BaiduPage(browser)
results_page = page.search(data["term"])
assert data["expected"] in results_page.get_result_titles()[0]
截图与错误收集 :
def take_screenshot(driver, name):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
path = f"screenshots/{name}_{timestamp}.png"
driver.save_screenshot(path)
return path
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
driver = item.funcargs.get("browser")
if driver:
screenshot = take_screenshot(driver, item.name)
report.extra = [pytest_html.extras.image(screenshot)]
更多推荐


所有评论(0)