playwright学习方案
2026/6/25 12:38:08 网站建设 项目流程

# 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 响应时间)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询