Skill: guolongganga/qa-engineer-assistant (version 1.0.0)

--- name: qa-engineer-assistant description: | This skill should be used when the user is a QA/test engineer needing help with any testing task. Covers the full testing workflow: understanding requirements, designing test cases, writing API automation scripts (Python/pytest/requests), writing UI automation scripts (Playwright/Selenium), generating bug reports, and providing guidance for junior testers. Trigger when the user mentions: test cases, test plan, API testing, interface testing, UI automation, bug report, regression test, test coverage, pytest, Selenium, Playwright, 测试用例, 接口测试, UI自动化, Bug报告, 测试计划, 冒烟测试, 回归测试. --- # QA Engineer Assistant Skill ## Purpose Accelerate the full testing workflow for QA engineers of all levels — from senior engineers to first-day newcomers. Covers three core domains: 1. **Functional / manual testing** — requirements analysis, test case design 2. **API / interface automation testing** — pytest + requests script generation 3. **UI automation testing** — Playwright (preferred) or Selenium script generation --- ## Core Workflow ### Phase 1 — Understand the Task When the user provides a requirement, user story, PRD excerpt, or API spec: - Identify the feature under test, key business rules, input constraints, and expected outcomes. - Proactively ask for missing information if the requirement is vague (e.g., "Is there an authentication step? What HTTP method does this endpoint use?"). - Classify the task: functional test case design / API script / UI script / bug report. ### Phase 2 — Test Case Design (Functional Testing) Load `references/test-case-template.md` for the standard test case format. Apply the following design techniques based on complexity: - **Equivalence partitioning** — group valid and invalid input ranges - **Boundary value analysis** — test at and around limits (min, max, min±1, max±1) - **Decision table** — for features with multiple condition combinations - **Error guessing** — empty input, null, special characters, extra-long strings, negative numbers Always include these test scenario categories: - Happy path (正常流程) - Edge cases (边界值) - Negative / invalid input (异常输入) - Permission / role validation (if applicable) - Data dependency scenarios (if applicable) Output format: use the test case table from `references/test-case-template.md`. Add a **coverage checklist** at the end summarizing which scenarios are covered. ### Phase 3 — API Automation Script Generation Load `references/api-test-guide.md` for conventions and patterns. When generating API test scripts: 1. Use **Python + pytest + requests** as the default stack. 2. Structure: one test file per API module, fixtures in `conftest.py`. 3. Always include: - Setup / teardown (via pytest fixtures) - Positive test (2xx response, schema validation) - Negative tests (4xx: missing required fields, invalid values, unauthorized) - Response time assertion (warn if > 2000ms) - Clear assertions with descriptive messages 4. Use `references/api-test-guide.md` for header/auth patterns and common assertion helpers. 5. Use `scripts/gen_api_test.py` to generate boilerplate when the user provides an endpoint description. Beginner-friendly output: add inline comments in Chinese explaining what each section does. ### Phase 4 — UI Automation Script Generation Default framework: **Playwright (Python)**. Fallback: Selenium + pytest if user specifies. When generating UI scripts: 1. Follow Page Object Model (POM) — separate page classes from test logic. 2. Each page class goes in `pages/`, each test file in `tests/`. 3. Always include: - Explicit waits (`page.wait_for_selector`, `expect(locator).to_be_visible()`) - Screenshot on failure - Locator priority: `data-testid` > `aria-label` > CSS > XPath 4. Add inline Chinese comments for beginners. 5. Provide a brief "how to run" block at the end of each script. ### Phase 5 — Bug Report Generation Load `references/bug-report-template.md` for the standard format. When the user describes a bug: - Fill in all fields: title, environment, severity/priority, preconditions, steps to reproduce, actual result, expected result, attachments note, root cause hypothesis. - Write the title in format: `[Module] Short description of the problem` (e.g., `[Login] 输入正确密码后提示"密码错误"`) - Severity guide: - P0/Blocker: core function unusable, data loss, security issue - P1/Critical: major feature broken, no workaround - P2/Major: feature partially broken, workaround exists - P3/Minor: cosmetic, typo, low-impact UX issue --- ## Beginner Guidance Mode When the user identifies as a newcomer, or when the task seems unfamiliar to them: - Explain **why** each step is done, not just what to do. - Define domain terms on first use (e.g., "等价类划分 (Equivalence Partitioning) 是指…"). - Suggest next steps after completing each task. - Offer a "quick start checklist" for the current task type. --- ## Output Standards - All test cases: use Markdown tables. - All scripts: use fenced code blocks with language tag (` ```python `). - All bug reports: use the template from `references/bug-report-template.md`. - Always end outputs with a **"下一步建议 (Next Steps)"** section. - Be concise but complete — avoid padding, but never omit critical test scenarios. --- ## Bundled Resources | Resource | Purpose | |---|---| | `references/test-case-template.md` | Standard test case table format + example | | `references/api-test-guide.md` | API testing conventions, auth patterns, common assertions | | `references/bug-report-template.md` | Bug report template + severity guide | | `scripts/gen_api_test.py` | CLI tool to generate pytest API test boilerplate from endpoint description |

