Commit 29a9f2d0 authored by liguangyu06's avatar liguangyu06
Browse files

Add SPD Playwright UI automation framework for GitLab CI and Jenkins.

Keep credentials, browser cache, and Cursor skills out of the repository so clones can run from .env.example and npm ci.
parents
{
"mcpServers": {
"playwright-test": {
"command": "npx",
"args": [
"playwright",
"run-test-mcp-server"
]
}
}
}
---
alwaysApply: false
rule_name: API接口自动化测试代码生成规范
version: V2.1
pair_rules:
- API-testcase-csv-rules.mdc
scene: CSV 数据驱动接口自动化、按规范生成/维护可运行项目;CSV 表头与用例编写约束以 pair_rules 为准,本文件侧重目录、代码与运行时行为。
---
# API 接口自动化测试代码生成规则(V2.1)
## 概述
本规范用于根据**标准 CSV 测试用例文件**,自动生成可运行的 **Python + pytest + requests + allure** 接口自动化测试项目。
**与 CSV 规则分工:** 用例文件如何编写(表头、枚举、条数、纯 CSV 输出等)见 **API-testcase-csv-rules.mdc**;本文件规定**如何解析 CSV、项目结构、请求封装、断言与模板代码**。
**核心能力:**
1. 以 CSV 管理接口用例,用例与代码解耦
2. 生成分层架构、配置、核心封装与工具类
3. 按 `module` 拆分测试文件,pytest 数据驱动执行
4. 统一请求封装、多环境、日志、异常重试
5. 分层断言(非全量响应体硬匹配)
6. Allure 报告:用例 ID、优先级、严重级别、标签、模块、步骤
7. 预留前置/后置、JSON 容错、动态变量等扩展
---
## 一、标准 CSV 用例规范(与用例生成规则对齐)
### 1.1 固定列(顺序不可变)
与 **API-testcase-csv-rules.mdc** 中表头**完全一致**,不可增删或调序:
```text
test_case_id,test_case_name,module,api_name,method,url,content_type,headers,request_data,preconditions,postconditions,expected_status_code,expected_key_assert,expected_value_assert,test_type,priority,tags,description
```
### 1.2 字段说明(代码侧解析要点)
| 列名 | 说明 |
|------|------|
| test_case_id | 用例唯一 ID,供 Allure `dynamic.id` |
| test_case_name | 用例名称 |
| module | 业务模块,**与** `test_{module}.py` 及 `get_case_by_module` 过滤**一致** |
| api_name | 接口名称 / Allure story |
| method | GET / POST / PUT / DELETE(大写) |
| url | 相对 `base_url` 的路径 |
| content_type | `json` / `form` / `form-data` |
| headers | JSON 字符串;无则 `""` 或 `"{}"`(`json.loads` 前作空判断,见模板) |
| request_data | JSON 字符串;无则 `""` 或 `"{}"` |
| preconditions | 前置说明;无则填 `无`;`exec_pre_condition` 扩展用 |
| postconditions | 后置说明;无则填 `无`;`exec_post_condition` 扩展用 |
| expected_status_code | 期望 HTTP 状态码(整型) |
| expected_key_assert | 响应 JSON **顶层**需存在的字段;**多个**用英文逗号分隔,模板中**逐字段**断言存在 |
| expected_value_assert | 精准值;`key=value`;**多组**用英文逗号分隔,模板中**逐组**断言(值内避免未转义逗号,或自行加强解析) |
| test_type | positive / negative / boundary |
| priority | high / medium / low,映射 Allure severity |
| tags | 英文逗号分隔,用于 `allure.dynamic.tag` |
| description | 用例描述 |
### 1.3 CSV 与运行态约束
1. 除 preconditions/postconditions 的「无」外,空值统一为**空字符串**;禁止 `null`、`NaN`(与用例规则一致)。
2. `headers` / `request_data` 须为合法 JSON 或空;非法时单条用例内**容错**为**空字典**(`json.JSONDecodeError`),不拖垮整模块加载。
3. `module` 稳定命名,作为**按模块拆分文件**与过滤用例的**唯一键**。
4. 禁止**全量响应体**硬匹配;采用**状态码 + 多字段存在性 + 多组 key=value**(与模板实现一致)。
### 1.4 标准 CSV 示例
```csv
test_case_id,test_case_name,module,api_name,method,url,content_type,headers,request_data,preconditions,postconditions,expected_status_code,expected_key_assert,expected_value_assert,test_type,priority,tags,description
API-USER-001,正常登录成功,user,用户登录,POST,/api/user/login,json,"{}","{""username"":""test"",""password"":""123456""}",无,无,200,code,code=200,positive,high,smoke,正向登录场景
API-USER-002,密码错误登录失败,user,用户登录,POST,/api/user/login,json,"{}","{""username"":""test"",""password"":""error""}",无,无,400,msg,msg=密码错误,negative,medium,login,反向异常场景
```
> 说明:JSON 列在 CSV 中双引号转义为 `""`;**新产用例**的完整约束见 **API-testcase-csv-rules.mdc**。
---
## 二、项目固定目录结构
```text
api_autotest/
├── .env
├── conftest.py
├── pytest.ini
├── requirements.txt
├── config/
│ ├── __init__.py
│ ├── config.yaml
│ └── config.py
├── core/
│ ├── __init__.py
│ ├── api_client.py
│ ├── logger.py
│ └── assertions.py
├── data/
│ └── test_cases.csv
├── testcases/
│ ├── __init__.py
│ ├── conftest.py
│ └── test_xxx.py
└── utils/
├── __init__.py
├── data_handler.py
├── var_render.py
└── common.py
```
---
## 三、配置文件规范
### 3.1 `.env`(环境变量,勿提交真实密钥到版本库)
```env
TEST_ENV=test
LOGIN_USER=test_admin
LOGIN_PASS=123456abc
COMMON_TOKEN=
```
### 3.2 `config/config.yaml`
```yaml
env:
test:
base_url: "http://test-api.example.com"
timeout: 30
dev:
base_url: "http://dev-api.example.com"
timeout: 30
prod:
base_url: "https://api.example.com"
timeout: 30
headers:
Content-Type: "application/json"
User-Agent: "AutoTest-V2.1"
logging:
level: INFO
format: "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
file: "logs/api_test.log"
```
### 3.3 `config/config.py`(单例加载 YAML + dotenv)
```python
import os
import yaml
from dotenv import load_dotenv
from typing import Dict, Any
load_dotenv()
class Config:
_instance = None
_config_data: Dict[str, Any] = {}
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._config_data:
self.load_config()
def load_config(self):
config_path = os.path.join(os.path.dirname(__file__), "config.yaml")
with open(config_path, "r", encoding="utf-8") as f:
self._config_data = yaml.safe_load(f)
@property
def env(self) -> str:
return os.getenv("TEST_ENV", "test")
@property
def base_url(self) -> str:
return self._config_data["env"][self.env]["base_url"]
@property
def global_headers(self) -> Dict[str, str]:
return self._config_data["headers"]
@property
def timeout(self) -> int:
return self._config_data["env"][self.env]["timeout"]
```
### 3.4 `pytest.ini`
```ini
[pytest]
addopts = -v -s --alluredir=./allure-results --strict-markers
testpaths = testcases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
smoke: 冒烟测试用例
positive: 正向用例
negative: 反向用例
boundary: 边界用例
high: 高优先级用例
medium: 中优先级用例
low: 低优先级用例
```
### 3.5 `requirements.txt`
```text
pytest>=7.4.0
allure-pytest>=2.13.0
requests>=2.31.0
pyyaml>=6.0.1
pandas>=2.0.0
python-dotenv>=1.0.0
urllib3>=2.0.0
```
---
## 四、核心公共组件
### 4.1 `core/logger.py`
```python
import logging
import os
from config.config import Config
conf = Config()
log_path = conf._config_data["logging"]["file"]
os.makedirs(os.path.dirname(log_path), exist_ok=True)
logging.basicConfig(
level=conf._config_data["logging"]["level"],
format=conf._config_data["logging"]["format"],
handlers=[
logging.FileHandler(log_path, encoding="utf-8"),
logging.StreamHandler()
],
)
logger = logging.getLogger("api_auto_test")
```
### 4.2 `core/api_client.py`
```python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from typing import Dict, Any, Optional
from core.logger import logger
from config.config import Config
class ApiClient:
def __init__(self):
self.config = Config()
self.session = requests.Session()
self.session.headers.update(self.config.global_headers)
retry = Retry(total=2, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])
adapter = HTTPAdapter(max_retries=retry)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def request(
self,
method: str,
url: str,
headers: Optional[Dict[str, str]] = None,
data: Optional[Dict[str, Any]] = None,
content_type: str = "json",
**kwargs,
) -> requests.Response:
full_url = f"{self.config.base_url}{url}"
merge_headers = {**self.session.headers, **(headers or {})}
try:
logger.info(f"【{method.upper()}】{full_url}")
logger.debug(f"请求头:{merge_headers}")
logger.debug(f"请求参数:{data}")
if content_type == "json":
resp = self.session.request(
method=method,
url=full_url,
headers=merge_headers,
json=data,
timeout=self.config.timeout,
**kwargs,
)
else:
resp = self.session.request(
method=method,
url=full_url,
headers=merge_headers,
data=data,
timeout=self.config.timeout,
**kwargs,
)
logger.info(f"响应码:{resp.status_code}")
logger.debug(f"响应内容:{resp.text[:800]}")
return resp
except Exception as e:
logger.error(f"请求异常:{str(e)}")
raise
```
### 4.3 `core/assertions.py`
```python
import allure
from requests import Response
class ApiAssertions:
@staticmethod
@allure.step("校验响应状态码")
def assert_status_code(resp: Response, expect_code: int):
assert resp.status_code == expect_code, (
f"状态码错误,期望:{expect_code},实际:{resp.status_code}"
)
@staticmethod
@allure.step("校验关键字段存在")
def assert_key_exists(resp: Response, expect_key: str):
res = resp.json()
assert expect_key in res, f"缺失字段:{expect_key}"
@staticmethod
@allure.step("校验字段精准值")
def assert_key_equal(resp: Response, key: str, expect_val):
res = resp.json()
assert res.get(key) == expect_val, f"{key} 期望:{expect_val},实际:{res.get(key)}"
@staticmethod
@allure.step("校验响应包含文本")
def assert_resp_contains(resp: Response, text: str):
assert text in resp.text, f"响应未包含:{text}"
```
---
## 五、工具类
### 5.1 `utils/data_handler.py`
```python
import pandas as pd
from typing import List, Dict, Any
class DataHandler:
@staticmethod
def load_all_cases(csv_path: str = "data/test_cases.csv") -> pd.DataFrame:
df = pd.read_csv(csv_path, dtype=str)
df = df.fillna("")
return df
@staticmethod
def get_case_by_module(module_name: str) -> List[Dict[str, Any]]:
df = DataHandler.load_all_cases()
filter_df = df[df["module"] == module_name]
return filter_df.to_dict("records")
```
### 5.2 `utils/var_render.py`(动态变量预留)
```python
class VarRender:
@staticmethod
def render_params(data: str, context: dict = None) -> str:
if not context:
context = {}
return data
```
---
## 六、全局 `conftest.py`(根目录)
```python
import pytest
from core.api_client import ApiClient
from utils.data_handler import DataHandler
@pytest.fixture(scope="session")
def api_client():
return ApiClient()
@pytest.fixture(scope="session")
def all_cases_data():
return DataHandler.load_all_cases()
def exec_pre_condition(pre_str: str, client: ApiClient):
if not pre_str.strip():
return
# 预留:登录、造数、调依赖接口等
def exec_post_condition(post_str: str, client: ApiClient):
if not post_str.strip():
return
# 预留:清理数据、登出等
```
---
## 七、按模块测试文件生成模板
**路径:** `testcases/test_{module}.py`
**约定:**
- `pytest.mark.parametrize` **不要**直接参数化 `fixture`;在模块内用 `DataHandler.get_case_by_module` 取列表。
- `headers` / `request_data` 必须 `try/except`,单条用例失败不阻断整模块加载。
- 按 `test_type`、`priority` 可映射为 pytest mark / allure severity(示例见下)。
```python
import pytest
import allure
import json
from core.assertions import ApiAssertions
from utils.data_handler import DataHandler
from conftest import exec_pre_condition, exec_post_condition
CUR_MODULE = "user"
case_list = DataHandler.get_case_by_module(CUR_MODULE)
severity_map = {
"high": allure.severity_level.CRITICAL,
"medium": allure.severity_level.NORMAL,
"low": allure.severity_level.MINOR,
}
@allure.feature(f"{CUR_MODULE} 业务模块接口")
class TestApiUser:
@pytest.mark.parametrize("case", case_list)
def test_api_case(self, api_client, case):
allure.dynamic.id(case["test_case_id"])
allure.dynamic.title(case["test_case_name"])
allure.dynamic.story(case["api_name"])
allure.dynamic.description(case["description"])
allure.dynamic.severity(severity_map.get(case["priority"]))
tag_list = [t.strip() for t in case["tags"].split(",") if t.strip()]
allure.dynamic.tag(*tag_list)
exec_pre_condition(case["preconditions"], api_client)
try:
headers = json.loads(case["headers"]) if case["headers"] else {}
except json.JSONDecodeError:
headers = {}
try:
req_data = json.loads(case["request_data"]) if case["request_data"] else {}
except json.JSONDecodeError:
req_data = {}
with allure.step(f"{case['method']} 请求:{case['url']}"):
resp = api_client.request(
method=case["method"],
url=case["url"],
headers=headers,
data=req_data,
content_type=case["content_type"],
)
ApiAssertions.assert_status_code(resp, int(case["expected_status_code"]))
if case["expected_key_assert"]:
for key in (k.strip() for k in case["expected_key_assert"].split(",") if k.strip()):
ApiAssertions.assert_key_exists(resp, key)
if case["expected_value_assert"]:
for pair in (p.strip() for p in case["expected_value_assert"].split(",") if "=" in p.strip()):
k, v = pair.split("=", 1)
ApiAssertions.assert_key_equal(resp, k.strip(), v.strip())
exec_post_condition(case["postconditions"], api_client)
```
> 多字段/多键值用**英文逗号**分隔;`expected_value_assert` 的**值**中若需含逗号,应调整解析策略或避免在 CSV 中未转义使用。`json` 解析已用 `except json.JSONDecodeError`(上例)。
---
## 八、执行命令
```bash
# 安装依赖
pip install -r requirements.txt
# 全量执行
pytest
# 按标记(如冒烟)
pytest -m smoke
# 本地查看 Allure 报告
allure serve allure-results
```
---
## 九、关键修复与约束(必须遵守)
| 项 | 要求 |
|----|------|
| 参数化与 fixture | 禁止 `parametrize` 直接引用 `fixture`;在代码内读 CSV 并按 `module` 过滤 |
| JSON 解析 | 所有从 CSV 读入的 JSON 串必须异常捕获,避免单条用例导致整段导入失败 |
| 断言策略 | 禁用全量响应断言;使用:状态码 + 字段存在 + 单键值(或少量键值) |
| 凭据与密钥 | 敏感信息放 `.env`;禁止硬编码到测试代码与 CSV(若 CSV 需占位符,走变量渲染扩展) |
| 多环境 | 通过 `TEST_ENV` 切换 `config.yaml` 中 `env` 段 |
| Allure | 自动挂接:用例 ID、名称、故事、描述、严重等级、标签 |
| 扩展 | 预留:动态变量、DB 断言、接口依赖链(`preconditions` / `postconditions` 中实现) |
---
## 十、版本与适用场景
- **版本:** V2.1(与 frontmatter `version` 一致)
- **场景:** 从标准 CSV 生成/维护可运行工程、统一用例与 Allure 报告形态。
**规则冲突时:** 以**用例 CSV 形态、列与枚举**为 **API-testcase-csv-rules.mdc** 优先;**项目结构、请求与断言语义**以本文件优先;若项目内另有 `AGENTS.md` 等更新约定,以**项目内最新**为准。
---
alwaysApply: true
rule_name: 标准接口测试用例CSV生成规则
version: V1.2
depend_rules:
- API-code-rules.mdc
pair_rules:
- API-code-rules.mdc
scene: 生成与《API接口自动化测试代码生成规范》一致的 CSV;下列条款为强制约束。字段语义与代码解析方式以 pair_rules 中代码规范为准。
---
## 一、规则目标与协作关系
**目标:** 根据接口文档、地址、参数与业务逻辑,生成可直接放入 `data/test_cases.csv` 的标准 UTF-8 CSV,无需改表头结构即可被自动化项目加载。
**协作:** CSV 列定义、枚举取值与 `headers`/`request_data` 的 JSON 格式须与 **API-code-rules.mdc** 中 `DataHandler` + `json.loads` 解析方式一致;生成用例时若存在歧义,以代码规范中的模板与断言逻辑为准。
---
## 二、必须遵守的核心规则(生成 CSV 时强制执行)
生成或补全接口测试用例 CSV 时,**必须**满足下列全部条款;**不得**因用户未复述某条而降低标准。
### 2.1 编码、表头与结构
1. **编码与形态**:输出为标准 **UTF-8** **CSV 纯文本**;禁止附带说明文字、Markdown 表格、注释或前言后记。
2. **表头**:第一行**必须且仅能**为第三节中的一行表头,顺序不可变、列不可缺。
3. **行结构**:每条用例一行;禁止多余空行;字段含逗号、换行时按 **RFC 4180** 用双引号包裹字段。
4. **空值**:除 `preconditions`/`postconditions` 填「无」外,其余列空值统一为 **空字符串** `""`;禁止 `null`、`NaN`、仅空格。
5. **headers / request_data**:须为 **合法 JSON 字符串**(双引号键名与字符串值),以便代码中 `json.loads` 解析;CSV 单元格内双引号转义为 `""`。无请求头填 `"{}"`,无请求体填 `"{}"`(与 API-code-rules 示例一致)。支持占位如 `${token}`。
### 2.2 用例数量与类型
1. 每接口至少 **2** 条:正向(`positive` + `high`)、反向(`negative` + `medium`)。
2. 有长度/范围/格式限制时,**额外**至少 **1** 条边界(`boundary` + `low`)。
3. 覆盖必填、异常、空值、格式错误及边界;`description` 与断言与场景一致。
### 2.3 字段取值摘要(细则见第四节)
| 列 | 强制摘要 |
|----|-----------|
| test_case_id | `API-{module小写}-{3位序号}`,全文件唯一 |
| method | `GET` / `POST` / `PUT` / `DELETE` |
| content_type | `json` / `form` / `form-data` |
| preconditions / postconditions | 无则填 **无**(不用空字符串表示「无」) |
| expected_key_assert | 多个字段名用英文逗号分隔;值内勿含未转义逗号 |
| expected_value_assert | 多组 `key=value`,组间英文逗号分隔;值若含逗号需约定不含或与代码解析策略一致 |
### 2.4 交付形态
1. 默认**只输出可直接保存的 CSV 正文**;用户明确要求「放在代码块里」时可用 Markdown 代码块便于复制。
2. 结果须可直接保存为 `test_cases.csv` 放入项目 `data` 目录,无需改表头。
---
## 三、CSV 固定表头(顺序不可变)
```text
test_case_id,test_case_name,module,api_name,method,url,content_type,headers,request_data,preconditions,postconditions,expected_status_code,expected_key_assert,expected_value_assert,test_type,priority,tags,description
```
---
## 四、字段生成规则(与自动化代码兼容)
1. **test_case_id**:`API-{module}-{序号}`;`module` 与 **module** 列一致;全局不重复。
2. **test_case_name**:**场景 + 结果**,简短可读。
3. **module**:小写英文,与生成文件 `testcases/test_{module}.py` 及 `DataHandler.get_case_by_module` 过滤一致。
4. **api_name**:接口功能,中文,与文档一致。
5. **method**:仅 `GET`、`POST`、`PUT`、`DELETE`。
6. **url**:相对路径,不含域名(`base_url` 在 `config.yaml`)。
7. **content_type**:仅 `json`、`form`、`form-data`,与 `ApiClient.request` 分支一致。
8. **headers**:JSON 字符串;无则 `"{}"`。
9. **request_data**:JSON 字符串;无则 `"{}"`。
10. **preconditions / postconditions**:无则 **无**。
11. **expected_status_code**:整数 HTTP 状态码。
12. **expected_key_assert**:响应 JSON **顶层**字段存在性校验;多个字段名英文逗号分隔(与代码循环断言一致);无则空字符串。
13. **expected_value_assert**:`key=value`;多组英文逗号分隔;无则空字符串。
14. **test_type**:`positive` / `negative` / `boundary`。
15. **priority**:`high` / `medium` / `low`(默认正向 high、反向 medium、边界 low)。
16. **tags**:英文逗号分隔;无则空字符串。
17. **description**:场景、目的、校验点。
---
## 五、使用者提供接口信息
用户只需提供 **【接口信息】**(地址、Method、`content_type`、参数、模块、约束等);**不要求**粘贴固定口令。生成方仍须完整执行本节至第四节。
---
## 六、示例(goods 查询)
**接口信息摘要:** `GET` `/api/goods/getDetail`,`json`,参数 `goodsId`(1–99999),模块 `goods`。
```csv
test_case_id,test_case_name,module,api_name,method,url,content_type,headers,request_data,preconditions,postconditions,expected_status_code,expected_key_assert,expected_value_assert,test_type,priority,tags,description
API-GOODS-001,商品ID合法查询详情成功,goods,商品详情查询接口,GET,/api/goods/getDetail,json,"{}","{""goodsId"":12345}",商品已存在,无,200,"code,data,msg","code=200",positive,high,"smoke,goods,query",正向:合法 goodsId,校验状态码与 code
API-GOODS-002,商品ID为空查询失败,goods,商品详情查询接口,GET,/api/goods/getDetail,json,"{}","{""goodsId"":""""}",商品已存在,无,400,"code,msg","code=400",negative,medium,"goods,query",反向:空 goodsId
API-GOODS-003,商品ID超出范围查询失败,goods,商品详情查询接口,GET,/api/goods/getDetail,json,"{}","{""goodsId"":100000}",商品已存在,无,400,"code,msg","code=400",boundary,low,"goods,query,boundary",边界:超出范围
```
---
## 七、闭环与注意事项
1. 生成 CSV → 保存 `data/test_cases.csv` → 按 **API-code-rules.mdc** 生成/维护测试代码 → `pip install -r requirements.txt` → `pytest`,按需 Allure。
2. 接口信息越完整,用例越准;生成后核对表头与列数、RFC 4180 引号。
3. `${token}` 等与 `var_render` 扩展配合;多接口时注意 **test_case_id** 不重复、**module** 命名统一。
---
alwaysApply: true
---
# UI / API 自动化测试用例设计原则
本规则约定**自动化用例**层面的设计原则(与功能测试 MD 结构化规范、CSV 表头规范分工:前者管人工/文档用例形态,后者管数据驱动契约)。编写或评审 UI/API 自动化脚本时遵循本文。
---
## 第一部分:UI 自动化测试用例设计
### 一、核心设计原则
1. **业务优先级原则**
优先覆盖核心主流程、高频场景、高危场景,放弃边缘低频、临时活动、极少变更页面。自动化只做稳定、高价值的用例,不追求 100% 覆盖。
2. **单一职责原则**
一条用例只验证一个核心业务结果(如:登录成功是否进入首页、加购后数量是否 +1)。用例内部可以有多个操作步骤,但断言只有一个。失败时能立即定位是哪个业务点出了问题。
3. **原子化、可复用原则**
将公共操作(登录、退出、弹窗处理、下拉选择)封装为独立方法。用例层只调用这些方法,不重复编写步骤。一处维护,处处生效。
4. **独立性原则**
每条用例独立执行,互不依赖,不依赖上一条用例的执行结果。可单独运行,也可批量并行,执行顺序不影响结果。用例执行前需保证初始状态一致。
5. **正向优先、兼顾反向原则**
优先覆盖正向正常流程,确保核心功能可用。再补充关键反向异常场景:非法输入、边界值、网络异常、权限不足、重复提交等。
6. **稳定性优先原则**
优先使用稳定定位方式:`id` > `accessibility-id` > 相对 xpath。对于动态元素(随机 ID、时间戳、动态文案),使用包含匹配、正则匹配或向上查找稳定父节点的方式定位。避免绝对 xpath 和固定坐标。
7. **断言精准原则**
每步关键操作后必有断言,不只跑通流程。断言只校验核心关键字段/状态,不做过度断言(如 UI 样式、间距等),避免微小 UI 变更导致误失败。
8. **数据隔离原则**
使用独立测试账号、独立测试数据,支持参数化。用例执行前后做好数据清理/重置(如退出登录、清空购物车),避免数据污染影响其他用例。
9. **可维护性原则(POM)**
采用 Page Object Model 分层设计:
- 元素定位器放在 Page 类顶部
- 操作方法放在 Page 类中
- 用例层只调用 Page 方法
页面改版时,只需修改 Page 类中的定位器,用例代码尽量不改。同时保持用例命名规范、步骤注释清晰。
10. **适配兼容性原则**
设计时考虑多分辨率、多设备、横竖屏切换等场景。避开固定坐标点击,使用相对布局或百分比定位。移动端注意 iOS/Android 的控件差异。
11. **快速失败原则**
每个关键操作后设置超时断言(如 10–15 秒),不要仅依赖全局过长等待。用例执行过程中一旦发现无法继续的失败(如元素找不到),立即抛出明确错误信息,包含:
- 失败步骤名称
- 预期元素定位器
- 当前页面截图
便于快速判断是元素变更还是业务缺陷。
### 二、UI 自动化不适合写用例的场景
| 场景类型 | 原因 |
|---------|------|
| 频繁迭代、页面经常改版的临时功能 | 维护成本高于收益 |
| 纯 UI 展示、无业务逻辑的静态页面 | 价值低,手工目测更快 |
| 复杂验证码、人机校验无法绕过的场景 | 自动化难以处理 |
| 极低频次、几乎不会回归的边缘功能 | 投入产出比低 |
| 视觉细节(像素级配色、字体间距、动画效果) | 适合人工或视觉 diff 工具 |
### 三、原则分组速记(按职责)
| 分组 | 包含原则 | 一句话记忆 |
|------|-----------|------------|
| 选什么 | 1. 业务优先级、10. 适配兼容性 | 只测高价值稳定场景 |
| 怎么写 | 2. 单一职责、3. 原子复用、4. 独立性、5. 正向+反向 | 用例独立、步骤可复用、断言单一 |
| 怎么稳 | 6. 稳定性优先、7. 断言精准、11. 快速失败 | 稳定定位、精准断言、快速失败 |
| 怎么维护 | 8. 数据隔离、9. 可维护性 | 数据独立、POM 分层 |
### 四、一句话总结(UI)
选高价值稳定场景,用例独立且断言单一;步骤原子化复用,POM 分层易维护;动态元素稳定定位,失败时有截图有提示,快速定位根因。
---
## 第二部分:API 自动化测试用例设计
### 一、核心设计原则
1. **业务优先级原则**
优先覆盖核心接口、高频调用、读写操作(POST/PUT/DELETE),其次是查询类接口(GET)。对于低频、内部管理类、即将下线的接口,不优先自动化。
2. **单一职责原则**
一条用例只验证一个接口的一种响应情况(如:正常参数返回 200、缺少参数返回 400)。不要把多个接口、多种断言塞进一条用例。
- 正确示例:`test_login_success`、`test_login_wrong_password`
- 错误示例:`test_login_and_query_and_update`
3. **全链路覆盖原则**(相对 UI 的「原子复用」)
单接口用例保证基础正确性后,需设计业务场景串联用例(如:登录 → 查询 → 更新 → 删除)。每个步骤的响应数据作为下一步的输入参数,验证完整业务流程。
4. **独立性原则**
每条用例独立执行,互不依赖。不假定其他用例先执行,不依赖用例执行顺序。用例执行前需准备好前置数据,执行后清理产生的数据(尤其是写入操作)。
5. **正向优先、兼顾反向原则**
先覆盖正向用例:正常参数、必填参数、正确类型、边界内值。再补充反向用例:参数缺失、参数类型错误;边界值超限、空值、超长字符串;未授权访问、Token 过期、权限不足;重复提交、资源不存在等。
6. **幂等性原则(针对 PUT/DELETE)**
重复执行同一用例多次,结果应一致。不应出现第一次成功、第二次报错的情况。对于非幂等的业务操作(如扣款),需在用例中做好数据重置。
7. **断言精准原则**
不只看 HTTP 状态码(如 200),必须校验响应体关键字段:业务状态码(如 `code`)、返回消息(如 `message`)、核心数据字段(如 `id`、`status`)、数据结构(如数组长度、字段类型)。
**反例**:只断言 `response.status_code == 200`,接口业务错误但状态码仍为 200 时用例仍会通过。
8. **数据隔离原则**
使用独立测试数据,不依赖生产或共享环境数据。每条用例执行前自己创建所需数据(或从隔离库获取),执行后自行清理。避免用例间数据污染。
9. **可维护性原则**
采用分层设计:
- 配置层:环境 URL、超时时间、通用 Header
- 数据层:测试数据与代码分离(JSON/YAML/Excel 等)
- 接口封装层:每个接口封装为独立方法,统一处理请求、日志、重试
- 用例层:只调用封装方法,不出现裸 HTTP 细节散落
10. **环境适配原则**
支持多环境切换(开发/测试/预发/生产),不硬编码 IP 或域名。通过配置文件或环境变量控制;且不在预发/生产环境执行写操作用例。
11. **快速失败原则**
设置合理超时(通常 3–5 秒),不无限等待。断言失败时立即终止用例,并输出:请求 URL + 请求体;响应状态码 + 响应体;期望值 vs 实际值。
### 二、API 自动化不适合写用例的场景
| 场景类型 | 原因 |
|---------|------|
| 一次性接口、临时活动接口 | 开发完即下线,投入无回报 |
| 强依赖第三方、无法 Mock 的接口 | 不稳定、不可控 |
| 纯透传、无业务逻辑的代理接口 | 无验证价值 |
| 需要人工审批/扫码/OTP 的接口 | 无法完全自动化 |
| 接口响应数据结构频繁变更 | 维护成本高 |
### 三、一句话总结(API)
选核心读写接口,用例独立且校验响应体;封装接口方法支持场景串联;数据自建自清保证幂等;失败时输出请求响应详情,快速定位根因。
---
## 与仓库内其他规则的关系
- **功能/结构化 MD 用例**:见 `testcase_structured_witing_specification.mdc`。
- **测试点分析**:见 `test-point-rules.mdc`。
- **接口 CSV 表头与生成约束**:见 `API-testcase-csv-rules.mdc`;**接口自动化代码与解析**:见 `API-code-rules.mdc`。
- 本文不替代上述文件的格式与契约,仅补充**自动化用例设计与选型**层面的原则。
---
alwaysApply: false
description: 工作区 knowledge-base 目录结构、全局编号、YAML 元数据强制项、治理流程与各库 INDEX 维护约定;编辑或新建知识库 Markdown 时启用
globs: knowledge-base/**/*.md
---
# 测试与质量知识库(治理规则)
本规则与仓库内 **`knowledge-base/`** 目录一一对应。Agent 或成员在**新建、修改、拆分、索引**该目录下任意 `.md` 时,须遵守本节;细则以 `knowledge-base/09-知识库治理规范/` 下文件为准。
---
## 一、完整目录(须保持文件夹名称一致)
```text
knowledge-base/
├── 01-业务规则库/
│ └── INDEX.md
├── 02-历史缺陷库/
│ └── INDEX.md
├── 03-接口契约库/
│ └── INDEX.md
├── 04-业务流程旅程库/
│ └── INDEX.md
├── 05-测试资产用例库/
│ ├── INDEX.md
│ ├── API用例矩阵/
│ ├── UI-E2E用例/
│ ├── 自动化失败用例登记/
│ └── Flaky不稳定用例治理/
├── 06-测试问题沉淀库/
│ └── INDEX.md
├── 07-测试数据资产库/
│ └── INDEX.md
├── 08-UI专项规范库/
│ └── INDEX.md
└── 09-知识库治理规范/
├── INDEX.md
├── 01-全局编号命名规范.md
├── 02-通用文档元数据模板.md
├── 03-文档变更更新流程.md
└── 04-版本复盘落地机制.md
```
---
## 二、全局固定配置(优先阅读)
| 文档 | 作用 |
|------|------|
| `09-知识库治理规范/01-全局编号命名规范.md` | 各库**编号前缀**、**文件名 = 完整编号.md**、业务域简写、状态枚举、与 CSV 用例 ID 并存时的互链说明 |
| `09-知识库治理规范/02-通用文档元数据模板.md` | **所有** `knowledge-base/**/*.md` 文件头须使用的 **YAML** 模板与字段说明 |
---
## 三、YAML 元数据(强制)
以下键**不可删减**(值可为空字符串 `""`),须出现在每个 `.md` 文件最上方的 YAML front matter 中:
`title`、`module`、`status`、`related_br`、`related_api`、`related_jrn`、`related_tc`、`related_def`、`create_time`、`update_time`、`author`
新建文档:先粘贴 `02-通用文档元数据模板.md` 中的代码块,再写正文。
---
## 四、各库 INDEX 职责
- 每个编号库根下的 **`INDEX.md`** 为**台账**:新增/变更/废弃条目时,**同步更新**表格行与维护人、时间、状态。
- 各库「说明 / 关联指引 / 枚举」以已落盘的 `INDEX.md` 为准;Agent 补全内容时不得删除既有章节标题结构。
---
## 五、治理原则(摘要)
1. 需求变更 → 同步 **业务规则**、**接口契约**、**流程旅程**。
2. Bug 修复 → 新增或关联 **回归用例**(`related_tc`)。
3. 接口迭代 → 同步 **API 用例矩阵** 与 **UI 关联**说明。
4. 每版本结束 → 按 `04-版本复盘落地机制.md` 将结论落入对应库。
流程细节:`03-文档变更更新流程.md`、`04-版本复盘落地机制.md`。
---
## 六、与项目其他规则的衔接
- **API CSV 用例表头与断言**:仍以 `API-testcase-csv-rules.mdc`、`API-code-rules.mdc` 为准;知识库中的 `API-*`、`TC-API-*` 通过 `related_tc` / 文档描述与之**互链**,避免重复定义冲突。
- **自动化用例设计原则**:见 `automation-testcase-design-principles.mdc`。
- **功能结构化用例(MD H1–H6)**:见 `testcase_structured_witing_specification.mdc`;与 `05-测试资产用例库` 可并存,职责不同需在元数据或正文中标明。
---
## 七、Agent 操作约束
1. 在 `knowledge-base/` 下**新建** `.md`:必须使用规范 **文件名**(见 `01-全局编号命名规范.md`)与 **YAML** 头。
2. **禁止**删除各库 `INDEX.md` 中的固定表格列名;无内容时保留空行或用占位说明「暂无」。
3. 修改条目内容时,**必须**更新该文件的 `update_time`;若影响其他库,联动更新对应 `related_*` 与相关 `INDEX.md`。
---
description: 需求评审规则 - 基于PRD/原型/业务背景开展标准化需求评审,识别歧义、缺失、逻辑冲突、落地风险,输出评审报告与整改清单
globs: *.md
alwaysApply: false
---
# 需求评审规则
## 仓库路径约定(可选落盘,与 UI 自动化同仓)
- 项目根目录命名支持两种方式:**用户自定义项目名称** 或 **由 Agent 自动生成项目名称**;下文统一以 `{项目名称}` 作为占位符。
- 自动生成命名规则(未指定项目名称时):`{业务域}-{端类型}-automation`(如 `crm-ui-automation`);命名统一使用小写字母、数字、短横线 `-`,空格转为 `-`,连续分隔符合并为单个 `-`。
- 若将评审产出纳入本工作区 Git 管理,建议目录:**`{项目名称}/docs/requirements-review/`**(可按迭代再分子文件夹)。
- 与测试相关的下游产物路径统一参见:`{项目名称}/testpoint/`、`{项目名称}/testcases/`、`{项目名称}/testcase/`(见对应专项规则),**勿**再使用 `crmeb-ui-e2e/` 等未在本仓库定义的路径。
## 评审目标
依据PRD、产品原型、业务背景、线上现状、技术架构与合规要求,对需求进行全维度标准化评审。
提前识别需求歧义、规则缺失、逻辑矛盾、边界遗漏、交互漏洞、落地风险,输出整改建议、规则补充、风险预警,确保需求**完整、清晰、统一、可落地、可测试、可验收**,为下游测试点拆解、用例设计、研发开发提供稳定依据。
## 评审维度
### 1. 需求完整性
- 核心流程、分支流程、反向流程全覆盖
- 业务约束、数值规则、状态流转、权限规则定义完整
- 正常、边界、异常、极端、兜底场景无缺失
- 上下游模块、数据联动、跨系统交互规则明确
### 2. 需求清晰度
- 功能描述、操作逻辑、约束条件无歧义
- 字段定义、默认值、必填项、选项范围明确
- 角色权限、数据范围、操作限制划分清晰
- 交互逻辑、弹窗提示、二次确认、失败反馈统一说明
### 3. 业务合理性
- 设计贴合实际业务场景与用户操作习惯
- 操作路径精简合理,无冗余步骤与反常识设计
- 风控限制、数据约束、流程管控符合业务目标
- 功能价值匹配迭代诉求,无无效冗余设计
### 4. 逻辑一致性
- 全局状态、文案、按钮、术语、规则统一
- 同字段/同状态在多页面、多操作中逻辑无冲突
- 新增/编辑/删除/审核等通用操作口径一致
- 历史功能、存量数据、旧版本兼容逻辑明确
### 5. 落地可行性
- 适配现有技术架构,无无法实现的过度设计
- 性能、并发、大数据量、多终端兼容风险可控
- UI组件、交互规范、移动端适配可落地
- 开发、测试、运维成本合理可控
### 6. 可测试 & 可验收
- 验收标准量化、可观测、可落地
- 所有规则、条件、状态变更可校验、可复现
- 异常拦截、报错提示、兜底展示有明确定义
### 7. 安全与合规
- 越权访问、重复提交、数据泄露风险可控
- 敏感数据脱敏、权限隔离、操作风控完善
- 符合行业合规、隐私政策与监管要求
## 评审方法
1. 需求通读拆解:梳理业务目标、核心流程、功能范围、变更内容
2. 逐条规则校验:按模块、页面、操作、条件逐条核对需求描述
3. 边界反向推演:覆盖临界值、空数据、中断操作、异常中断场景
4. 跨模块联动校验:核查模块依赖、数据同步、消息联动、状态互通
5. 多视角交叉评估:业务、产品、研发、测试四维视角综合评审
## 评审报告结构
1. 评审概述:范围、版本、依据、整体评价
2. 需求完整度分析:缺失场景、遗漏规则、流程断点汇总
3. 需求质量分析:歧义点、逻辑冲突、口径不统一问题
4. 落地风险评估:技术风险、性能风险、上线风险、合规风险
5. 分级问题清单:严重问题 / 一般问题 / 优化建议
6. 需求补充方案:缺失规则、边界逻辑、异常处理补充
7. 最终评审结论:准入判定、必改项、优化项、后续要求
## 评审分级标准
### 1. 完整性标准
- 核心业务流程:100%覆盖
- 业务规则 & 状态流转:100%明确定义
- 边界/异常兜底场景:≥95%覆盖
### 2. 规范标准
- 无模糊描述、无口头化表述、无逻辑自相矛盾
- 全局交互、文案、状态、校验规则保持统一
### 3. 准入标准
- 严重问题全部整改完成,方可进入测试点拆解与开发排期
- 一般问题可按需优化,体验类建议可迭代优化
## 评审输出物
1. 需求评审报告(MD格式)
2. 分级问题整改清单(可直接同步产品闭环)
3. 需求规则补充文档(合并更新至PRD)
## 标准评审流程
1. 准备阶段:收集PRD、原型、流程图、迭代背景
2. 执行评审:分模块逐项校验+边界反向推演
3. 问题汇总:按严重等级分类,标注影响范围
4. 报告输出:标准化评审报告+整改清单
5. 闭环复核:跟进修改,复审通过后归档
## 注意事项
- 保持客观中立,以规范、业务、落地为核心依据
- 全局视角评审,兼顾存量兼容与跨模块联动
- 需求变更需触发简易复审,保证资料同步更新
- 与《测试点分析规则》《功能测试用例结构化编写规范》保持口径统一
\ No newline at end of file
---
description: 测试点分析规则 - 从定稿需求中标准化提取模块/功能/验证点,统一分类、颗粒度、优先级,为结构化测试用例提供输入
globs: *.md
alwaysApply: false
---
# 测试点分析规则
## 仓库路径约定(勿建平行空工程)
- 项目根目录命名支持两种方式:**用户自定义项目名称** 或 **由 Agent 自动生成项目名称**;下文统一以 `{项目名称}` 作为占位符。
- 自动生成命名规则(未指定项目名称时):`{业务域}-{端类型}-automation`(如 `crm-ui-automation`);命名统一使用小写字母、数字、短横线 `-`,空格转为 `-`,连续分隔符合并为单个 `-`。
- **测试点 Markdown 输出路径**:`{项目名称}/testpoint/{需求名称}-测试点分析-{序号}.md`(与项目根目录 `{项目名称}` 同仓)。
- **Playwright 自动化**:见 `{项目名称}/tests/`(与 `testpoint` / `testcases` 并列)。
- 禁止脱离该根目录另建 `crmeb-ui-e2e` 等仅放文档的平行工程,除非需求明确要求多仓。
## 概述
本规则用于在**需求评审定稿后**,统一拆解、提取、梳理测试点。
测试点只定义「需要验证什么」,不包含操作步骤、测试数据,作为上游需求、下游测试用例的中间标准载体。
## 核心定义
**测试点**:从规范需求中提炼的独立验证项,用于界定需覆盖的功能、规则、边界、异常、集成校验范围。
- 测试点:回答「要测什么」
- 测试用例:回答「怎么测、用什么数据、预期结果是什么」
## 测试点统一分类
### 1. 功能验证点
核心业务流程、正常操作、数据处理、状态正向变更、基础业务规则。
### 2. 边界验证点
输入长度、数值范围、数量上限、时间区间、分页限制、权限临界条件。
### 3. 异常验证点
非法输入、重复操作、断网、服务异常、无权限、会话过期、错误提示。
### 4. 集成验证点
模块联动、第三方对接、接口回调、跨模块数据同步、消息通知联动。
## 测试点提取统一规则
1. 层级拆分:模块 → 功能点 → 验证类型 → 原子测试点
2. 动作拆解:围绕CRUD、业务操作、状态变更提取验证项
3. 规则拆解:所有条件判断、约束限制、强制校验单独成点
4. 数据拆解:格式、完整性、一致性、唯一性逐一提取
5. 场景拆解:主动推导异常、边界、兜底隐性场景
## 颗粒度与联动规范
- 原子性原则:一个测试点只对应一项独立验证,避免糅合
- 联动关系:1个测试点可向下拆解为多条结构化测试用例
- 优先级对齐:高/中/低 与需求评审、测试用例优先级统一
- 高:主线流程、上线阻断、核心规则
- 中:常规约束、次要功能、分支流程
- 低:边缘场景、兼容兜底、体验优化
## 标准模板与编号规范
- 测试点ID:`TP_模块_功能_序号`
- 固定字段:测试点名称、测试分类、优先级、验证要点、关联需求
## 质量检查清单
- 全覆盖:匹配定稿需求100%业务规则
- 分类准:功能/边界/异常/集成归类无误
- 无重复:通用规则统一归集,避免多模块重复
- 可落地:便于直接转化为结构化测试用例
## 输出格式规范
统一Markdown层级:模块→功能点→四类验证点→列表化测试点,
文件路径:`{项目名称}/testpoint/{需求名称}-测试点分析-{序号}.md`
## 使用流程
需求定稿 → 拆解功能模块 → 提取四类测试点 → 质量自检 → 评审归档 → 输出至用例设计环节
\ No newline at end of file
---
alwaysApply: false
description: CSV 格式功能测试用例生成规范 - 与 MD 结构化用例双轨同源
---
# CSV格式功能测试用例生成规范
## 零、仓库路径约定(勿建平行空工程)
- 项目根目录命名支持两种方式:**用户自定义项目名称** 或 **由 Agent 自动生成项目名称**;下文统一以 `{项目名称}` 作为占位符。
- 自动生成命名规则(未指定项目名称时):`{业务域}-{端类型}-automation`(如 `crm-ui-automation`);命名统一使用小写字母、数字、短横线 `-`,空格转为 `-`,连续分隔符合并为单个 `-`。
- **本工作区 UI 自动化根目录**:`{项目名称}/`(与 Playwright、`data/`、结构化 MD 用例同仓)。
- **CSV 落盘目录**:`{项目名称}/testcase/`(目录名 `testcase`,与 `testcases/` 区分:后者放 `.md` 结构化用例)。
- **Playwright 代码用例**:在 `{项目名称}/tests/`(`testDir`),与 `testcase` / `testcases` 并列,职责不同。
- 禁止再使用无仓库根的写法(如单独 `/testcase/`、`crmeb-ui-e2e/` 等易误解路径)。
## 一、规范定位与整体联动
本规范为**测试执行层落地规范**,与《需求评审规则》《测试点分析规则》《功能测试用例结构化编写规范》形成完整闭环质量体系。
采用**一套设计源、两套输出物**双轨模式:
1. 设计评审层:以 **MD 结构化测试用例** 为唯一源头标准、评审依据、长期维护载体。
2. 测试执行层:基于定稿 MD 结构化用例,统一转换生成 **CSV 表格用例**。
3. 核心约束:所有业务规则、场景逻辑、验收标准仅在 MD 源文件维护;CSV 只做格式重组、执行字段补充,严禁单独修改业务内容,保证同源一致、无缝双向切换。
## 二、目标要求
当需要输出可落地执行的测试用例时,无需重复编写,直接基于评审通过的结构化 MD 用例,按本规范自动转换生成标准 CSV 文档。
CSV 文件统一存放至 **`{项目名称}/testcase/`** 目录,作为测试执行、结果回填、进度统计、缺陷关联、项目交付、归档留存的标准表格用例。
## 三、文件格式与命名规范
### 3.1 基础文件规则
- 编码格式:UTF-8
- 分隔符:英文逗号 `,`
- 文件格式:`.csv`
- 存放目录:固定 **`{项目名称}/testcase/`**(相对当前 Git 仓库根目录)
### 3.2 文件命名规范
- 命名格式:`[需求名称-测试用例-序号].csv`
- 示例:`{项目名称}/testcase/用户登录功能-测试用例-1.csv`
## 四、CSV固定列结构(9列固定,不可删减)
| 列名 | 说明 | 必填 |
|------|------|------|
| 用例编号 | 按功能模块缩写+三位序号编制,同文件唯一不重复 | 是 |
| 功能模块 | 所属一级业务模块名称,取自MD结构化用例H2标题 | 是 |
| 功能测试点 | 细分功能测试点名称,取自MD结构化用例H3标题 | 是 |
| 用例标题 | 简洁清晰描述单条测试场景,取自MD H5用例场景 | 是 |
| 优先级 | 高/中/低,与MD中P0/P1/P2严格映射 | 是 |
| 前置条件 | 执行当前用例所需前置环境、账号、数据;简单场景可留空 | 否 |
| 测试步骤 | 有序分步操作,序号+换行格式,可直接落地执行 | 是 |
| 预期结果 | 明确、可验证、无歧义的系统表现与数据状态 | 是 |
| 实际结果 | 测试执行阶段回填字段,设计阶段默认留空 | 否 |
## 五、全域统一映射规则
### 5.1 优先级双向对齐
- MD 结构化用例:P0 / P1 / P2
- CSV 执行用例:
- P0 → 高
- P1 → 中
- P2 → 低
### 5.2 内容同源规则
- 功能模块、测试点、用例标题、预期结果:完全复用 MD 原文。
- 测试步骤:由 MD 场景描述拆解细化为标准化分步操作。
- 需求变更、用例优化、缺陷修复:**先更新 MD 源文件,再同步更新 CSV**,杜绝版本分裂。
## 六、用例编号规范
1. 禁止全局统一无意义前缀(如固定 TCC),编号必须关联业务模块。
2. 标准格式:`模块缩写-三位序号`,示例:`LOGIN-001`、`USER-002`、`PAY-003`。
3. 模块缩写:取功能模块名称 2~6 位英文/拼音缩写,多级模块使用短横线连接。
4. 同文件要求:序号连续递增、无跳号、无重复,全局唯一可追溯。
## 七、内容编写格式标准
### 7.1 测试步骤格式
- 统一规范:`1. 操作内容\n2. 操作内容\n3. 操作内容`
- 内容要求:页面路径、按钮操作、输入内容、触发动作描述具体可复现。
### 7.2 预期结果格式
- 整合 MD 多条预期描述,合并为连贯通顺文本。
- 必须包含:页面展示、交互提示、按钮状态、数据落库、业务约束、报错文案。
- 禁止模糊描述,做到可观测、可对比、可验收。
### 7.3 特殊字符转义规则
单元格内容包含**逗号、双引号、换行符、特殊符号、公式字符**时,
整单元格使用英文双引号包裹并自动转义,防止 CSV 解析乱码、列错位。
## 八、测试用例设计方法
转换与设计过程中,必须完整覆盖六大标准测试设计方法,与上游测试点规范统一:
1. **等价类划分**
划分有效等价类、无效等价类,每类至少覆盖一条用例,覆盖合法与非法输入。
2. **边界值分析**
覆盖最大值、最小值、临界值、临界±1、空值、超长字符、数量上限等边界场景。
3. **判定表驱动法**
针对多条件、多规则组合场景,完整覆盖所有条件组合分支。
4. **场景法**
覆盖正常流程、异常流程、编辑修改、重复操作、取消中断等全用户场景。
5. **错误推测法**
基于业务经验补充特殊字符、脚本注入、网络异常、并发操作、权限越权等隐性用例。
6. **状态迁移法**
针对带状态流转功能,覆盖正常转换、异常转换、回退转换全链路。
## 九、MD 转 CSV 标准流程
1. 数据源确认:使用评审通过、定稿归档的 MD 结构化测试用例。
2. 结构提取:依次提取功能模块、功能测试点、用例场景、优先级、预期结果。
3. 内容加工:拆解标准化测试步骤、补充前置条件、编制模块化用例编号。
4. 格式处理:特殊字符转义、换行统一、单元格格式化处理。
5. 文件生成:在 **`{项目名称}/testcase/`** 目录生成 UTF-8 标准 CSV 文件。
6. 自检验收:核对字段完整性、编号唯一性、内容一致性、文件打开可用性。
## 十、质量验收标准
### 10.1 格式验收
- CSV 使用 Excel / WPS / 记事本 打开无乱码、无列偏移、无格式错误。
- 9 列字段完整,编码 UTF-8,分隔符规范。
- 特殊字符、多行内容转义合规,解析正常。
### 10.2 内容验收
- 100% 覆盖 MD 结构化用例全部场景,无遗漏、无删减。
- 用例独立可执行、可重复复现、无依赖耦合。
- 编号规范、优先级准确、步骤清晰、预期结果可验证。
### 10.3 体系联动验收
- 用例范围与需求评审、测试点分析完全匹配,需求覆盖率达标。
- 严格遵循双轨机制,不脱离 MD 源头单独维护 CSV。
## 十一、注意事项
1. 设计评审阶段统一使用 MD 结构化用例,测试执行阶段统一输出 CSV。
2. 所有 CSV 用例统一归档至 **`{项目名称}/testcase/`** 目录,集中管理、便于迭代。
3. 所有需求变更、规则调整、用例优化,严格执行「MD优先、CSV同步」。
4. 对外交付、测试执行、日常测试填表,统一使用 CSV 格式文件。
5. 全程与整套质量规范术语、标准、优先级保持统一,保证团队协作一致性。
\ No newline at end of file
---
alwaysApply: false
description: 功能测试用例结构化编写规范 - 基于标准化测试点,统一MD层级、格式、写法、优先级,兼容XMind导入,实现测试点→用例标准化流转
---
# 功能测试用例结构化编写规范
## 仓库路径约定(勿建平行空工程)
- 项目根目录命名支持两种方式:**用户自定义项目名称** 或 **由 Agent 自动生成项目名称**;下文统一以 `{项目名称}` 作为占位符。
- 自动生成命名规则(未指定项目名称时):`{业务域}-{端类型}-automation`(如 `crm-ui-automation`);命名统一使用小写字母、数字、短横线 `-`,空格转为 `-`,连续分隔符合并为单个 `-`。
- **结构化 MD 用例**:`{项目名称}/testcases/`(见下文「输出物规范」)。
- **同源 CSV 执行用例**:`{项目名称}/testcase/`(见《CSV格式功能测试用例生成规范》)。
- **测试点分析文档**:`{项目名称}/testpoint/`(见《测试点分析规则》)。
- **Playwright 自动化用例**(`@playwright/test`):目录名为 **`tests/`**,由 `{项目名称}/playwright.config.ts` 的 **`testDir: './tests'`** 指定,与 Playwright 官方脚手架一致;放 `*.spec.ts`,**不得**与 `testcases/`(人工维护的 H1–H6 结构化 MD)合并或改名混用。
- 上述均相对当前工作区 Git 根目录;勿再使用已废弃的 `crmeb-ui-e2e/` 等易混淆路径。
## 规范目标
基于上游**标准化测试点文档**,统一功能测试用例的结构、层级、描述、预期结果、优先级,
实现:格式统一、内容清晰、XMind兼容、可直接执行、便于评审与维护,打通「需求评审→测试点→测试用例」全链路质量体系。
## 核心结构(固定H1~H6层级,不可改动)
H1 文档标题
H2 功能模块
H3 功能测试点
H4 验证点
H5 用例场景
H6 预期结果
## 用例设计来源
1. 优先基于正式测试点文档一对一/一对多转化
2. 无测试点时,采用等价类、边界值、场景法、错误猜测、状态迁移法补充设计
3. 四类验证点严格对应:
- 功能测试点 → 正向主流程用例
- 边界测试点 → 边界值专项用例
- 异常测试点 → 异常拦截&错误处理用例
- 集成测试点 → 跨模块联调用例
## 编写统一规范
### 1. 用例场景(H5)
简洁描述操作+条件+场景,关键测试数据显性体现,不冗余、不模糊。
### 2. 预期结果(H6)
必须具体、可验证、无模糊话术,覆盖:页面展示、交互反馈、数据落库、状态变更。
### 3. 优先级轻量化标注
统一标注【P0/P1/P2】,与需求评审、测试点优先级完全对齐:
- P0:高优,核心主线,上线必过
- P1:中优,常规业务规则
- P2:低优,边界、异常、兜底兼容
### 4. 复杂场景兼容优化
- 简单场景:仅保留 用例场景 + 预期结果
- 复杂多步骤场景:可**按需增加可选操作步骤**,不新增层级,不破坏原有结构
## 设计方法应用要求
- 必选:等价类划分 + 边界值分析
- 按需:场景法、判定表、状态迁移、错误猜测法补充覆盖
## 输出物规范
- 存放路径:`{项目名称}/testcases/{需求名称}-测试用例-{序号}.md`(与当前 UI 自动化工程同仓;若多项目可再分子目录)
- 格式统一、编号连续、模块分类清晰、便于迭代维护
## 管理与维护
1. 版本化管理,需求变更同步更新用例
2. 定期评审优化,删除冗余、补充缺失场景
3. 全程与需求评审规则、测试点分析规则保持术语、口径、标准统一
\ No newline at end of file
# 环境:dev | test | prod
TEST_ENV=test
BASE_URL=http://spdtest.cmic.com.cn:8080
LOGIN_PATH=/spd/login
HOME_PATH=/spd/home/index
# 业务账号(勿提交真实密码;复制本文件为 .env 后填写)
TEST_USER=
TEST_PASS=
# 设为 1 时每次运行强制重新登录(默认复用 auth/user.json,token 过期或失效才登录)
# FORCE_AUTH_LOGIN=0
# token 剩余有效期 ≥ AUTH_FRESH_REMAINING_MS 时跳过首页探活(默认 20 分钟);设 0 强制探活
# AUTH_SKIP_PROBE_IF_FRESH=1
# AUTH_FRESH_REMAINING_MS=1200000
# 框架超时(毫秒):短超时 + 明确失败;不设则用 config/timeouts.config.ts 默认
# TEST_TIMEOUT=60000
# EXPECT_TIMEOUT=8000
# ACTION_TIMEOUT=12000
# TABLE_READY_TIMEOUT=20000
# 操作层瞬时重试(click/fill/goto),默认 3 次;断言失败不会走这里
# ACTION_RETRIES=3
# ACTION_RETRY_DELAY_MS=250
# 整条用例重试默认 0(不要全局打开);仅排查时可显式设 PW_RETRIES
# PW_RETRIES=0
# 已知瞬时 flaky 用 testFlaky,额外重跑次数(不含首次)
# PW_FLAKY_RETRIES=1
# 隔离清单:CI 默认 skip quarantine 项;never=主门禁也不隔离
# QUARANTINE_POLICY=skip-in-ci
# RUN_QUARANTINE=1
# FLAKE_MIN_RUNS=4
# FLAKE_RATE=0.3
# PW_WORKERS=3
# 分片并行(npm run test:warn:shard / test:spd:shard):SHARDS=3 WORKERS=2
# 排序全列等 @full 用例:RUN_FULL=1
# Jenkins / 定时:npm run test:ci:smoke | test:ci:nightly | SUITE=center npm run test:ci
# 套件清单:npm run suites
# 分片:SHARDS=2 或 PW_SHARD=1/2(多 Agent)
# 默认按文件并行;不要开 PW_FULLY_PARALLEL=1(同账号会互踩)
# 冒烟快速失败:PW_MAX_FAILURES=8
# 关闭 Allure:ALLURE=0
# 纳入调试 spec:INCLUDE_DEBUG_SPECS=1
# 跳过站点探活:SKIP_PREFLIGHT=1
# Playwright 浏览器缓存目录(默认项目内 .playwright-browsers/,postinstall 自动安装 chromium)
# PLAYWRIGHT_BROWSERS_PATH=.playwright-browsers
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
# MySQL(只放 .env,勿入库;示例勿填内网地址/真实库名)
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_DATABASE=your_database
name: Playwright Tests
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:
inputs:
suite:
description: '执行套件'
required: true
default: smoke
type: choice
options:
- smoke
- nightly
- full
- warn
- center
jobs:
test:
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: npm
- uses: actions/cache@v4
id: playwright-browsers
with:
path: .playwright-browsers
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- run: npm ci
- name: Typecheck
run: npm run typecheck
- run: npx playwright install-deps chromium
- name: Run suite
run: node scripts/run-ci.mjs --suite="${{ github.event.inputs.suite || 'smoke' }}"
env:
CI: true
PW_RETRIES: '0'
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PASS: ${{ secrets.TEST_PASS }}
BASE_URL: ${{ secrets.BASE_URL }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: reports/latest/
retention-days: 7
node_modules/
dist/
test-results/
playwright-report/
allure-results/
allure-report/
blob-report/
playwright/.cache/
.playwright/
.playwright-cli/
.playwright-browsers/
.pw-home/
auth/*.json
.env
*.env
!.env.example
.DS_Store
*.log
*.pid
.idea/
.vscode/
logs/
# 执行产物只保留说明;其余一律不入库
reports/*
!reports/README.md
# Cursor Agent Skills 不入库;框架运行不依赖它们。保留 .cursor/rules/
.cursor/skills/
# GitLab CI:默认只做类型检查。冒烟需配置 CI/CD Variables:TEST_USER、TEST_PASS,可选 BASE_URL。
# 完整回归走 Jenkins(见 docs/jenkins.md),不要把 nightly/full 绑在每次 MR 上。
stages:
- check
- test
default:
image: node:20
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.playwright-browsers"
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
CI: "true"
PW_RETRIES: "0"
cache:
key:
files:
- package-lock.json
paths:
- .npm/
typecheck:
stage: check
script:
- npm ci --ignore-scripts
- npm run typecheck
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
smoke:
stage: test
image: mcr.microsoft.com/playwright:v1.55.0-jammy
timeout: 2 hours
needs: ["typecheck"]
script:
- npm ci
- node scripts/run-ci.mjs --suite="${SUITE:-smoke}"
artifacts:
when: always
expire_in: 7 days
paths:
- reports/latest/
rules:
- if: $CI_PIPELINE_SOURCE == "web"
when: manual
- if: $CI_PIPELINE_SOURCE == "schedule"
- when: never
variables:
SUITE: smoke
def sendReportEmail() {
def to = (params.REPORT_EMAIL_TO ?: '').trim()
if (!to) {
to = (env.REPORT_EMAIL_TO ?: '').trim()
}
def cc = (params.REPORT_EMAIL_CC ?: '').trim()
if (!cc) {
cc = (env.REPORT_EMAIL_CC ?: '').trim()
}
if (!to) {
echo '未配置 REPORT_EMAIL_TO,跳过邮件。在任务参数或环境变量里填写收件人(逗号分隔)。'
return
}
def subject = fileExists('reports/latest/email-subject.txt')
? readFile(encoding: 'UTF-8', file: 'reports/latest/email-subject.txt').trim()
: "【SPD UI自动化】${params.SUITE} ${currentBuild.currentResult} #${env.BUILD_NUMBER}"
def body = fileExists('reports/latest/email-summary.html')
? readFile(encoding: 'UTF-8', file: 'reports/latest/email-summary.html')
: "<p>构建 ${currentBuild.currentResult},未生成摘要。请打开 <a href='${env.BUILD_URL}'>Jenkins</a></p>"
try {
emailext(
to: to,
cc: cc,
subject: subject,
body: body,
mimeType: 'text/html',
charset: 'UTF-8',
attachLog: false,
recipientProviders: []
)
echo "已发送报告邮件至 ${to}"
} catch (err) {
echo "邮件发送失败。请安装 Email Extension 插件,并在 Manage Jenkins → System 配置 SMTP:${err}"
}
}
pipeline {
agent any
options {
timestamps()
disableConcurrentBuilds()
timeout(time: 6, unit: 'HOURS')
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '10'))
}
parameters {
choice(
name: 'SUITE',
choices: ['smoke', 'nightly', 'full', 'center', 'dept', 'invoice', 'trace', 'consume', 'recon', 'cost', 'warn', 'pol', 'exc', 'ana', 'ops', 'reag', 'invoice-trace', 'recon-cost', 'extended', 'quarantine'],
description: '执行套件(见 config/suites.config.mjs)'
)
string(name: 'WORKERS', defaultValue: '3', description: '单进程内并行文件数(同一 spec 不拆开)')
string(name: 'SHARDS', defaultValue: '1', description: '本机分片进程数;夜间建议 2,总浏览器约 SHARDS×WORKERS')
string(name: 'RETRIES', defaultValue: '0', description: '整条用例重试次数,默认 0;瞬时加载由操作层重试,勿全局打开')
booleanParam(name: 'RUN_FULL', defaultValue: false, description: '包含 @full 全列排序(耗时长)')
string(
name: 'REPORT_EMAIL_TO',
defaultValue: '',
description: '报告收件人,逗号分隔。留空则用任务环境变量 REPORT_EMAIL_TO;都空则不发信'
)
string(name: 'REPORT_EMAIL_CC', defaultValue: '', description: '抄送,逗号分隔,可选')
}
environment {
CI = '1'
SUITE = "${params.SUITE}"
PW_WORKERS = "${params.WORKERS}"
SHARDS = "${params.SHARDS}"
PW_RETRIES = "${params.RETRIES}"
RUN_FULL = "${params.RUN_FULL ? '1' : '0'}"
PLAYWRIGHT_BROWSERS_PATH = "${WORKSPACE}/.playwright-browsers"
// 在 Jenkins 凭据里创建 Username/Password,ID 与此一致;或在任务里覆盖
SPD_CREDENTIALS_ID = "${env.SPD_CREDENTIALS_ID ?: 'spd-ui-test-account'}"
}
stages {
stage('Prepare') {
steps {
sh '''
set -e
node -v
npm -v
test "$(node -p "process.versions.node.split('.')[0]")" -ge 20
'''
}
}
stage('Install') {
steps {
sh '''
set -e
npm ci
npx playwright install-deps chromium || true
npm run playwright:install
'''
}
}
stage('Typecheck') {
steps {
sh 'npm run typecheck'
}
}
stage('Test') {
steps {
script {
// 已注入账号,或 Agent 上有 .env 时不强制绑凭据
if (env.TEST_USER?.trim() || fileExists('.env')) {
sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
} else {
withCredentials([
usernamePassword(
credentialsId: env.SPD_CREDENTIALS_ID,
usernameVariable: 'TEST_USER',
passwordVariable: 'TEST_PASS'
)
]) {
sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
}
}
}
}
}
}
post {
always {
junit allowEmptyResults: true, testResults: 'reports/latest/junit.xml'
archiveArtifacts artifacts: 'reports/latest/**, reports/ci-*/summary.json, reports/ci-*/meta.txt', allowEmptyArchive: true, fingerprint: true
script {
if (fileExists('reports/latest/summary.json')) {
def desc = sh(
script: 'node -p "const s=require(\'./reports/latest/summary.json\'); const c=s.counts||{}; `${s.suite} shards=${s.shards} workers=${s.workers} fail=${c.failures||0}/${c.tests||0}`"',
returnStdout: true,
).trim()
currentBuild.description = desc
}
}
publishHTML([
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports/latest/playwright-report',
reportFiles: 'index.html',
reportName: 'Playwright Report'
])
script {
if (fileExists('reports/latest/allure-results')) {
try {
allure includeProperties: false, jdk: '', results: [[path: 'reports/latest/allure-results']]
} catch (err) {
echo "Allure 插件不可用,已保留 reports/latest/allure-report:${err}"
if (fileExists('reports/latest/allure-report/index.html')) {
publishHTML([
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports/latest/allure-report',
reportFiles: 'index.html',
reportName: 'Allure Report'
])
}
}
}
withEnv(["BUILD_RESULT=${currentBuild.currentResult}"]) {
sh 'node scripts/ci-email-summary.mjs || true'
}
sendReportEmail()
}
}
}
}
# MyTestUi
企业级 **Playwright + TypeScript** UI 自动化,目标系统为 SPD(FLI+ 医用耗材精益管理平台)。
## 快速开始
```bash
cp .env.example .env # 填写 TEST_USER / TEST_PASS
npm ci # postinstall 自动安装 chromium 到 .playwright-browsers/
npm run typecheck # CI 同样会跑 tsc --noEmit
npx playwright test
npx playwright test --ui
```
Chromium 缓存在项目内 `.playwright-browsers/`(已 gitignore),与 `@playwright/test` 版本绑定;升级依赖后 `npm run playwright:install` 即可。跳过安装:`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci`
默认站点:`http://spdtest.cmic.com.cn:8080`,登录页 `/spd/login`。账号只放 `.env`(已 gitignore),不要写入源码。
`login` project 不带登录态,测登录页本身;`chromium` 依赖 `auth.setup` 复用 `auth/user.json`
**登录态复用**`utils/auth-session.ts` 统一编排。`auth.setup` 与同页 `describeWithReuseOpen` 共用:token 未过期则复用 `auth/user.json`,过期或被踢回登录页则重新登录并重建上下文。需强制刷新:`FORCE_AUTH_LOGIN=1`。Jenkins / `npm run test:ci` 默认强制重新登录,避免 Agent 工作区残留旧会话。
## 套件与 Jenkins
定时执行用套件,不要按页面脚本拼命令。清单:`npm run suites`
| 套件 | 命令 | 用途 |
|------|------|------|
| `smoke` | `npm run test:ci:smoke` | 登录/首页 + 成本/开票/科室出入库 + 预警/政策/异常已打 `@smoke` 的用例 |
| `nightly` | `npm run test:ci:nightly` | 主 spec 回归,排除 `@full` 与调试 `*.stat-*.spec.ts` |
| `full` | `npm run test:ci:full` | 含全列排序,建议周末 |
| 模块 | `SUITE=center npm run test:ci` | `center` / `dept` / `warn` / `recon` 等 |
| 夜间分片 | `npm run test:spd:shard` | 先登录一次,再 2 进程分片并合并报告 |
并行默认按 **文件**`PW_WORKERS`)。不要开 `PW_FULLY_PARALLEL=1`:本仓库大量同页 serial,拆开后同一账号会互踩。总浏览器约 `SHARDS × WORKERS`,单机建议不超过 6。详见 [docs/jenkins.md](docs/jenkins.md)
产物在 `reports/latest/`:Playwright HTML、`junit.xml`、Allure results、`summary.json`。定时跑完会发 **HTML 摘要邮件**(失败列表 + Jenkins 报告链接),不要把完整报告当附件。收件人配 `REPORT_EMAIL_TO`,SMTP 与任务拆分见 [docs/jenkins.md](docs/jenkins.md)
Jenkins:仓库根目录 `Jenkinsfile`,配置说明见 [docs/jenkins.md](docs/jenkins.md)。上 Git 后建 Pipeline from SCM,凭据 ID 默认 `spd-ui-test-account`
GitLab:`.gitlab-ci.yml` 对 MR/分支跑 `npm run typecheck`。冒烟需在 GitLab CI/CD Variables 配置 `TEST_USER` / `TEST_PASS`(可选 `BASE_URL`),用 Web 流水线或定时任务触发,不要把 nightly 绑在每次推送上。
本地按页调试仍可用 `npm run test:page -- warn-low`,或原来的 `npm run test:warn-low`
## 目录结构
```text
config/ 环境、超时、浏览器、账号、CI、套件
core/ BasePage、操作重试、智能等待、失败诊断、用例编号
locators/ 定位器(只定义元素,不含操作)
pages/ Page Object(只写操作,定位器 private)
helpers/ 列设置/排序/导出等可复用控件
components/ 顶栏、下载中心
fixtures/ 把 Page 注入用例;失败挂诊断;标题提取 STAT-xxx
data/ 页面差异数据(路径、列、组合字段),不含密码
tests/spd/ 业务用例(编排 + 断言)
tests/setup/ 会话级登录态写入 auth/user.json
utils/ 登录态文件、会话编排
scripts/run-ci.mjs Jenkins / 定时执行入口
```
## 分层约定
| 层 | 允许 | 禁止 |
|----|------|------|
| `config/` | 环境、超时、套件 | 业务操作 |
| `data/` | 路径、列名、样本字段、mock 值 | 选择器、click/fill |
| `locators/` | `getByRole` / `getByPlaceholder` / `getByTestId` | click、fill、断言 |
| `core/` | 等待、操作重试、诊断、健康检查、BasePage | 具体报表选择器 |
| `pages/` | 业务操作、调用 core 等待 | 用例流程编排、裸选择器 |
| `tests/` | 调用 Page 方法 + 断言业务结果 | `page.locator(...)`、访问 `locators` |
**智能等待**:查询按钮 loading、Ant Spin、表体有行/空态,统一走 `core/smart-wait.ts`。动画沉降用 `waitUiSettle`(双 rAF),不要在 Page 里写 `waitForTimeout`
**重试策略**
- **操作层**(默认开):`BasePage``goto` / `click` / `fill` / `getText` 对点击被挡、节点卸载、导航超时等瞬时失败重试(`ACTION_RETRIES`,默认 3 次)。`expect`、健康检查、`guardedSkip` **不重试**
- **用例层**(默认关):全局 `retries=0`,Jenkins / GitHub 也不自动打开。已知瞬时 flaky 用 `testFlaky`(带 `@flaky`),断言失败仍立即失败。不要设 `PW_RETRIES` 掩盖断言失败。
**Flake 治理**:失败自动分为产品/脚本/环境/数据/瞬时,写入 `failure-context.md`。CI 累计 `reports/flake/history.jsonl``npm run flake:report` 看趋势与隔离候选。已知瞬时问题进 `config/quarantine.json`,主门禁默认隔离;`npm run test:quarantine` 单独验证。细则见 [docs/flake.md](docs/flake.md)
**数据驱动**:每个报表一份 `data/{page}.data.ts``CenterReportPageData`),Page 构造时注入;用例标题带 `STAT-xxx`,报告里自动带 `caseId`
**失败诊断**:失败时附件 `failure-context.md`(URL、iframe、接口/页面报错),HTML / Allure 可直接打开。通过用例仍做运行时健康收尾,避免接口 5xx 被当成通过。
`seed.spec.ts` 默认不执行。需要时:`npm run test:seed`。CI 默认不跑 `*.stat-*.spec.ts` 调试用例;本地按文件跑不受影响,强制纳入:`INCLUDE_DEBUG_SPECS=1`
## 新增页面清单
1. `locators/{模块}/{页面}.locator.ts`
2. `pages/{模块}/{页面}.page.ts`(定位器 `private`
3.`fixtures/index.ts``definePageFixture(XxxPage)` 注册
4. `tests/{模块}/{场景}.spec.ts` — 一条用例一个核心断言
import { devices, type PlaywrightTestConfig } from '@playwright/test';
import './load-env';
import { resolveRetries } from './ci.config';
import { timeouts } from './timeouts.config';
/** 与 playwright.config.ts 的 use 互补:截图、trace、视口。 */
export const browserUse: PlaywrightTestConfig['use'] = {
...devices['Desktop Chrome'],
launchOptions: {
...(process.env.SLOW_MO ? { slowMo: Number(process.env.SLOW_MO) } : {}),
},
screenshot: 'only-on-failure',
video: 'off',
// 默认无用例级重试,失败即留 trace;仅显式 PW_RETRIES>0 时改录首次重试
trace: resolveRetries() > 0 ? 'on-first-retry' : 'retain-on-failure',
actionTimeout: timeouts.action,
};
/**
* CI / Jenkins / GitHub 环境判定与执行参数。
* Playwright 配置与 runner 共用,避免各处各写一套。
*/
export function isGitHubActions(): boolean {
return process.env.GITHUB_ACTIONS === 'true';
}
export function isJenkins(): boolean {
return Boolean(process.env.JENKINS_URL || process.env.BUILD_NUMBER || process.env.JENKINS_HOME);
}
export function isCi(): boolean {
return process.env.CI === '1' || process.env.CI === 'true' || isJenkins() || isGitHubActions();
}
export function resolveBuildId(): string {
return (
process.env.BUILD_TAG ||
process.env.BUILD_NUMBER ||
process.env.GITHUB_RUN_ID ||
new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
);
}
/**
* 整条用例级重试:默认 0,不按 CI 自动打开。
* 瞬时加载走操作层 `retryAction`;已知 flaky 用 `testFlaky`。
* 仅当显式设置 PW_RETRIES 时才覆盖(排查用,会连断言失败一起重跑,不推荐)。
*/
export function resolveRetries(): number {
if (process.env.PW_RETRIES !== undefined && process.env.PW_RETRIES !== '') {
return Math.max(0, Number(process.env.PW_RETRIES) || 0);
}
return 0;
}
/**
* Worker:显式 PW_WORKERS > GitHub 1(小机器)> Jenkins 3 > 本地交给 Playwright。
* 共享 Agent 不要用满 CPU,避免把被测系统和节点打满。
*/
export function resolveWorkers(): number | undefined {
if (process.env.PW_WORKERS !== undefined && process.env.PW_WORKERS !== '') {
const n = Number(process.env.PW_WORKERS);
return Number.isFinite(n) && n > 0 ? n : undefined;
}
if (isGitHubActions()) return 1;
if (isJenkins()) return 3;
return undefined;
}
/**
* 默认 false:并行单位是「文件」。
* 本仓库大量 describeWithReuseOpen(serial),fullyParallel 会把同一 spec 拆到多个 worker,
* 同一测试账号同时操作同一报表,容易互踢会话。需要时设 PW_FULLY_PARALLEL=1。
*/
export function resolveFullyParallel(): boolean {
return process.env.PW_FULLY_PARALLEL === '1';
}
export function resolveMaxFailures(): number | undefined {
if (process.env.PW_MAX_FAILURES === undefined || process.env.PW_MAX_FAILURES === '') {
return undefined;
}
const n = Number(process.env.PW_MAX_FAILURES);
return Number.isFinite(n) && n > 0 ? n : undefined;
}
/** 单进程分片:PW_SHARD=1/3 或 --shard CLI(run-ci 会写入环境变量)。 */
export function resolveShard(): { current: number; total: number } | undefined {
const raw = process.env.PW_SHARD || '';
const match = raw.match(/^(\d+)\s*\/\s*(\d+)$/);
if (!match) return undefined;
const current = Number(match[1]);
const total = Number(match[2]);
if (!Number.isFinite(current) || !Number.isFinite(total) || current < 1 || total < 1 || current > total) {
return undefined;
}
return { current, total };
}
export function shouldEnableBlob(): boolean {
return process.env.PW_BLOB === '1';
}
export function resolveBlobDir(): string {
return process.env.PW_BLOB_DIR || 'blob-report';
}
export function resolveOutputDir(): string {
return process.env.PW_OUTPUT_DIR || 'test-results';
}
export function resolveHtmlDir(): string {
return process.env.PW_HTML_DIR || 'playwright-report';
}
export function resolveJunitFile(): string {
return process.env.PW_JUNIT_FILE || 'reports/junit/results.xml';
}
export function resolveAllureResultsDir(): string {
return process.env.ALLURE_RESULTS_DIR || 'allure-results';
}
export function shouldEnableAllure(): boolean {
if (process.env.ALLURE === '0') return false;
return process.env.ALLURE === '1' || isCi();
}
export function shouldEnableJunit(): boolean {
if (process.env.JUNIT === '0') return false;
return process.env.JUNIT === '1' || isCi();
}
import './load-env';
/**
* 账号从环境变量读取,禁止把真实密码写入仓库。
* 登录成功后的 storageState 放在 auth/*.json(已 gitignore)。
*/
export type Credentials = {
username: string;
password: string;
};
export const credentials: Credentials = {
username: process.env.TEST_USER ?? '',
password: process.env.TEST_PASS ?? '',
};
export function hasCredentials(): boolean {
return Boolean(credentials.username && credentials.password);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment