作为一位在测试领域摸爬滚打多年的老手,我深知一个高效的自动化测试框架对团队效率的提升有多重要。今天要分享的这个基于Python+Selenium的测试框架,是我经过多个项目实战后提炼出来的精华版本,特别适合中小型项目的自动化测试需求。
这个框架最大的特点就是"模块化"和"可维护性"。采用Page Object模式将页面元素定位与业务逻辑分离,用YAML管理测试数据,配合DDT实现数据驱动测试。在实际项目中,这套架构让我们的用例维护工作量减少了60%,新成员上手速度提升了3倍。下面我就从设计理念到具体实现,带大家完整走一遍这个框架的搭建过程。
Python 3 + Selenium 3的组合在自动化测试领域已经成为事实标准。Python简洁的语法和丰富的测试库生态,加上Selenium强大的浏览器控制能力,可以应对绝大多数Web UI自动化测试场景。
选择YAML作为数据管理格式是因为:
PO(Page Object)设计模式的价值在于:
这个框架采用经典的四层架构:
code复制├── 基础层(Base)
│ ├── 浏览器操作封装
│ ├── 元素查找封装
│ └── 常用工具方法
├── 业务层(Pages)
│ ├── 页面对象类
│ └── 业务流程组合
├── 用例层(TestCases)
│ ├── 测试用例
│ └── 数据驱动
└── 框架层(Framework)
├── 报告生成
├── 异常处理
└── 邮件通知
这种分层带来的好处是:
首先需要安装以下依赖:
bash复制pip install selenium==3.141.0
pip install PyYAML==5.4.1
pip install ddt==1.4.2
pip install HTMLTestRunner==1.0.3
建议使用virtualenv创建隔离环境:
bash复制python -m venv testenv
source testenv/bin/activate # Linux/Mac
testenv\Scripts\activate # Windows
标准的项目结构应该如下:
code复制DemoUI/
├── config/ # 配置文件
│ └── setting.py # 全局配置
├── testdata/ # 测试数据
│ └── *.yaml # YAML数据文件
├── testyaml/ # 元素定位
│ └── *.yaml # 页面元素YAML
├── public/ # 公共组件
│ ├── page_obj/ # 页面对象
│ ├── models/ # 工具类
│ └── lib/ # 第三方库
├── testcase/ # 测试用例
│ └── *_sta.py # 测试脚本
├── report/ # 测试报告
│ ├── html/ # HTML报告
│ └── screenshot/ # 截图
└── run.py # 主入口
提示:可以使用tree命令生成目录结构文档,方便团队共享
以登录页面为例,首先在testyaml/login.yaml定义元素:
yaml复制# 登录页面元素定位
- login-link-a # 登录链接
- mobile # 手机号输入框
- mbpwd # 密码输入框
- //input[@class='keeplogin'] # 自动登录复选框
- //span[text()='登录'] # 登录按钮
- userProNick # 用户昵称
- //a[@class='logout'] # 退出链接
然后在page_obj/loginPage.py中封装页面操作:
python复制class LoginPage(Page):
"""登录页面封装"""
def __init__(self, driver):
super().__init__(driver)
self.elements = get_yaml('login.yaml') # 加载元素定位
@property
def phone_input(self):
return self.find_element(By.ID, self.elements[1])
def input_phone(self, phone):
self.phone_input.clear()
self.phone_input.send_keys(phone)
def login(self, phone, pwd):
self.input_phone(phone)
self.input_password(pwd)
self.click_login_button()
测试数据存储在testdata/login_data.yaml:
yaml复制- id: TC_LOGIN_001
detail: "空密码测试"
data:
phone: "13800001111"
password: ""
check:
- "密码不能为空"
screenshot: "empty_pwd"
使用ddt加载测试数据:
python复制@ddt.ddt
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
@ddt.file_data('testdata/login_data.yaml')
def test_login(self, case):
"""登录测试用例"""
page = LoginPage(self.driver)
page.login(case['data']['phone'], case['data']['password'])
self.assertIn(case['check'][0], page.get_error_msg())
框架中需要特别处理以下几种异常:
实现方案:
python复制def find_element(self, by, value, timeout=10):
try:
return WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((by, value))
)
except TimeoutException:
self.logger.error(f"元素定位失败: {by}={value}")
self.save_screenshot("element_not_found")
raise
使用Selenium Grid实现:
python复制def setup_driver():
capabilities = {
"browserName": "chrome",
"version": "latest",
"platform": "WINDOWS"
}
return webdriver.Remote(
command_executor='http://grid-hub:4444/wd/hub',
desired_capabilities=capabilities
)
集成HTMLTestRunner:
python复制def run_tests():
suite = unittest.TestLoader().discover('testcase')
with open('report/report.html', 'wb') as f:
runner = HTMLTestRunner(
stream=f,
title='自动化测试报告',
description='测试结果详情'
)
runner.run(suite)
使用smtplib发送测试报告:
python复制def send_email(report_file):
msg = MIMEMultipart()
msg['Subject'] = '自动化测试报告'
msg['From'] = 'sender@example.com'
msg['To'] = 'receiver@example.com'
with open(report_file, 'rb') as f:
att = MIMEApplication(f.read())
att.add_header('Content-Disposition', 'attachment', filename='report.html')
msg.attach(att)
smtp = smtplib.SMTP('smtp.example.com')
smtp.send_message(msg)
smtp.quit()
python复制driver.find_element(By.XPATH, "//div[contains(@id,'temp')]")
python复制def wait_for_text(driver, locator, text, timeout=10):
try:
WebDriverWait(driver, timeout).until(
lambda d: text in d.find_element(*locator).text
)
return True
except TimeoutException:
return False
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 元素找不到 | 1. 定位表达式错误 2. 页面未完全加载 3. 元素在iframe中 |
1. 检查定位表达式 2. 增加等待时间 3. 切换iframe |
| 测试不稳定 | 1. 网络延迟 2. 动态内容加载慢 |
1. 增加超时时间 2. 添加重试机制 |
| 浏览器崩溃 | 1. 版本不兼容 2. 内存不足 |
1. 更新驱动版本 2. 减少并发数 |
python复制# Chrome headless模式配置
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)
通过Appium扩展移动端测试能力:
python复制desired_caps = {
'platformName': 'Android',
'deviceName': 'emulator-5554',
'app': '/path/to/app.apk'
}
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
结合requests库实现接口测试:
python复制def test_api_login():
url = "https://api.example.com/login"
data = {"phone": "13800138000", "password": "123456"}
resp = requests.post(url, json=data)
assert resp.status_code == 200
assert resp.json()['success'] is True
使用Allure生成高级报告:
python复制import allure
@allure.feature('登录模块')
class TestLogin:
@allure.story('手机号登录')
def test_phone_login(self):
with allure.step('输入手机号'):
page.input_phone('13800001111')
with allure.step('输入密码'):
page.input_password('123456')
with allure.step('点击登录'):
page.click_login()
安装必要插件:
创建Pipeline任务:
groovy复制pipeline {
agent any
stages {
stage('Checkout') {
steps {
git 'https://github.com/your/repo.git'
}
}
stage('Test') {
steps {
sh 'python run.py'
}
}
stage('Report') {
steps {
publishHTML(
target: [
allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: true,
reportDir: 'report',
reportFiles: 'report.html',
reportName: 'HTML Report'
]
)
}
}
}
}
分支管理:
提交规范:
命名规范:
注释要求:
python复制def test_login_with_empty_phone(self):
"""测试场景:手机号为空
预期结果:提示"手机号不能为空"
"""
| 指标名称 | 计算方式 | 目标值 |
|---|---|---|
| 用例通过率 | 通过用例数/总用例数 | ≥95% |
| 缺陷发现率 | 发现缺陷数/执行用例数 | ≥15% |
| 平均执行时间 | 总耗时/用例数 | ≤30s |
这套框架在我所在团队已经稳定运行2年多,支撑了超过5000次的自动化测试执行,平均为每个项目节省了200+人时的手工测试工作量。特别是在频繁回归测试的场景下,优势尤为明显。