path: scripts/gen_api_test.py
content: #!/usr/bin/env python3
"""
gen_api_test.py — 接口测试脚本生成器
用法：
    python gen_api_test.py --name "用户登录" --method POST --path "/auth/login" --auth bearer --out ./tests
    python gen_api_test.py --help
"""

import argparse
import os
import re
import sys
from datetime import date


def to_snake_case(name: str) -> str:
    """将中文或英文名称转为 snake_case 模块名"""
    # 移除非字母数字字符，空格转下划线
    s = re.sub(r'[^\w\s]', '', name)
    s = re.sub(r'\s+', '_', s.strip())
    # 如果是中文，直接用拼音首字母（简单处理）
    if re.search(r'[\u4e00-\u9fff]', s):
        # 无法转拼音时使用时间戳作为后缀
        s = f"api_{date.today().strftime('%m%d')}"
    return s.lower()


def generate_conftest() -> str:
    return '''\
import pytest
import requests

# ======================================================
# conftest.py — 全局 Fixtures
# 修改 BASE_URL、用户名、密码为你的真实环境配置
# ======================================================

BASE_URL = "https://api.example.com"  # TODO: 替换为真实 base URL


@pytest.fixture(scope="session")
def base_url():
    """返回接口基础 URL"""
    return BASE_URL


@pytest.fixture(scope="session")
def auth_token(base_url):
    """登录并获取 Bearer Token，整个测试会话只登录一次"""
    resp = requests.post(f"{base_url}/auth/login", json={
        "username": "[REDACTED_SECRET]",  # TODO: 替换为测试账号
        "password": "Test@1234"          # TODO: 替换为测试密码
    })
    assert resp.status_code == 200, f"登录失败: {resp.text}"
    [REDACTED_SECRET]).get("data", {}).get("token", "")
    assert token, "未能从响应中获取 token，请检查接口返回结构"
    return token


@pytest.fixture(scope="session")
def auth_headers(auth_token):
    """返回带 Bearer Token 的请求头"""
    return {"Authorization": f"Bearer {auth_token}"}
'''


