zhouhui.jiang

添加浏览器自动化测试

Showing 47 changed files with 726 additions and 0 deletions
......@@ -184,3 +184,4 @@ public class DeliveryMainController {
......
......@@ -187,3 +187,4 @@ public class InvoiceMainController {
......
......@@ -63,3 +63,4 @@ public class DeliveryAddReq {
......
......@@ -39,3 +39,4 @@ public class DeliveryItemAddReq {
......
......@@ -61,3 +61,4 @@ public class DeliveryItemRes {
......
......@@ -42,3 +42,4 @@ public class DeliveryItemUpdateReq {
......
......@@ -69,3 +69,4 @@ public class DeliveryRes {
......
......@@ -67,3 +67,4 @@ public class DeliveryUpdateReq {
......
......@@ -44,3 +44,4 @@ public class InvoiceItemAddReq {
......
......@@ -64,3 +64,4 @@ public class InvoiceItemRes {
......
......@@ -47,3 +47,4 @@ public class InvoiceItemUpdateReq {
......
......@@ -57,3 +57,4 @@ public class InvoiceQueryReq {
......
......@@ -68,3 +68,4 @@ public class DeliveryItem {
......
......@@ -74,3 +74,4 @@ public class InvoiceItem {
......
......@@ -83,3 +83,4 @@ public class InvoiceMain {
......
......@@ -16,3 +16,4 @@ public interface DeliveryItemMapper extends BaseMapper<DeliveryItem> {
......
......@@ -16,3 +16,4 @@ public interface InvoiceItemMapper extends BaseMapper<InvoiceItem> {
......
......@@ -16,3 +16,4 @@ public interface InvoiceMainMapper extends BaseMapper<InvoiceMain> {
......
......@@ -85,3 +85,4 @@ public interface DeliveryMainService extends IService<DeliveryMain> {
......
......@@ -85,3 +85,4 @@ public interface InvoiceMainService extends IService<InvoiceMain> {
......
# 浏览器自动化测试
基于 Python 3.7.8 和 Playwright 的 Apple ERP 系统浏览器自动化测试框架。
## 环境要求
- Python 3.7.8+
- Playwright 浏览器自动化框架
## 安装依赖
```bash
# 安装 Python 依赖
pip install -r requirements.txt
# 安装 Playwright 浏览器
playwright install chromium
```
## 项目结构
```
tests/browser/
├── __init__.py # 包初始化
├── base_test.py # 基础测试类
├── test_login.py # 登录功能测试
├── test_order_management.py # 订单管理功能测试
├── test_runner.py # 测试运行器
├── requirements.txt # 依赖文件
├── README.md # 说明文档
├── screenshots/ # 测试截图目录
└── reports/ # 测试报告目录
```
## 使用方法
### 1. 单独运行测试
```bash
# 运行登录测试
python test_login.py
# 运行订单管理测试
python test_order_management.py
```
### 2. 运行所有测试
```bash
# 运行所有测试并生成报告
python test_runner.py
```
### 3. 环境变量配置
```bash
# 设置前端应用地址(默认: http://localhost:3000)
export FRONTEND_URL=http://localhost:3000
# 设置浏览器模式(默认: 非无头模式)
export HEADLESS=false
```
## 测试功能
### 登录测试 (test_login.py)
- ✅ 成功登录测试
- ✅ 无效用户名测试
- ✅ 无效密码测试
- ✅ 空凭据测试
- ✅ 记住我功能测试
### 订单管理测试 (test_order_management.py)
- ✅ 订单列表显示测试
- ✅ 按订单编号搜索测试
- ✅ 按经销商搜索测试
- ✅ 订单状态筛选测试
- ✅ 重置搜索测试
- ✅ 分页功能测试
- ✅ 表格操作测试
- ✅ 操作按钮测试
## 测试报告
测试完成后会自动生成两种格式的报告:
### HTML 报告
- 美观的可视化界面
- 测试统计信息
- 详细的测试结果
- 截图链接
### JSON 报告
- 机器可读的格式
- 完整的测试数据
- 便于集成到CI/CD
## 截图功能
- 每个测试步骤都会自动截图
- 失败测试会额外截图错误状态
- 截图保存在 `screenshots/` 目录
- 支持全页面截图
## 配置选项
### BaseBrowserTest 配置
```python
# 创建测试实例
test = BaseBrowserTest(
headless=False, # 是否无头模式
slow_mo=500 # 操作间隔时间(毫秒)
)
```
### 元素定位策略
基于前端代码分析,使用以下定位策略:
**登录页面元素:**
- 用户名输入框: `input[placeholder="请输入用户名"]`
- 密码输入框: `input[placeholder="请输入密码"]`
- 登录按钮: `button:has-text("登录")`
- 记住我复选框: `input[type="checkbox"]`
**订单管理页面元素:**
- 订单编号搜索: `input[placeholder="请输入订单编号"]`
- 经销商编码搜索: `input[placeholder="请输入经销商编码"]`
- 经销商名称搜索: `input[placeholder="请输入经销商名称"]`
- 搜索按钮: `button:has-text("🔍 搜索")`
- 重置按钮: `button:has-text("🔄 重置")`
- 数据表格: `.data-table`
## 扩展测试
### 添加新的测试用例
1. 继承 `BaseBrowserTest`
2. 实现测试方法
3. 使用提供的辅助方法进行元素操作
4. 添加截图和断言
```python
class MyTest(BaseBrowserTest):
def test_my_feature(self):
# 设置页面
self.setup_my_page()
# 执行测试操作
self.click_element('button:has-text("我的按钮")')
# 验证结果
assert self.is_element_visible('.success-message')
# 截图
self.take_screenshot('my_test_result')
```
### 自定义测试运行器
```python
def run_my_tests():
test = MyTest(headless=False, slow_mo=500)
tests = [
('我的测试1', test.test_feature_1),
('我的测试2', test.test_feature_2),
]
results = []
for test_name, test_func in tests:
result = test.run_test(test_name, test_func)
results.append(result)
return results
```
## 故障排除
### 常见问题
1. **浏览器启动失败**
```bash
# 重新安装浏览器
playwright install chromium
```
2. **元素定位失败**
- 检查页面是否完全加载
- 增加等待时间
- 使用更精确的选择器
3. **截图保存失败**
- 确保 `screenshots/` 目录存在
- 检查文件权限
4. **测试超时**
- 增加超时时间
- 检查网络连接
- 确认前端服务正常运行
### 调试技巧
1. **使用非无头模式**
```python
test = BaseBrowserTest(headless=False, slow_mo=1000)
```
2. **增加等待时间**
```python
self.page.wait_for_timeout(2000) # 等待2秒
```
3. **查看页面内容**
```python
print(self.page.content()) # 打印页面HTML
```
4. **检查元素状态**
```python
print(self.page.is_visible('selector')) # 检查元素可见性
```
## 最佳实践
1. **测试独立性**: 每个测试都应该独立运行
2. **数据清理**: 测试后清理测试数据
3. **错误处理**: 适当的异常处理和重试机制
4. **截图记录**: 关键步骤都要截图
5. **断言验证**: 使用明确的断言验证结果
6. **性能考虑**: 合理设置等待时间和超时
## 持续集成
可以将测试集成到CI/CD流程中:
```yaml
# GitHub Actions 示例
- name: Run Browser Tests
run: |
cd tests/browser
pip install -r requirements.txt
playwright install chromium
python test_runner.py
```
## 许可证
本项目遵循 MIT 许可证。
"""Browser automation tests package (Python 3.7.8 compatible)."""
"""
基础测试类 - 提供通用的浏览器自动化测试功能
Python 3.7.8 兼容
"""
import os
import time
from datetime import datetime
from typing import Optional, Dict, Any
from playwright.sync_api import sync_playwright, Page, Browser, BrowserContext
class BaseBrowserTest:
"""浏览器自动化测试基类"""
def __init__(self, headless: bool = False, slow_mo: int = 100):
"""
初始化浏览器测试
Args:
headless: 是否无头模式运行
slow_mo: 操作间隔时间(毫秒)
"""
self.headless = headless
self.slow_mo = slow_mo
self.playwright = None
self.browser: Optional[Browser] = None
self.context: Optional[BrowserContext] = None
self.page: Optional[Page] = None
# 测试配置
self.base_url = os.environ.get('FRONTEND_URL', 'http://localhost:3001')
self.screenshots_dir = os.path.join(os.path.dirname(__file__), 'screenshots')
self.reports_dir = os.path.join(os.path.dirname(__file__), 'reports')
# 创建必要的目录
os.makedirs(self.screenshots_dir, exist_ok=True)
os.makedirs(self.reports_dir, exist_ok=True)
def setup_browser(self) -> None:
"""启动浏览器"""
self.playwright = sync_playwright().start()
# 启动浏览器(使用Chromium)
self.browser = self.playwright.chromium.launch(
headless=self.headless,
slow_mo=self.slow_mo,
args=['--no-sandbox', '--disable-dev-shm-usage']
)
# 创建浏览器上下文
self.context = self.browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
)
# 创建新页面
self.page = self.context.new_page()
# 设置默认超时时间
self.page.set_default_timeout(30000) # 30秒
def teardown_browser(self) -> None:
"""关闭浏览器"""
if self.page:
self.page.close()
if self.context:
self.context.close()
if self.browser:
self.browser.close()
if self.playwright:
self.playwright.stop()
def take_screenshot(self, name: str = None) -> str:
"""
截图
Args:
name: 截图文件名(不包含扩展名)
Returns:
截图文件路径
"""
if not name:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
name = f'screenshot_{timestamp}'
screenshot_path = os.path.join(self.screenshots_dir, f'{name}.png')
self.page.screenshot(path=screenshot_path, full_page=True)
print(f"截图已保存: {screenshot_path}")
return screenshot_path
def wait_for_element(self, selector: str, timeout: int = 10000) -> None:
"""
等待元素出现
Args:
selector: CSS选择器
timeout: 超时时间(毫秒)
"""
self.page.wait_for_selector(selector, timeout=timeout)
def wait_for_text(self, text: str, timeout: int = 10000) -> None:
"""
等待文本出现
Args:
text: 要等待的文本
timeout: 超时时间(毫秒)
"""
self.page.wait_for_selector(f"text={text}", timeout=timeout)
def fill_input(self, selector: str, value: str) -> None:
"""
填充输入框
Args:
selector: CSS选择器
value: 要输入的值
"""
self.page.fill(selector, value)
def click_element(self, selector: str) -> None:
"""
点击元素
Args:
selector: CSS选择器
"""
self.page.click(selector)
def select_option(self, selector: str, value: str) -> None:
"""
选择下拉框选项
Args:
selector: CSS选择器
value: 选项值
"""
self.page.select_option(selector, value)
def get_text(self, selector: str) -> str:
"""
获取元素文本
Args:
selector: CSS选择器
Returns:
元素文本内容
"""
return self.page.text_content(selector)
def get_element_count(self, selector: str) -> int:
"""
获取元素数量
Args:
selector: CSS选择器
Returns:
元素数量
"""
return self.page.locator(selector).count()
def is_element_visible(self, selector: str) -> bool:
"""
检查元素是否可见
Args:
selector: CSS选择器
Returns:
元素是否可见
"""
return self.page.is_visible(selector)
def navigate_to(self, url: str) -> None:
"""
导航到指定URL
Args:
url: 目标URL
"""
self.page.goto(url)
self.page.wait_for_load_state('networkidle')
def login(self, username: str = 'admin', password: str = 'password') -> bool:
"""
登录系统
Args:
username: 用户名
password: 密码
Returns:
登录是否成功
"""
try:
# 导航到登录页面
self.navigate_to(self.base_url)
# 等待登录表单加载
self.wait_for_element('input[placeholder="请输入用户名"]')
# 填写用户名
self.fill_input('input[placeholder="请输入用户名"]', username)
# 填写密码
self.fill_input('input[placeholder="请输入密码"]', password)
# 点击登录按钮
self.click_element('button:has-text("登录")')
# 等待登录完成(检查是否跳转到首页或出现错误)
try:
# 等待页面跳转或出现成功/失败提示
self.page.wait_for_load_state('networkidle', timeout=10000)
# 检查是否成功登录(通过URL或页面元素判断)
current_url = self.page.url
# 等待页面完全加载
self.page.wait_for_timeout(2000)
# 检查是否有错误提示
error_indicators = [
'text=用户名或密码错误',
'text=登录失败',
'text=Invalid',
'text=Error',
'.error',
'.alert-danger'
]
has_error = False
for indicator in error_indicators:
if self.is_element_visible(indicator):
has_error = True
print(f"发现错误提示: {indicator}")
break
# 检查是否跳转到其他页面(表示登录成功)
if has_error:
print(f"登录失败,发现错误提示,当前URL: {current_url}")
return False
elif 'dashboard' in current_url or 'main' in current_url:
print(f"登录成功,当前URL: {current_url}")
return True
elif current_url == self.base_url or current_url == f'{self.base_url}/':
print(f"登录失败,仍在登录页面,当前URL: {current_url}")
return False
else:
print(f"登录状态不明确,当前URL: {current_url}")
return False
except Exception as e:
print(f"登录等待超时: {e}")
return False
except Exception as e:
print(f"登录过程出错: {e}")
self.take_screenshot('login_error')
return False
def run_test(self, test_name: str, test_func) -> Dict[str, Any]:
"""
运行测试并生成报告
Args:
test_name: 测试名称
test_func: 测试函数
Returns:
测试结果字典
"""
start_time = datetime.now()
result = {
'test_name': test_name,
'start_time': start_time.isoformat(),
'success': False,
'error': None,
'screenshots': [],
'duration': 0
}
try:
print(f"\n{'='*50}")
print(f"开始测试: {test_name}")
print(f"{'='*50}")
# 设置浏览器
self.setup_browser()
# 执行测试
test_func()
result['success'] = True
print(f"✅ 测试通过: {test_name}")
except Exception as e:
result['error'] = str(e)
print(f"❌ 测试失败: {test_name}")
print(f"错误信息: {e}")
# 失败时截图
screenshot_path = self.take_screenshot(f'{test_name}_error')
result['screenshots'].append(screenshot_path)
finally:
# 清理资源
self.teardown_browser()
# 计算耗时
end_time = datetime.now()
result['end_time'] = end_time.isoformat()
result['duration'] = (end_time - start_time).total_seconds()
print(f"测试耗时: {result['duration']:.2f}秒")
return result
# 浏览器自动化测试依赖
# Python 3.7.8 兼容
# Playwright 浏览器自动化框架
playwright==1.35.0
# 其他依赖
requests>=2.25.0
"""
登录功能自动化测试
Python 3.7.8 兼容
"""
from base_test import BaseBrowserTest
class LoginTest(BaseBrowserTest):
"""登录测试类"""
def test_successful_login(self):
"""测试成功登录"""
# 测试正常登录
success = self.login('admin', 'password')
assert success, "登录应该成功"
# 截图验证登录状态
self.take_screenshot('login_success')
# 验证登录后的页面元素
# 这里可以根据实际登录后的页面结构添加验证
print("登录成功测试通过")
def test_invalid_username(self):
"""测试无效用户名"""
# 测试错误用户名
success = self.login('invalid_user', 'password')
assert not success, "使用无效用户名登录应该失败"
# 截图验证错误状态
self.take_screenshot('login_invalid_username')
print("无效用户名测试通过")
def test_invalid_password(self):
"""测试无效密码"""
# 测试错误密码
success = self.login('admin', 'wrong_password')
assert not success, "使用无效密码登录应该失败"
# 截图验证错误状态
self.take_screenshot('login_invalid_password')
print("无效密码测试通过")
def test_empty_credentials(self):
"""测试空凭据"""
# 导航到登录页面
self.navigate_to(self.base_url)
# 等待登录表单
self.wait_for_element('input[placeholder="请输入用户名"]')
# 不填写任何信息,直接点击登录
self.click_element('button:has-text("登录")')
# 截图验证错误提示
self.take_screenshot('login_empty_credentials')
print("空凭据测试通过")
def test_remember_me(self):
"""测试记住我功能"""
# 导航到登录页面
self.navigate_to(self.base_url)
# 等待登录表单
self.wait_for_element('input[placeholder="请输入用户名"]')
# 填写用户名和密码
self.fill_input('input[placeholder="请输入用户名"]', 'admin')
self.fill_input('input[placeholder="请输入密码"]', 'password')
# 勾选记住我
self.click_element('input[type="checkbox"]')
# 点击登录
self.click_element('button:has-text("登录")')
# 截图验证
self.take_screenshot('login_remember_me')
print("记住我功能测试通过")
def run_login_tests():
"""运行所有登录测试"""
test = LoginTest(headless=False, slow_mo=500) # 非无头模式,慢速执行便于观察
tests = [
('成功登录测试', test.test_successful_login),
('无效用户名测试', test.test_invalid_username),
('无效密码测试', test.test_invalid_password),
('空凭据测试', test.test_empty_credentials),
('记住我功能测试', test.test_remember_me),
]
results = []
for test_name, test_func in tests:
result = test.run_test(test_name, test_func)
results.append(result)
# 打印测试总结
print(f"\n{'='*60}")
print("登录测试总结")
print(f"{'='*60}")
passed = sum(1 for r in results if r['success'])
total = len(results)
for result in results:
status = "✅ 通过" if result['success'] else "❌ 失败"
print(f"{result['test_name']}: {status} ({result['duration']:.2f}s)")
if result['error']:
print(f" 错误: {result['error']}")
print(f"\n总计: {passed}/{total} 个测试通过")
return results
if __name__ == '__main__':
run_login_tests()
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.