357 lines
7.6 KiB
Markdown
357 lines
7.6 KiB
Markdown
# UI 集成测试指南
|
|
|
|
## 📋 概述
|
|
|
|
UI 集成测试使用 **Playwright** 框架来测试正则路径替换功能的用户界面交互。
|
|
|
|
## 🚀 快速开始
|
|
|
|
### 1. 安装依赖
|
|
|
|
```bash
|
|
# 安装 Playwright 和浏览器驱动
|
|
pip install pytest-playwright
|
|
playwright install chromium
|
|
|
|
# 或安装所有浏览器
|
|
playwright install
|
|
```
|
|
|
|
### 2. 启动应用服务器
|
|
|
|
在运行 UI 测试前,需要先启动应用:
|
|
|
|
```bash
|
|
# 方式 1: 直接运行
|
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|
|
|
# 方式 2: 使用 Docker
|
|
docker compose up
|
|
```
|
|
|
|
### 3. 运行 UI 测试
|
|
|
|
```bash
|
|
# 无头模式(不显示浏览器)
|
|
pytest tests/test_ui_regex_rules.py -v
|
|
|
|
# 有头模式(显示浏览器,便于调试)
|
|
pytest tests/test_ui_regex_rules.py -v --headed
|
|
|
|
# 慢速模式(方便观察)
|
|
pytest tests/test_ui_regex_rules.py -v --headed --slowmo=500
|
|
|
|
# 运行特定测试
|
|
pytest tests/test_ui_regex_rules.py::TestRegexRulesUI::test_add_single_rule -v --headed
|
|
```
|
|
|
|
## 📊 测试覆盖
|
|
|
|
### 基础 UI 交互测试 (TestRegexRulesUI)
|
|
|
|
| 测试 | 描述 |
|
|
|------|------|
|
|
| `test_page_loads_successfully` | 页面成功加载 |
|
|
| `test_add_single_rule` | 添加单个规则 |
|
|
| `test_add_multiple_rules` | 添加多个规则 |
|
|
| `test_remove_rule` | 删除规则 |
|
|
| `test_save_rules` | 保存规则 |
|
|
| `test_rules_persist_after_save` | 规则持久化验证 |
|
|
| `test_empty_pattern_validation` | 空模式验证 |
|
|
| `test_rule_order_preserved` | 规则顺序保持 |
|
|
|
|
### 复杂场景测试 (TestComplexScenarios)
|
|
|
|
| 测试 | 描述 |
|
|
|------|------|
|
|
| `test_windows_to_linux_path_conversion` | Windows → Linux 路径转换 |
|
|
| `test_nas_path_normalization` | NAS 路径规范化 |
|
|
|
|
### 性能测试 (TestPerformance)
|
|
|
|
| 测试 | 描述 |
|
|
|------|------|
|
|
| `test_add_many_rules_performance` | 添加大量规则性能 |
|
|
|
|
## 🎯 测试场景示例
|
|
|
|
### 场景 1: 添加单个规则
|
|
|
|
```python
|
|
# 1. 点击"添加规则"按钮
|
|
# 2. 填写正则表达式: /old/path/
|
|
# 3. 填写替换文本: /new/path/
|
|
# 4. 验证输入框内容正确
|
|
```
|
|
|
|
### 场景 2: NAS 路径规范化
|
|
|
|
```python
|
|
# 添加三条规则:
|
|
# 1. \\koha9-nas\koha9-nas\Music → N:\Music
|
|
# 2. /music/cache/ → /data/music/
|
|
# 3. \ → /
|
|
#
|
|
# 保存并验证规则持久化
|
|
```
|
|
|
|
### 场景 3: 规则持久化验证
|
|
|
|
```python
|
|
# 1. 添加规则
|
|
# 2. 保存
|
|
# 3. 刷新页面
|
|
# 4. 验证规则仍然存在
|
|
```
|
|
|
|
## 🔧 配置选项
|
|
|
|
### pytest.ini 配置
|
|
|
|
```ini
|
|
[pytest]
|
|
# Playwright 配置
|
|
addopts =
|
|
--browser=chromium
|
|
--headed
|
|
--slowmo=100
|
|
```
|
|
|
|
### 环境变量
|
|
|
|
```bash
|
|
# 设置测试服务器地址
|
|
export TEST_SERVER_URL="http://localhost:8000"
|
|
|
|
# 设置浏览器类型
|
|
export BROWSER=chromium # 或 firefox, webkit
|
|
```
|
|
|
|
## 🐛 调试技巧
|
|
|
|
### 1. 使用有头模式
|
|
|
|
```bash
|
|
pytest tests/test_ui_regex_rules.py --headed
|
|
```
|
|
|
|
### 2. 使用慢速模式
|
|
|
|
```bash
|
|
pytest tests/test_ui_regex_rules.py --headed --slowmo=1000
|
|
```
|
|
|
|
### 3. 截图调试
|
|
|
|
在测试中添加截图:
|
|
|
|
```python
|
|
def test_something(page: Page):
|
|
page.screenshot(path="debug_screenshot.png")
|
|
```
|
|
|
|
### 4. 使用 Playwright Inspector
|
|
|
|
```bash
|
|
# 启动调试模式
|
|
PWDEBUG=1 pytest tests/test_ui_regex_rules.py::test_add_single_rule
|
|
```
|
|
|
|
### 5. 查看追踪
|
|
|
|
```python
|
|
# 在 conftest.py 中添加
|
|
@pytest.fixture
|
|
def context(browser):
|
|
context = browser.new_context()
|
|
context.tracing.start(screenshots=True, snapshots=True)
|
|
yield context
|
|
context.tracing.stop(path="trace.zip")
|
|
```
|
|
|
|
然后查看:
|
|
```bash
|
|
playwright show-trace trace.zip
|
|
```
|
|
|
|
## 📝 编写新的 UI 测试
|
|
|
|
### 基本模板
|
|
|
|
```python
|
|
def test_my_feature(page: Page):
|
|
"""测试我的功能"""
|
|
# 1. 导航到页面
|
|
page.goto("http://localhost:8000")
|
|
|
|
# 2. 与元素交互
|
|
button = page.locator("#myButton")
|
|
button.click()
|
|
|
|
# 3. 验证结果
|
|
expect(page.locator("#result")).to_have_text("Success")
|
|
```
|
|
|
|
### 等待策略
|
|
|
|
```python
|
|
# 等待元素可见
|
|
page.wait_for_selector("#element", state="visible")
|
|
|
|
# 等待网络空闲
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
# 等待特定时间(尽量避免)
|
|
page.wait_for_timeout(1000) # 1 秒
|
|
```
|
|
|
|
### 选择器策略
|
|
|
|
```python
|
|
# 推荐: 使用 data-testid
|
|
page.locator("[data-testid='add-rule-btn']")
|
|
|
|
# 通过文本
|
|
page.locator("button:has-text('保存规则')")
|
|
|
|
# 通过 ID
|
|
page.locator("#addRuleBtn")
|
|
|
|
# 通过 CSS 类
|
|
page.locator(".rule-row")
|
|
|
|
# 组合选择器
|
|
page.locator(".rule-row input[name='pattern']")
|
|
```
|
|
|
|
## 🎨 最佳实践
|
|
|
|
### 1. 使用 Page Object 模式
|
|
|
|
```python
|
|
class RulesPage:
|
|
def __init__(self, page: Page):
|
|
self.page = page
|
|
self.add_button = page.locator("#addRuleBtn")
|
|
self.save_button = page.locator("button:has-text('保存规则')")
|
|
|
|
def add_rule(self, pattern: str, replacement: str):
|
|
self.add_button.click()
|
|
self.page.wait_for_timeout(100)
|
|
|
|
patterns = self.page.locator("input[name='pattern']")
|
|
replacements = self.page.locator("input[name='replacement']")
|
|
|
|
patterns.last.fill(pattern)
|
|
replacements.last.fill(replacement)
|
|
|
|
def save(self):
|
|
self.save_button.click()
|
|
self.page.wait_for_load_state("networkidle")
|
|
|
|
# 使用
|
|
def test_with_page_object(page: Page):
|
|
rules_page = RulesPage(page)
|
|
rules_page.add_rule(r"/old/", r"/new/")
|
|
rules_page.save()
|
|
```
|
|
|
|
### 2. 使用 Fixtures 清理状态
|
|
|
|
```python
|
|
@pytest.fixture
|
|
def clean_rules(page: Page):
|
|
"""清除所有规则"""
|
|
page.goto("http://localhost:8000")
|
|
while page.locator(".rule-row").count() > 0:
|
|
page.locator(".rule-row button[title='删除此规则']").first.click()
|
|
page.wait_for_timeout(50)
|
|
yield
|
|
```
|
|
|
|
### 3. 避免硬编码等待时间
|
|
|
|
```python
|
|
# ❌ 不好
|
|
page.wait_for_timeout(2000)
|
|
|
|
# ✅ 好
|
|
page.wait_for_selector("#element", state="visible")
|
|
page.wait_for_load_state("networkidle")
|
|
```
|
|
|
|
### 4. 使用断言而非 if 判断
|
|
|
|
```python
|
|
# ❌ 不好
|
|
assert page.locator("#element").count() > 0
|
|
|
|
# ✅ 好
|
|
expect(page.locator("#element")).to_be_visible()
|
|
```
|
|
|
|
## 🔄 CI/CD 集成
|
|
|
|
### GitHub Actions 示例
|
|
|
|
```yaml
|
|
name: UI Tests
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
ui-tests:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v2
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v2
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
pip install -r requirements.txt
|
|
pip install pytest-playwright
|
|
playwright install --with-deps chromium
|
|
|
|
- name: Start application
|
|
run: |
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000 &
|
|
sleep 5
|
|
|
|
- name: Run UI tests
|
|
run: pytest tests/test_ui_regex_rules.py -v
|
|
|
|
- name: Upload screenshots on failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@v2
|
|
with:
|
|
name: screenshots
|
|
path: screenshots/
|
|
```
|
|
|
|
## 📚 参考资料
|
|
|
|
- [Playwright 官方文档](https://playwright.dev/python/)
|
|
- [pytest-playwright 插件](https://github.com/microsoft/playwright-pytest)
|
|
- [Playwright 最佳实践](https://playwright.dev/python/docs/best-practices)
|
|
|
|
## 🆘 常见问题
|
|
|
|
### Q: 测试运行时找不到浏览器?
|
|
A: 运行 `playwright install chromium`
|
|
|
|
### Q: 测试失败,如何调试?
|
|
A: 使用 `--headed --slowmo=500` 参数可视化执行过程
|
|
|
|
### Q: 如何在测试中等待异步操作?
|
|
A: 使用 `page.wait_for_load_state("networkidle")` 或 `page.wait_for_selector()`
|
|
|
|
### Q: 如何处理动态加载的内容?
|
|
A: 使用 `expect().to_be_visible()` 会自动等待元素出现
|
|
|
|
### Q: 测试很慢怎么办?
|
|
A: 减少不必要的 `wait_for_timeout()`,使用事件驱动的等待方法
|