def generate_test_file(name: str, method: str, path: str, auth: str, module: str) -> str:
    method_upper = method.upper()
    method_lower = method.lower()
    auth_fixture = ""
    auth_call = ""

    if auth == "bearer":
        auth_fixture = "auth_headers, "
        auth_call = ", headers=auth_headers"
    elif auth == "none":
        auth_fixture = ""
        auth_call = ""

    # 生成请求体示例（仅 POST/PUT/PATCH）
    body_example = ""
    body_param = ""
    if method_upper in ("POST", "PUT", "PATCH"):
        body_example = '''
        # TODO: 替换为真实的请求体参数
        payload = {
            "key": "value",
        }'''
        body_param = ", json=payload"

    return f'''\
"""
{name} — 接口自动化测试
生成日期: {date.today()}
接口: {method_upper} {path}

使用方法:
    pytest {module}.py -v
    pytest {module}.py -v --html=report.html
"""

import pytest
import requests


class Test{name.replace(" ", "").replace("-", "").replace("（", "").replace("）", "")}:
    """
    {name} 接口测试套件
    覆盖场景：正向成功 / 参数缺失 / 未授权 / 边界值
    """

    # ============================================================
    # 正向测试：期望接口正常返回
    # ============================================================
    def test_{module}_success(self, base_url, {auth_fixture}):
        """正向：请求合法参数，期望返回成功"""
        url = f"{{base_url}}{path}"{body_example}

        resp = requests.{method_lower}(url{body_param}{auth_call})

        # 1. 状态码断言
        assert resp.status_code == 200, (
            f"期望状态码 200，实际 {{resp.status_code}}\\n响应内容: {{resp.text}}"
        )

        # 2. 响应时间断言（超 2s 视为性能问题）
        elapsed = resp.elapsed.total_seconds()
        assert elapsed < 2.0, f"响应时间 {{elapsed:.2f}}s 超过 2s 阈值"

        # 3. 响应体基础校验
        body = resp.json()
        # TODO: 替换为实际的业务码字段名和成功值
        assert body.get("code") == 0, f"业务码异常: {{body}}"
        assert "data" in body, f"响应体缺少 data 字段: {{body}}"

        # 4. 字段完整性校验（TODO: 补充实际必返字段）
        # data = body["data"]
        # assert "id" in data, "响应缺少 id 字段"

    # ============================================================
    # 负向测试：未授权
    # ============================================================
    def test_{module}_unauthorized(self, base_url):
        """负向：不带 token 请求，期望返回 401"""
        url = f"{{base_url}}{path}"
        resp = requests.{method_lower}(url)
        assert resp.status_code == 401, (
            f"期望 401，实际 {{resp.status_code}}，响应: {{resp.text}}"
        )

    # ============================================================
    # 负向测试：缺少必填参数
    # ============================================================
    def test_{module}_missing_required_param(self, base_url, {auth_fixture}):
        """负向：缺少必填参数，期望返回 400"""
        url = f"{{base_url}}{path}"
        # TODO: 传入缺少必填字段的请求体
        payload = {{}}  # 空 payload 触发必填校验

        resp = requests.{method_lower}(url, json=payload{auth_call})
        assert resp.status_code == 400, (
            f"期望 400，实际 {{resp.status_code}}，响应: {{resp.text}}"
        )

    # ============================================================
    # 参数化测试：多场景批量覆盖
    # ============================================================
    @pytest.mark.parametrize("description,payload,expected_status", [
        # TODO: 根据实际接口补充参数化用例
        # ("描述",     {{"字段": "值"}},  期望状态码),
        ("空 payload", {{}},              400),
    ])
    def test_{module}_parametrize(
        self, base_url, {auth_fixture}description, payload, expected_status
    ):
        """参数化：批量覆盖多种输入场景"""
        url = f"{{base_url}}{path}"
        resp = requests.{method_lower}(url, json=payload{auth_call})
        assert resp.status_code == expected_status, (
            f"[{{description}}] 期望 {{expected_status}}，实际 {{resp.status_code}}\\n响应: {{resp.text}}"
        )
'''


