# Playwright 前端自动化测试框架 - 详细设计方案
## 一、目标与核心流程
一个**通用**的前端自动化测试框架,核心链路为:
```
Markdown 写用例(自然语言描述操作步骤)
→ 框架解析并自动执行
→ 产出:HTML 测试报告(含截图) + 可复跑的回归 Python 脚本
```
### 需求对照
| # | 需求 | 方案 |
|---|------|------|
| 1 | 自然语言操作浏览器 | Markdown 中用自然语言描述步骤,框架自动解析执行 |
| 2 | 执行完形成回归脚本 | 自动生成独立 `.py` 脚本,包含精确选择器,直接可跑 |
| 3 | 通用,换任何 Web 系统都能用 | 系统相关配置抽到 `profile.yaml`,框架不耦合任何业务 |
| 4 | 全自动,只提供用例 | 一行命令 `python main.py xxx.md`,headless 全自动 |
| 5 | 报告包含截图 | 每步自动截图,HTML 报告可点击查看 |
| 6 | 报错记录后继续下一条 | 用例级 `try/catch`,失败截图 + 记录,自动跳到下一条 |
---
## 二、项目目录结构
```
项目根目录/
├── framework/ # 框架核心(与业务无关)
│ ├── __init__.py # 包初始化,导出各模块
│ ├── engine.py # 执行引擎:调度用例、隔离错误、继续下一条
│ ├── parser.py # Markdown 解析器:把 .md 用例解析成结构化数据
│ ├── interpreter.py # 指令解释器:自然语言 → Playwright 操作
│ ├── locator.py # 智能定位:8 级选择器降级策略
│ ├── codegen.py # 代码生成:自动生成回归 Python 脚本
│ └── reporter.py # 报告生成:HTML 报告 + 截图汇总
│
├── profiles/ # 系统配置(不同 Web 系统配不同的 yaml)
│ └── default.yaml # 通用默认配置
│
├── test_cases/ # 测试用例(Markdown 文件)
│ └── (你写的 .md 文件放这里)
│
├── outputs/ # 执行产物(每次运行自动创建时间戳子目录)
│ ├── reports/ # HTML 报告
│ ├── screenshots/ # 截图
│ ├── videos/ # 操作视频
│ └── scripts/ # 生成的回归脚本
│
├── main.py # 入口:python main.py test_cases/你的用例.md
└── config.yaml # 全局配置
```
---
## 三、Markdown 用例格式
用户只需按以下格式写 Markdown:
```markdown
# 测试计划: XXX系统
## 系统信息
| 配置项 | 值 |
|--------|-----|
| 目标地址 | https://your-system.com |
| 登录地址 | https://your-system.com/login |
| 账号 | admin |
| 密码 | 123456 |
| 浏览器 | chromium |
| 分辨率 | 1920x1080 |
---
## 用例1: 登录功能正常流程
**优先级**: P0
**标签**: 登录, 冒烟测试
1. 打开登录页面
2. 输入账号 `admin` 和密码 `123456`
3. 点击登录按钮
4. 验证页面包含"欢迎"或"首页"
5. 截图
---
## 用例2: 菜单导航检查
**优先级**: P1
1. 点击菜单"用户管理"
2. 验证页面显示用户列表
3. 截图
4. 点击菜单"系统设置"
5. 验证页面显示设置选项
6. 截图
---
## 用例3: 异常场景测试
**优先级**: P2
1. 打开登录页面
2. 输入账号 `admin` 和密码 `错误密码`
3. 点击登录按钮
4. 验证页面出现错误提示
5. 截图
```
### 格式规范
- 以 `## 用例` 开头的行为用例标题
- `**优先级**` 和 `**标签**` 为可选元数据
- 以 `1. ` `2. ` 编号开头的行为操作步骤
- 提取反引号内的参数值(如 `` `admin` ``)
- `验证页面包含"欢迎"或"首页"` → 多个候选文本,匹配任一即可
---
## 四、各模块详细设计
### 4.1 parser.py —— Markdown 解析器
**输入**:Markdown 文件路径
**输出**:`TestPlan` 对象,包含 `system_info` 字典和 `test_cases` 列表
#### 解析流程
```
Markdown 文件
│
├─→ 解析 ## 系统信息 表格 → dict {target_url, login_url, username, ...}
│
├─→ 按 ## 用例 分割块
│ └─→ 每个块解析为 TestCase 对象
│ ├─→ 提取优先级、标签
│ └─→ 提取步骤文本 → 正则匹配 18 种模式
│
└─→ 输出 TestPlan.to_dict()
```
#### 18 种自然语言模式映射表
| 自然语言模式 | 解析为 action | 提取参数 |
|-------------|-------------|---------|
| `打开登录页面` | `navigate_login` | — |
| `打开 XXX 页面` | `navigate_page` | page=X |
| `跳转到 XXX` | `navigate_page` | page=X |
| `访问 XXX` | `navigate_url` | url=X |
| `输入账号 \`X\` 和密码 \`Y\`` | `fill_credentials` | username, password |
| `输入 X 到「Y」` | `fill_by_label` | value, label |
| `在「X」中输入 Y` | `fill_by_label` | label, value |
| `点击 X 按钮` | `click_button` | target=X |
| `点击菜单「X」` | `click_menu` | target=X |
| `点击链接「X」` | `click_link` | target=X |
| `点击 X` | `click` | target=X |
| `验证页面包含「X」或「Y」` | `assert_text` | texts=[X,Y] |
| `验证 URL 包含 X` | `assert_url` | url_fragment=X |
| `验证 X 可见` | `assert_visible` | target=X |
| `验证 X 不存在` | `assert_not_visible` | target=X |
| `等待 N 秒` | `wait` | seconds=N |
| `截图` / `截屏` | `screenshot` | — |
| `选择「X」中的「Y」` | `select_option` | target, option |
| `勾选 X` | `check` | target=X |
| `取消勾选 X` | `uncheck` | target=X |
| `滚动到 X` | `scroll_to` | target=X |
| `悬浮到 X` | `hover` | target=X |
**设计决策:为什么用正则而不是 NLP/LLM?**
测试用例的步骤描述本身就是结构化意图,不是随意对话。"点击登录按钮"在不同人写的用例里高度一致,正则足够。LLM 有不确定性——同一个句子两次解析可能不同——测试需要可复现。18 种模式覆盖 90% 常见操作,未匹配的标记为 `unknown` 但不中断。
---
### 4.2 interpreter.py —— 指令解释器
**输入**:单条解析后的 `step` 字典(`{action, params...}`)
**输出**:调用 Playwright API 执行操作,成功返回 True,失败抛异常
#### 动态分发机制
```python
class InstructionInterpreter:
def execute(self, step):
action = step.get("action")
handler = getattr(self, f"_handle_{action}", None)
handler(step) # 动态调用 _handle_click / _handle_fill / ...
```
每个操作类型对应一个 `_handle_xxx` 方法,新增操作只需加方法,不改调用逻辑。
#### 自然语言 → Playwright API 映射
| 操作类型 | Playwright API | 说明 |
|---------|---------------|------|
| `navigate_login` | `page.goto(login_url)` | 从系统信息取 login_url |
| `navigate_page` | `page.goto(url)` | 拼接 target_url + page_name |
| `navigate_url` | `page.goto(url)` | 支持绝对/相对路径 |
| `fill_credentials` | `locator.find_input_field(type)` | 自动检测用户名/密码框 |
| `fill_by_label` | `locator.find_by_label(label)` | 按 label 文本定位 |
| `click` | `locator.smart_find(target).click()` | 8 级选择器策略 |
| `click_button` | `smart_find(target, prefer_role='button')` | 优先按钮角色 |
| `click_menu` | `smart_find(target, prefer_role='navigation')` | 优先导航区域 |
| `click_link` | `smart_find(target, prefer_role='link')` | 优先链接角色 |
| `assert_text` | `wait_for_selector(text=X)` | 多候选文本,匹配任一 |
| `assert_url` | `assert fragment in page.url` | URL 片段断言 |
| `assert_visible` | `smart_find(target).is_visible()` | 可见性断言 |
| `wait` | `page.wait_for_timeout(N*1000)` | 显式等待 |
| `screenshot` | 由 engine 层处理 | 引擎统一截图管理 |
#### 登录模块
`login()` 方法自动检测登录表单,遍历 15+ 种选择器:
- **用户名框**:`input[name="username"]` → `input[name="account"]` → `input[name="email"]` → `input[type="text"]` → `placeholder*="用户名"` → ...
- **密码框**:`input[name="password"]` → `input[name="passwd"]` → `input[type="password"]` → `placeholder*="密码"` → ...
- **提交按钮**:`button[type="submit"]` → `button:has-text("登录")` → `button:has-text("Sign in")` → `.login-btn` → ...
---
### 4.3 locator.py —— 8 级智能定位策略
#### 选择器优先级(从快到慢、从精确到模糊)
```
Level 1: getByRole('button', {name: 'xxx'}) ← 最优先,语义化角色
Level 2: getByLabel('xxx') ← form 标签关联
Level 3: getByPlaceholder('xxx') ← placeholder 匹配
Level 4: getByText('xxx') ← 精确文本 → 模糊文本
Level 5: page.locator('#xxx') ← ID 选择器
Level 6: page.locator('[data-testid="xxx"]') ← 测试专用属性
Level 7: page.locator('css: ...') ← CSS 选择器
Level 8: page.locator('xpath: //*[...]') ← XPath 兜底
```
#### 设计哲学:稳定性决定优先级
| 层级 | 稳定性 | 原因 |
|------|--------|------|
| L1-L4 | **高** | 不依赖 DOM 结构。CSS 类名改了、布局重排、div 变 span——这些 API 不受影响 |
| L5-L6 | **中** | 依赖开发者主动维护。id 可能冲突,data-testid 不一定有 |
| L7-L8 | **低** | CSS/XPath 最脆弱,前端改下 DOM 嵌套就可能失效,仅做最后兜底 |
#### 降级逻辑
```python
def smart_find(target, prefer_role=None):
strategies = _build_strategies(target) # 从 L1 到 L8 的策略列表
for strategy in strategies:
try:
locator = strategy(page)
if locator and locator.is_visible():
return locator
except:
continue # 当前层失败,静默降级到下一层
return None # 8 层全失败 → 步骤标记 FAIL
```
每一层尝试失败时静默吞掉异常,全部失败才返回 `None`。保证大部分场景能找到元素,同时不因某个选择器报错而中断流程。
---
### 4.4 engine.py —— 核心执行引擎
#### 执行流程
```
1. parser.parse_file(md_path) → 解构 TestPlan
2. _setup_output_dirs() → 创建时间戳子目录
3. sync_playwright() → 启动 browser + context + page
4. interpreter.login() → 全局登录(如果配置了账号)
5. for case in cases: → 逐条执行
├─→ try: 执行所有步骤
│ ├─→ 每步成功后截图
│ └─→ 每步失败后截图 + 记录
└─→ except: 用例级崩溃 → 截图 + goto 恢复 + continue
6. reporter.generate() + codegen.generate() → 生成报告和脚本
7. _print_summary() → 打印汇总统计
```
#### 两层错误隔离
```python
for case in cases:
try: # 用例级 try
for step in case.steps:
try: # 步骤级 try
interpreter.execute(step)
step_result.status = "pass"
screenshot() # 成功截图
except Exception:
step_result.status = "fail"
screenshot(prefix="FAIL") # 失败截图
continue # 继续同用例的下一步
except Exception:
case_result.status = "fail"
screenshot(prefix="ERROR")
page.goto(target_url) # 恢复页面状态
continue # 继续下一条用例
```
**关键设计**:
- **步骤失败**:该用例内其他步骤照常执行(一个页面 5 个按钮,第 2 个失败,第 3-5 个还试)
- **用例崩溃**:该用例标记 FAIL,page 恢复到目标 URL,下条用例从干净状态开始
#### StepResult 和 CaseResult
```python
class StepResult:
index: int # 步骤序号
action: dict # 原始操作
status: str # pass | fail
error: str # 失败时的错误信息
screenshot_path: str # 截图路径
class CaseResult:
case: TestCase # 原始用例
status: str # pass | fail
step_results: list[StepResult]
error: str
start_time / end_time
passed_steps / failed_steps / total_steps
```
---
### 4.5 reporter.py —— HTML 报告生成
#### 报告结构
```
┌─────────────────────────────────────┐
│ 测试计划名称 + 生成时间 │
├─────────────────────────────────────┤
│ [总用例数] [通过] [失败] [步骤] [通过率] │ ← 6 个统计卡片
├─────────────────────────────────────┤
│ ████████████████░░ 81.3% │ ← 通过率进度条
├─────────────────────────────────────┤
│ ✓ TC001 登录功能正常流程 P0 5/5 │ ← 可折叠用例卡片
│ ┌─ 步骤表格 ────────────────────┐ │
│ │ #│状态│描述 │操作│截图 │ │
│ │ 1│ ✓ │打开登录页面│goto│[截图] │ │
│ │ 2│ ✓ │输入账号密码│fill│[截图] │ │
│ │ ... │ │
│ └────────────────────────────────┘ │
│ ✗ TC003 异常场景测试 P2 2/5 │
│ ┌─ 步骤表格(失败项红色高亮)────┐ │
│ │ 4│ ✗ │验证错误提示 │assert│[截图]││
│ │ 错误: 未找到预期文本 │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────┘
```
#### 交互功能
- **折叠卡片**:默认收起,点击用例头展开步骤详情
- **截图缩略图**:200px 宽显示,点击放大到原始尺寸,再次点击恢复
- **失败高亮**:失败步骤红色背景,附带错误信息
- **优先级标签**:P0 红色 / P1 橙色 / P2 绿色
---
### 4.6 codegen.py —— 回归脚本生成
#### 设计原则
生成的脚本:
- **独立可运行**:不依赖框架任何模块,`pip install playwright` 就能跑
- **带异常处理**:每个操作用 `try/catch` 包裹,失败打印错误 + 截图
- **可脱离 Markdown**:回归脚本不需要原始用例文件,可以直接发给别人
#### 生成示例
```python
# 自动生成的回归测试脚本
# 生成时间: 2026-06-24 22:00:00
# 测试计划: 通用 Web 系统
from playwright.sync_api import sync_playwright
def run():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1920, "height": 1080})
# ---- [TC001] 登录功能正常流程 ----
try:
page.goto("https://example.com/login", wait_until="networkidle")
page.locator('input[name="username"]').first.fill("admin")
page.locator('input[type="password"]').first.fill("123456")
page.locator('button[type="submit"]').first.click()
page.wait_for_selector("text=欢迎", timeout=5000)
page.screenshot(path="TC001_step5.png", full_page=True)
except Exception as e:
print(f"[FAIL] TC001 登录功能正常流程: {e}")
page.screenshot(path="FAIL_TC001.png", full_page=True)
# ---- [TC002] 菜单导航检查 ----
try:
page.get_by_text("用户管理").first.click(timeout=3000)
page.wait_for_selector("text=用户列表", timeout=5000)
# ...
except Exception as e:
print(f"[FAIL] TC002 菜单导航检查: {e}")
browser.close()
if __name__ == "__main__":
run()
```
---
### 4.7 profiles/default.yaml —— 通用性保障
框架本身不耦合任何业务系统。所有系统差异通过 profile 管理:
```yaml
# 所有配置项默认 "auto",框架优先自动检测,检测不到再用配置值
系统名称: ""
登录方式: auto # auto: 自动检测 | none: 无需登录 | form: 表单登录
登录页识别: auto # 自动通过URL模式识别(/login, /signin 等)
用户名框定位: auto # auto: 自动检测 | "#username": 手动指定
密码框定位: auto
登录按钮定位: auto
登录后确认: auto # url_change: URL变化算成功 | text: 出现特定文本
菜单位置: auto # sidebar: 左侧栏 | top: 顶部栏 | auto: 自动检测
框架类型: auto # element-ui | ant-design | bootstrap | auto
页面加载策略: networkidle
截图方式: fullpage
```
#### 自动检测逻辑
- **登录页识别**:检查 URL 是否包含 `/login`、`/signin`、`/sign_in`、`/console` 等模式
- **表单检测**:遍历 15+ 种选择器组合尝试用户名框和密码框
- **菜单位置**:检测 sidebar 和 top nav 区域,按布局特征判断
换一个 Web 系统,理论上只需要改 Markdown 用例里的系统信息表格。
---
### 4.8 config.yaml —— 全局配置
```yaml
browser: chromium # chromium | firefox | webkit
viewport:
width: 1920
height: 1080
mode: headless # headless | headed
timeout:
default: 30000 # 默认超时(毫秒)
navigation: 60000 # 导航超时
action: 10000 # 操作超时
screenshot:
format: png
full_page: true
report:
title: "自动化测试报告"
output_dir: outputs # 输出根目录
concurrency: 1 # 并发数
```
---
## 五、使用方式
### 环境准备
```bash
pip install playwright pyyaml
python -m playwright install chromium
```
### 运行测试
```bash
# 基本用法
python main.py test_cases/demo_test.md
# 指定输出目录
python main.py test_cases/demo_test.md --output my_outputs
# 指定配置文件
python main.py test_cases/demo_test.md --config my_config.yaml
```
### 执行效果
```
==================================================
Playwright Auto Test Framework
==================================================
测试计划: 通用 Web 系统
用例总数: 3 | 浏览器: Chromium (headless)
==================================================
[PASS] TC001 登录功能正常流程 (5/5 通过)
[PASS] TC002 菜单导航检查 (6/6 通过)
[FAIL] TC003 异常场景测试 (2/5 通过) → 已截图记录,继续执行
==================================================
总步骤: 16 | 通过: 13 | 失败: 3 | 通过率: 81.3%
--------------------------------------------------
报告: outputs/20260624_220000/reports/report.html
脚本: outputs/20260624_220000/scripts/regression_20260624.py
截图: outputs/20260624_220000/screenshots/
==================================================
```
---
## 六、文件清单
| 文件 | 行数 | 说明 |
|------|------|------|
| `framework/__init__.py` | ~20 | 包初始化,导出所有模块 |
| `framework/parser.py` | ~250 | Markdown 解析器,18 种自然语言模式 |
| `framework/interpreter.py` | ~280 | 指令解释器,NL → Playwright API |
| `framework/locator.py` | ~180 | 8 级选择器降级策略 |
| `framework/engine.py` | ~310 | 核心执行引擎,两层错误隔离 |
| `framework/reporter.py` | ~220 | HTML 报告生成,含交互式 UI |
| `framework/codegen.py` | ~125 | 回归脚本代码生成 |
| `profiles/default.yaml` | ~15 | 通用默认配置 |
| `main.py` | ~70 | 命令行入口 |
| `config.yaml` | ~25 | 全局配置 |
| `test_cases/demo_test.md` | ~40 | 通用示例用例 |
**总计:约 1500 行 Python 代码**
---
## 七、扩展方向
### 短期可扩展
- 支持更多自然语言模式(如"拖拽 A 到 B"、"双击"、"右键菜单")
- 支持数据驱动(同一个用例用多组数据执行)
- 支持用例间变量传递
### 中期可扩展
- 并发执行多个用例
- 集成 CI/CD(Jenkins/GitHub Actions 插件)
- 对比截图 + 视觉回归检测
- 邮件/钉钉/企微通知
### 长期可扩展
- LLM 辅助定位(当 8 级降级全失败时,用 LLM 理解页面结构并生成选择器)
- 录制回放(浏览器操作录制 → 自动生成 Markdown 用例)
- 性能指标采集(页面加载时间、API 响应时间)