1. 项目概述
最近在重构公司内部博客系统时,我深刻体会到手动测试的局限性——每次迭代都要重复验证登录、列表展示、编辑提交等核心流程,不仅耗时耗力,还容易遗漏关键场景。于是决定搭建一套完整的UI自动化测试框架,经过两周的实战,终于实现了从0到1的突破。这套基于Python+Selenium的框架已经稳定运行3个月,累计发现27个回归问题,测试效率提升80%。下面就把完整实现过程分享给大家。
2. 环境准备与框架设计
2.1 技术选型解析
选择Python+Selenium组合主要基于三点考量:
- 生态成熟:Selenium支持所有主流浏览器,Python丰富的测试库(如pytest)可以扩展功能
- 维护成本低:相比Java方案,Python脚本更简洁,适合快速迭代的Web项目
- 团队适配:我们开发团队主要使用C++,Python作为辅助语言学习成本最低
实际测试中发现Chrome 110+版本与Selenium 4.10.0兼容性最佳,建议锁定这两个版本
2.2 环境配置实操
bash复制# 推荐使用virtualenv创建隔离环境
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# 核心依赖安装
pip install selenium==4.10.0 webdriver-manager==4.0.0
浏览器驱动管理采用webdriver-manager的方案,相比手动下载驱动有三大优势:
- 自动检测本地浏览器版本
- 下载匹配的驱动程序
- 自动配置环境变量路径
2.3 目录结构设计
code复制blog_auto_test/
├── common/ # 核心工具类
│ ├── __init__.py
│ ├── Utils.py # 驱动管理
│ └── Reporter.py # 报告生成(扩展)
├── cases/ # 测试用例
│ ├── __init__.py
│ ├── BlogLogin.py
│ ├── BlogList.py
│ ├── BlogDetail.py
│ └── BlogEdit.py
├── config/ # 配置文件
│ └── settings.py # URL/账号等配置
├── logs/ # 运行日志
├── images/ # 失败截图
└── RunCases.py # 执行入口
3. 核心模块实现
3.1 驱动管理封装
在common/Utils.py中实现智能驱动管理:
python复制class Driver:
"""增强版驱动管理器"""
_instance = None # 单例模式
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance._init_driver()
return cls._instance
def _init_driver(self):
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--disable-gpu')
options.add_argument('window-size=1920,1080')
# 无头模式配置(CI环境使用)
if os.getenv('HEADLESS') == 'true':
options.add_argument('--headless=new')
options.add_argument('--no-sandbox')
self.driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
self.driver.implicitly_wait(8) # 全局等待时间
关键改进点:
- 采用单例模式确保全局唯一驱动实例
- 通过环境变量
HEADLESS控制无头模式 - 增加沙箱模式禁用选项(部分Linux环境需要)
3.2 智能截图功能
python复制def get_screen_shot(self, element=None):
"""支持元素局部截图和全屏截图"""
try:
# 创建日期目录
today = datetime.date.today().strftime('%Y-%m-%d')
screenshot_dir = f"../images/{today}"
os.makedirs(screenshot_dir, exist_ok=True)
# 生成带调用栈信息的文件名
frame = sys._getframe().f_back
case_name = frame.f_code.co_name
timestamp = datetime.datetime.now().strftime('%H%M%S')
filename = f"{case_name}-{timestamp}.png"
if element: # 元素截图
element.screenshot(os.path.join(screenshot_dir, filename))
else: # 全屏截图
self.driver.save_screenshot(os.path.join(screenshot_dir, filename))
return os.path.abspath(os.path.join(screenshot_dir, filename))
except Exception as e:
logging.error(f"截图失败: {str(e)}")
return None
4. 测试用例开发实战
4.1 登录模块深度优化
在cases/BlogLogin.py中实现多维度验证:
python复制def login_suc_test(self, username, password):
"""增强版登录成功测试"""
try:
# 清除浏览器缓存确保初始状态
self.driver.delete_all_cookies()
# 操作流程
self._input_credentials(username, password)
self._click_login()
# 多维度验证
assert self._check_redirect(), "页面跳转验证失败"
assert self._check_session(), "Session设置验证失败"
assert self._check_ui_update(), "UI状态更新失败"
# 性能监控
start_time = time.time()
self._load_user_profile()
assert time.time() - start_time < 2.0, "用户数据加载超时"
except AssertionError as e:
self._handle_failure(e)
新增验证维度:
- 页面跳转是否正确
- 会话cookie是否设置
- 用户头像等UI元素是否更新
- 接口响应时间监控
4.2 列表页测试增强
cases/BlogList.py增加分页和排序测试:
python复制def test_pagination(self):
"""博客分页功能测试"""
pagination = self.driver.find_element(By.CLASS_NAME, 'pagination')
page_links = pagination.find_elements(By.TAG_NAME, 'a')
for i in range(min(3, len(page_links))): # 测试前3页
page_links[i].click()
time.sleep(1) # 等待加载
blogs = self._get_blog_items()
assert len(blogs) >= 10, f"第{i+1}页博客数量不足"
# 验证分页参数
assert f"page={i+1}" in self.driver.current_url, "分页参数错误"
5. 高级技巧与优化
5.1 智能等待策略
python复制def wait_for_element(self, by, value, timeout=10):
"""显式等待元素出现"""
try:
return WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((by, value))
)
except TimeoutException:
self.get_screen_shot()
raise ElementNotFoundError(f"元素定位失败: {by}={value}")
推荐组合等待策略:
- 全局隐式等待:
implicitly_wait(5) - 关键操作显式等待
- 固定休眠仅用于动画场景
5.2 失败自动重试
通过pytest插件实现:
python复制# conftest.py
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if rep.failed and 'retry' in item.keywords:
item.add_marker(pytest.mark.xfail(reason="Retry on failure"))
# 用例中使用
@pytest.mark.retry(max_retries=2)
def test_login_fail():
...
6. 持续集成方案
6.1 GitHub Actions配置
yaml复制name: UI Automation Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
env:
HEADLESS: 'true'
run: |
python -m pytest cases/ -v --html=reports/report.html
- name: Upload report
uses: actions/upload-artifact@v3
with:
name: ui-test-report
path: reports/
6.2 测试报告优化
使用pytest-html生成增强报告:
python复制# conftest.py
def pytest_html_report_title(report):
report.title = "博客系统UI自动化测试报告"
@pytest.hookimpl(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('driver')
if driver:
html = f'<div><img src="{driver.get_screen_shot()}" style="width:100%"></div>'
report.extra = [pytest_html.extras.html(html)]
7. 踩坑经验总结
-
元素定位失效问题
- 现象:脚本运行时突然找不到元素
- 原因:前端框架动态生成DOM导致元素ID变化
- 解决:改用相对定位策略(如XPath轴定位)
-
异步加载等待问题
- 现象:断言时元素尚未加载完成
- 优化:在关键操作后添加等待条件
python复制WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element( (By.CLASS_NAME, 'status'), '提交成功' ) ) -
跨浏览器兼容问题
- 解决方案:使用Selenium Grid搭建多浏览器测试环境
python复制capabilities = { "browserName": "firefox", "browserVersion": "latest", "platformName": "linux" } driver = webdriver.Remote( command_executor='http://grid:4444', options=Options(**capabilities) )
这套框架目前已经稳定运行3个月,累计执行测试用例超过1200次,发现各类问题47个。最大的收获不是技术本身,而是通过自动化测试建立的质量保障体系,让团队可以更自信地持续交付。