def main():
    parser = argparse.ArgumentParser(
        description="生成 pytest 接口测试脚本",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例：
  python gen_api_test.py --name "用户登录" --method POST --path "/auth/login" --auth bearer --out ./tests
  python gen_api_test.py --name "获取商品列表" --method GET --path "/api/products" --auth bearer
        """
    )
    parser.add_argument("--name",   required=True,  help="接口名称（中英文均可），如：用户登录")
    parser.add_argument("--method", required=True,  choices=["GET", "POST", "PUT", "PATCH", "DELETE"],
                        help="HTTP 请求方法")
    parser.add_argument("--path",   required=True,  help="接口路径，如 /api/user/info")
    parser.add_argument("--auth",   default="bearer", choices=["bearer", "none"],
                        help="认证方式：bearer（默认）或 none")
    parser.add_argument("--out",    default="./tests", help="输出目录（默认 ./tests）")

    args = parser.parse_args()

    # 生成文件名
    module = to_snake_case(args.name)
    test_filename = f"test_{module}.py"

    # 创建输出目录
    os.makedirs(args.out, exist_ok=True)

    # 生成测试文件
    test_path = os.path.join(args.out, test_filename)
    with open(test_path, "w", encoding="utf-8") as f:
        f.write(generate_test_file(args.name, args.method, args.path, args.auth, module))
    print(f"✅ 测试文件已生成: {test_path}")

    # 如果 conftest.py 不存在，同时生成
    conftest_path = os.path.join(args.out, "conftest.py")
    if not os.path.exists(conftest_path):
        with open(conftest_path, "w", encoding="utf-8") as f:
            f.write(generate_conftest())
        print(f"✅ conftest.py 已生成: {conftest_path}")
    else:
        print(f"ℹ️  conftest.py 已存在，跳过生成: {conftest_path}")

    print()
    print("📋 下一步:")
    print(f"  1. 编辑 {test_path}，填写 TODO 注释处的实际接口参数")
    print(f"  2. 编辑 {conftest_path}，填写真实的 base_url 和登录账号")
    print(f"  3. 运行测试: pytest {args.out} -v")


if __name__ == "__main__":
    main()


path: references/api-test-guide.md
content: # 接口测试规范与常用模式

## 技术栈

- **语言**: Python 3.8+
- **测试框架**: pytest
- **HTTP 客户端**: requests
- **断言辅助**: pytest 内置 assert + jsonschema（可选）
- **报告**: pytest-html 或 allure-pytest

安装依赖：
```bash
pip install pytest requests pytest-html allure-pytest jsonschema
```

---

## 项目目录结构

```
api_tests/
├── conftest.py          # 全局 fixtures（base_url、token、session）
├── pytest.ini           # pytest 配置
├── requirements.txt
├── tests/
│   ├── test_auth.py     # 认证模块测试
│   ├── test_user.py     # 用户模块测试
│   └── test_order.py    # 订单模块测试
└── utils/
    ├── helpers.py       # 公共断言、工具函数
    └── data_factory.py  # 测试数据生成
```

---

## conftest.py 标准写法

```python
import pytest
import requests

BASE_URL = "https://api.example.com"

@pytest.fixture(scope="session")
def base_url():
    return BASE_URL

@pytest.fixture(scope="session")
def auth_token(base_url):
    """登录获取 token，整个测试会话只执行一次"""
    resp = requests.post(f"{base_url}/auth/login", json={
        "username": "[REDACTED_SECRET]",
        "password": "Test@1234"
    })
    assert resp.status_code == 200, f"登录失败: {resp.text}"
    return resp.json()["data"]["token"]

@pytest.fixture(scope="session")
def auth_headers(auth_token):
    """返回带 token 的请求头"""
    return {"Authorization": f"Bearer {auth_token}"}
```

---

## 标准测试文件结构

```python
import pytest
import requests

class TestUserAPI:
    """用户接口测试"""

    def test_get_user_info_success(self, base_url, auth_headers):
        """正向：有效 token 获取用户信息"""
        resp = requests.get(f"{base_url}/api/user/info", headers=auth_headers)

        # 状态码断言
        assert resp.status_code == 200, f"期望 200，实际 {resp.status_code}，响应: {resp.text}"

        # 响应体断言
        data = resp.json()
        assert data["code"] == 0, f"业务码错误: {data}"
        assert "userId" in data["data"], "响应缺少 userId 字段"
        assert "email" in data["data"], "响应缺少 email 字段"

        # 响应时间断言（超过 2000ms 发出警告）
        assert resp.elapsed.total_seconds() < 2, f"响应超时: {resp.elapsed.total_seconds():.2f}s"

    def test_get_user_info_unauthorized(self, base_url):
        """负向：无 token 返回 401"""
        resp = requests.get(f"{base_url}/api/user/info")
        assert resp.status_code == 401, f"期望 401，实际 {resp.status_code}"

    def test_get_user_info_invalid_token(self, base_url):
        """负向：无效 token 返回 401"""
        headers = {"Authorization": "Bearer invalid_token_here"}
        resp = requests.get(f"{base_url}/api/user/info", headers=headers)
        assert resp.status_code == 401
```

---

## 常用断言模式

```python
# 1. 状态码断言
assert resp.status_code == 200

# 2. 业务码断言（常见 code/message 结构）
body = resp.json()
assert body["code"] == 0
assert body["message"] == "success"

# 3. 字段存在性断言
assert "data" in body
assert body["data"] is not None

# 4. 字段值断言
assert body["data"]["status"] == "active"

# 5. 列表非空断言
assert len(body["data"]["list"]) > 0

# 6. 响应时间断言
assert resp.elapsed.total_seconds() < 2.0, f"响应时间 {resp.elapsed.total_seconds():.2f}s 超过 2s"

# 7. JSON Schema 校验（推荐用于关键接口）
from jsonschema import validate
schema = {
    "type": "object",
    "required": ["code", "data"],
    "properties": {
        "code": {"type": "integer"},
        "data": {"type": "object"}
    }
}
validate(instance=body, schema=schema)
```

---

## 参数化测试（数据驱动）

```python
import pytest

# 登录接口参数化测试：多组数据覆盖边界和异常
@pytest.mark.parametrize("username,password,expected_code,desc", [
    ("[REDACTED_SECRET]", "Test@1234",  200, "正确账密"),
    ("[REDACTED_SECRET]", "WrongPwd1",  401, "密码错误"),
    ("",              "Test@1234",  400, "邮箱为空"),
    ("notanemail",    "Test@1234",  400, "邮箱格式非法"),
    ("[REDACTED_SECRET]", "",           400, "密码为空"),
])
def test_login_cases(base_url, username, password, expected_code, desc):
    """参数化覆盖多种登录场景"""
    resp = requests.post(f"{base_url}/auth/login", json={
        "username": username,
        "password": password
    })
    assert resp.status_code == expected_code, f"[{desc}] 期望 {expected_code}，实际 {resp.status_code}"
```

---

## 认证方式速查

```python
# Bearer Token
headers = {"Authorization": f"Bearer {token}"}

# API Key（Header）
headers = {"X-API-Key": "your_api_key"}

# Basic Auth
from requests.auth import HTTPBasicAuth
resp = requests.get(url, auth=HTTPBasicAuth("user", "pass"))

# Cookie
session = requests.Session()
session.post(f"{base_url}/login", json={"username": "u", "password": "p"})
# 后续请求自动带 cookie
resp = session.get(f"{base_url}/api/profile")
```

---

## 运行命令

```bash
# 运行全部测试
pytest tests/ -v

# 生成 HTML 报告
pytest tests/ -v --html=report.html --self-contained-html

# 生成 Allure 报告
pytest tests/ --alluredir=./allure-results
allure serve ./allure-results

# 只运行某个模块
pytest tests/test_user.py -v

# 按标记运行（如冒烟测试）
pytest -m smoke -v
```


path: references/bug-report-template.md
content: # Bug 报告标准模板

## 标题格式

```
[模块名] 简短描述现象（动词 + 对象 + 结果）
```

**示例：**
- `[登录] 输入正确密码后提示"账号或密码错误"`
- `[订单] 下单成功后库存未扣减`
- `[用户中心] 头像上传超过 2MB 时页面白屏`

---

## Bug 报告完整模板

```markdown
## Bug 标题
[模块] 现象描述

## 基本信息
| 字段 | 内容 |
|------|------|
| **严重程度** | P0 Blocker / P1 Critical / P2 Major / P3 Minor |
| **优先级** | 高 / 中 / 低 |
| **发现版本** | v1.2.3 |
| **测试环境** | 测试环境 / 预发环境 / 生产环境 |
| **操作系统** | Windows 11 / macOS 14 / iOS 17 / Android 14 |
| **浏览器/App版本** | Chrome 123 / Safari 17 / App v2.1.0 |
| **发现人** | 张三 |
| **发现时间** | 2026-03-30 |
| **指派给** | 开发负责人姓名 |

## 前置条件
> 复现此 Bug 前必须满足的环境或数据状态

- 用户已注册，账号状态正常
- 已登录系统
- ...

## 复现步骤
1. 打开 xxx 页面（URL: https://...）
2. 执行操作 A
3. 执行操作 B
4. 观察结果

## 实际结果
> 描述实际看到的现象，要具体可观测

页面显示"账号或密码错误"弹窗，停留在登录页，未跳转。

## 预期结果
> 描述正确行为应该是什么

登录成功，跳转至首页，顶部显示用户昵称。

## 复现频率
- [x] 必现（100%）
- [ ] 偶现（约 50%）
- [ ] 难以复现（< 10%）

## 附件
- [ ] 截图（标注问题区域）
- [ ] 视频录屏
- [ ] 接口请求/响应截图（Charles / DevTools）
- [ ] 日志文件

## 根因假设（可选）
> 测试人员对问题原因的初步判断，供开发参考

可能是登录接口密码加密方式与数据库存储格式不一致。

## 备注
其他需要说明的信息。
```

---

## 严重程度（Severity）定义

| 级别 | 名称 | 定义 | 示例 |
|------|------|------|------|
| **P0** | Blocker | 核心功能完全不可用，阻塞测试，或导致数据丢失/安全漏洞 | 无法登录、支付接口报错、用户数据泄露 |
| **P1** | Critical | 主要功能严重损坏，无可接受的绕过方案 | 下单后订单状态错误、搜索功能返回空结果 |
| **P2** | Major | 功能部分损坏，存在可接受的临时绕过方案 | 筛选条件无效但可手动翻页找到数据 |
| **P3** | Minor | 界面问题、文案错误、低影响 UX 缺陷 | 按钮颜色偏差、提示语错别字 |

---

## Bug 生命周期流转

```
新建(New) → 已分配(Assigned) → 修复中(In Progress) → 待验证(Resolved)
  → 验证通过(Closed)
  → 验证不通过(Reopened) → 修复中...
  → 无法复现(Cannot Reproduce)
  → 不予修复(Won't Fix) → 关闭前需说明理由
```

---

## 好 Bug 报告的标准

1. **可复现** — 步骤精确，任何人按步骤操作都能看到同样结果
2. **一个 Bug 一个单** — 不要把多个问题混在同一条 Bug 里
3. **有对比** — 实际结果 vs 预期结果，差异清晰
4. **有证据** — 截图/录屏/日志，让开发不用猜
5. **标题明确** — 看标题就知道问题在哪、是什么现象


path: references/test-case-template.md
content: # 测试用例标准模板

## 表格格式

每次输出测试用例时，使用以下 Markdown 表格格式：

| 用例编号 | 模块 | 用例标题 | 前置条件 | 测试步骤 | 输入数据 | 预期结果 | 优先级 | 用例类型 |
|---------|------|---------|---------|---------|---------|---------|--------|---------|
| TC-001  | 登录模块 | 正确账号密码登录成功 | 用户已注册；系统正常运行 | 1. 打开登录页<br>2. 输入用户名<br>3. 输入密码<br>4. 点击登录 | 用户名: [REDACTED_SECRET]<br>密码: Test@1234 | 登录成功，跳转至首页，顶部显示用户名 | P0 | 正向用例 |

---

## 字段说明

| 字段 | 说明 |
|------|------|
| **用例编号** | 格式：`模块缩写-序号`，如 `LOGIN-001`、`ORDER-003` |
| **模块** | 所属功能模块名称 |
| **用例标题** | 一句话描述测试目的，格式：`[条件] 执行[操作] 结果[预期]` |
| **前置条件** | 执行本用例前必须满足的环境/数据条件 |
| **测试步骤** | 按顺序编号，步骤要精确可复现 |
| **输入数据** | 具体的测试数据，不能写"正确的密码"，要写真实值或说明取值规则 |
| **预期结果** | 可验证、可观测的结果描述（界面变化/返回值/数据库状态） |
| **优先级** | P0=冒烟必测 / P1=核心功能 / P2=重要 / P3=边缘场景 |
| **用例类型** | 正向用例 / 负向用例 / 边界用例 / 性能用例 / 安全用例 |

---

## 设计覆盖检查清单

生成测试用例后，附上以下覆盖情况确认：

```
## 覆盖检查清单
- [ ] 正常流程（Happy Path）
- [ ] 必填字段为空
- [ ] 字段超出最大长度限制
- [ ] 字段低于最小长度限制
- [ ] 特殊字符输入（如 `<script>`, `'`, `--`）
- [ ] 重复提交 / 幂等性
- [ ] 无权限用户访问
- [ ] 并发操作（如适用）
- [ ] 网络异常 / 超时（如适用）
- [ ] 边界值（最大值、最小值、临界值±1）
```

---

## 完整示例：用户登录功能测试用例

> 场景说明：用户名为邮箱格式，密码 8-20 位含大小写字母和数字，连续失败 5 次锁定账号 30 分钟

| 用例编号 | 模块 | 用例标题 | 前置条件 | 测试步骤 | 输入数据 | 预期结果 | 优先级 | 类型 |
|---------|------|---------|---------|---------|---------|---------|--------|------|
| LOGIN-001 | 登录 | 有效账号密码 → 登录成功 | 账号已注册且状态正常 | 1.打开登录页 2.输入邮箱 3.输入密码 4.点击登录 | 邮箱: [REDACTED_SECRET] 密码: Test@1234 | 跳转首页，显示用户昵称，token 写入 cookie | P0 | 正向 |
| LOGIN-002 | 登录 | 密码错误 → 提示错误且不登录 | 账号已注册 | 同上，密码输入错误值 | 邮箱: [REDACTED_SECRET] 密码: Wrong@123 | 停留登录页，提示"账号或密码错误"，失败次数+1 | P0 | 负向 |
| LOGIN-003 | 登录 | 邮箱格式非法 → 前端校验拦截 | 无 | 输入非邮箱格式用户名 | 邮箱: notanemail 密码: Test@1234 | 邮箱输入框下方显示"请输入有效邮箱"，不触发接口请求 | P1 | 负向 |
| LOGIN-004 | 登录 | 邮箱为空 → 不可提交 | 无 | 邮箱留空，密码填写，点击登录 | 邮箱: (空) 密码: Test@1234 | 登录按钮置灰 或 提示"邮箱不能为空" | P1 | 负向 |
| LOGIN-005 | 登录 | 密码最短边界值 8 位 → 登录成功 | 账号密码为 8 位 | 正常登录操作 | 邮箱: [REDACTED_SECRET] 密码: Aa123456 | 登录成功 | P2 | 边界 |
| LOGIN-006 | 登录 | 密码 7 位（低于最小长度）→ 校验拦截 | 无 | 输入 7 位密码 | 密码: Aa12345 | 提示"密码长度不符合要求" | P2 | 边界 |
| LOGIN-007 | 登录 | 密码最长边界值 20 位 → 登录成功 | 账号密码为 20 位 | 正常登录操作 | 密码: Aa123456789012345678 | 登录成功 | P2 | 边界 |
| LOGIN-008 | 登录 | 密码 21 位（超出最大长度）→ 校验拦截 | 无 | 输入 21 位密码 | 密码: Aa1234567890123456789 | 提示"密码长度不符合要求" | P2 | 边界 |
| LOGIN-009 | 登录 | 连续失败 5 次 → 账号锁定 | 账号正常，已失败 4 次 | 再次输入错误密码 | 密码: WrongPwd1 | 提示"账号已锁定，请 30 分钟后重试"，后续正确密码也无法登录 | P1 | 负向 |
| LOGIN-010 | 登录 | SQL 注入尝试 → 安全拦截 | 无 | 邮箱字段输入注入语句 | 邮箱: `' OR 1=1--` | 提示格式错误或返回 400，不返回任何用户数据 | P1 | 安全 |


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)