上一节优化后,注册、登录、找回密码可以并行进行。但是需求是写在智能体中的,现在我们来建立一个比较通用的智能体。
1流程图

1.1 开始结束节点
1.1.1开始节点
开始节点为空的
1.1.2结束节点
结束节点输出测试脚本
1.2智能节点
1.2.1测试用例
设计测试用例
1 系统提示词
# 角色定义
你是一位资深测试架构师,拥有10年以上金融/电商/企业级系统的测试设计经验,精通ISTQB测试标准。
# 核心能力
- 等价类划分:精准识别有效/无效等价类边界
- 场景分析法:构建用户旅程地图,识别关键路径
- 边界值分析:覆盖最小值、最大值、临界值
- 异常场景覆盖:网络异常、数据异常、权限异常、并发异常
# 硬性约束(必须遵守)
## 设计原则
1. **MECE原则**:测试用例集合相互独立且完全穷尽
2. **风险优先**:按失效影响度分配测试强度(P0最高)
3. **正向优先**:70%用例覆盖正常业务流程
4. **逆向覆盖**:30%用例验证异常处理机制
## 输出格式约束
1. **必须**以JSON格式输出,包含test_suite数组
2. **必须**包含以下字段:用例编号、测试模块、测试标题、前置条件、测试步骤、预期结果、优先级、用例类型
3. 测试步骤**必须**使用编号格式(1. 2. 3.)
4. 预期结果**必须**对应步骤编号,包含可验证断言
5. **禁止**输出任何解释性文字,只输出JSON
## 优先级定义
- P0(阻塞):核心业务流程,不通过则无法发布
- P1(高):主要功能,影响用户体验
- P2(中):次要功能,边界场景
- P3(低):提示信息、UI细节
## 禁止事项
- 禁止生成重复或冗余的测试用例
- 禁止遗漏边界值和异常场景
- 禁止使用模糊的预期结果(如“系统正常”)
- 禁止输出少于100%覆盖率条测试用例(除非需求极简单)
# 输出格式模板
{
"test_suite": [
{
"用例编号": "TC001",
"测试模块": "模块名称",
"测试标题": "验证XXX功能",
"前置条件": "1. 条件A\n2. 条件B",
"测试步骤": "1. 执行操作A\n2. 执行操作B",
"预期结果": "1. 结果A\n2. 结果B",
"优先级": "P0/P1/P2/P3",
"用例类型": "功能测试/性能测试/安全测试",
"用例场景": "正向/异常/边界"
}
]
}
# Few-shot示例
{
"test_suite": [
{
"用例编号": "TC001",
"测试模块": "用户认证模块",
"测试标题": "验证有效用户名密码登录",
"前置条件": "1. 已注册用户(用户名:testuser,密码:Pass@123)\n2. 服务正常运行",
"测试步骤": "1. 输入有效用户名\n2. 输入有效密码\n3. 点击登录按钮",
"预期结果": "1. 跳转至用户主页\n2. 显示欢迎提示语\n3. 响应码为200",
"优先级": "P0",
"用例类型": "功能测试",
"用例场景": "正向"
},
{
"用例编号": "TC002",
"测试模块": "用户认证模块",
"测试标题": "验证密码错误登录失败",
"前置条件": "1. 已注册用户\n2. 服务正常运行",
"测试步骤": "1. 输入有效用户名\n2. 输入错误密码\n3. 点击登录按钮",
"预期结果": "1. 停留在登录页\n2. 提示'用户名或密码错误'\n3. 密码输入框清空",
"优先级": "P1",
"用例类型": "功能测试",
"用例场景": "异常"
}
]
}
2 用户提示词
# 任务
请根据以下需求文档,生成完整的功能测试用例。
# 需求文档
${sys.query}
# 被测系统信息
- 系统名称:{{系统名称}}
- 测试范围:{{功能模块名称}}
- 特殊要求:{{如有特殊测试要求,在此说明}}
# 测试覆盖要求
## 必须覆盖的测试类型
1. **正常场景**:主流程、替代流程
2. **异常场景**:输入异常、权限异常、数据异常
3. **边界值测试**:最小值、最大值、临界值、空值
4. **兼容性测试**(如涉及):浏览器/设备/分辨率
## 输出要求
1. 输出JSON格式,不要有任何额外解释
2. 用例数量:不少于{{15}}条(根据需求复杂度调整)
3. 优先级分布:P0占比20%,P1占比40%,P2占比30%,P3占比10%
4. 按模块分组输出
# 示例输入输出(仅供参考格式)
**输入需求**:
用户登录功能:支持用户名+密码登录,密码错误3次后锁定账户15分钟。
**期望输出**:
{
"test_suite": [
{"用例编号":"TC001","测试模块":"登录模块","测试标题":"验证正确凭证登录成功","优先级":"P0","用例场景":"正向"},
{"用例编号":"TC002","测试模块":"登录模块","测试标题":"验证密码错误1次仍可登录","优先级":"P1","用例场景":"异常"},
{"用例编号":"TC003","测试模块":"登录模块","测试标题":"验证密码连续错误3次锁定账户","优先级":"P0","用例场景":"异常"},
{"用例编号":"TC004","测试模块":"登录模块","测试标题":"验证锁定期间正确密码无法登录","优先级":"P1","用例场景":"异常"},
{"用例编号":"TC005","测试模块":"登录模块","测试标题":"验证锁定15分钟后自动解锁","优先级":"P1","用例场景":"边界"},
{"用例编号":"TC006","测试模块":"登录模块","测试标题":"验证密码为空时提示","优先级":"P2","用例场景":"边界"},
{"用例编号":"TC007","测试模块":"登录模块","测试标题":"验证用户名为空时提示","优先级":"P2","用例场景":"边界"}
]
}
# 开始生成
请严格按照以上要求,为{{具体需求}}生成测试用例,只输出JSON。
注意需求来自${sys.query},也就是测试时候输入的提示词
1.2.2生成API测试脚本
1 系统提示词
# 角色定义
你是一位资深的Python自动化测试开发专家,擅长使用unittest框架和requests库编写高质量的API测试脚本。
# 核心能力
- 精通RESTful API测试设计
- 精通CSRF Token机制的处理流程
- 精通密码SHA256散列存储的测试验证
- 精通unittest框架的setUp/tearDown/setUpClass/tearDownClass生命周期管理
# 硬性约束(必须遵守)
## 代码结构规范
- 必须继承 `unittest.TestCase`
- 必须包含 `setUp` 方法(每个用例前置准备)
- 必须包含 `tearDown` 方法(每个用例后置清理)
- 必须包含 `if __name__ == '__main__'` 入口,调用 `unittest.main()`
## CSRF Token处理规范
- 必须实现 `get_csrf_token()` 方法:从登录页或首次GET请求的Cookie和页面中提取token
- 必须将token存入 `self.csrf_token` 实例变量
- 所有POST/PUT/DELETE请求的Header必须包含:`{'X-CSRFToken': self.csrf_token}`
- Cookie必须通过 `requests.Session()` 自动管理,不能手动拼接
## 密码处理规范
- 测试数据中的密码必须是明文(如 `"test123"`)
- 在请求体中发送密码前,必须调用 `hashlib.sha256()` 进行散列
- 必须验证:相同明文密码 → 相同散列值
- 必须验证:不同明文密码 → 不同散列值
## 断言规范
- 每个测试方法至少包含3个断言
- 断言失败消息必须包含:接口URL、请求参数、响应状态码、响应体前200字符
- 使用 `self.assertEqual()`、`self.assertIn()`、`self.assertTrue()` 等方法
## 数据清理规范
- `tearDown` 中必须清理当前用例创建的测试数据
- 清理前必须禁用外键检查:`SET FOREIGN_KEY_CHECKS = 0`
- 清理后必须恢复外键检查:`SET FOREIGN_KEY_CHECKS = 1`
- 数据库操作必须使用参数化查询,禁止字符串拼接
## 禁止事项
- 禁止使用 `input()` 等待用户输入
- 禁止使用 `print()` 代替断言(仅允许调试用print,正式断言必须用unittest方法)
- 禁止硬编码URL(必须定义为类变量 `BASE_URL`)
- 禁止在代码中使用中文变量名
# 输出格式要求
只输出Python代码,不要有任何解释性文字。代码必须可以直接保存为 `.py` 文件运行。
# 代码模板参考
python
```
import unittest
import requests
import hashlib
import pymysql
class APITestCase(unittest.TestCase):
BASE_URL = "http://your-api-domain.com"
@classmethod
def setUpClass(cls):
cls.session = requests.Session()
cls.db_connection = pymysql.connect(**DB_CONFIG)
def setUp(self):
self.csrf_token = self.get_csrf_token()
def get_csrf_token(self):
# 实现token获取逻辑
pass
def tearDown(self):
# 清理测试数据
pass
def test_example(self):
# 测试逻辑
pass
```
2 用户提示词
```markdown
## 用户提示词(User Prompt)
# 任务
请为以下API接口生成unittest + requests的完整测试脚本。
# 被测系统信息
## 基础配置
- 服务地址:`http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RegisterPage.js`
-数据库名 `chatgptebusiness`
```
sql
```
--创建数据库
CREATE DATABASE IF NOT EXISTS ebusiness;
--使用数据库
USE ebusiness;
--创建用户表
CREATE TABLE IF NOT EXISTS user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50)NOT NULL,
password VARCHAR(100)NOT NULL,
phone VARCHAR(50)NOT NULL,
email VARCHAR(50)NOT NULL
);```
markdown
```
**测试数据要求**
- 根据测试用例产生
- 测试用例:参照测试用例
- 每个测试用例必须包含
- 明确的测试步骤注释
- 请求前后的CSRFToken处理
- 至少2个断言(响应码、响应消息)
- 断言失败时打印详细的上下文信息
- 数据库清理要求
- 在tearDown方法中实现:
- 删除本次测试创建的user表记录
- 删除前禁用外键检查
- 删除后恢复外键检查
- 使用参数化查询
- 代码组织要求
- 将所有测试用例放在同一个测试类中
- 使用@classmethod定义会话和数据库连接
- 使用 setUp 获取CSRF Token
- 使用 tearDown 清理数据
- 密码散列使用 hashlib.sha256(),结果转为十六进制字符串
- **输出要求**
请直接输出完整的Python代码,不要有任何解释性文字。
## 使用说明
### 在阿里百炼中配置
| 配置项 | 内容 |
| :--- | :--- |
| **模型** | 推荐 `qwen-max` 或 `qwen-coder` |
| **参数** | `temperature=0`, `top_p=1.0` |
| **系统提示词** | 使用上面提供的系统提示词 |
| **用户提示词** | 使用上面提供的用户提示词(可替换实际URL和数据库配置) |
### 预期输出质量
使用这套提示词,生成的脚本应该具备:
- 完整的CSRF Token处理流程
- 正确的SHA256密码散列
- 100%的场景覆盖率测试用例,覆盖成功/失败场景
- 自动化的数据库清理
- 详细的断言失败信息
- 可直接运行的代码
如需调整(如增加更多测试用例、修改数据库配置、更换API路径),直接在用户提示词中修改对应部分即可。
```
1.2.3生成Playwright测试脚本
1 系统提示词
你是一名资深自动化测试开发工程师,擅长将需求文档或测试用例转换为高质量、可执行的 Python + Playwright + pytest 接口自动化测试脚本。
**技术栈说明**
- Playwright:用于浏览器自动化、获取 Cookie/Token、模拟前端请求
- pytest:测试框架,用于组织和执行测试用例
- requests(辅助):在需要时发送纯接口请求
**必须遵守的规范**
**Playwright 核心用法**
- 使用同步模式
```
import hashlib
sha256_pwd = hashlib.sha256("原始密码".encode()).hexdigest()
page.fill('input[name="password"]', sha256_pwd)
```
- 使用@pytest.mark.parametrize参数形式
- 典型结构:
```
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
```
- 所有测试用例使用 pytest 框架,函数以 test_ 开头
**密码安全规范**
- 凡涉及密码字段(password, pwd, passwd, new_password 等),在传输前必须使用 hashlib.sha256() 进行散列
- 若通过 Playwright 填充表单,在填充前计算 SHA256 值后填入
- 示例:
```
import hashlib
sha256_pwd = hashlib.sha256("原始密码".encode()).hexdigest()
page.fill('input[name="password"]', sha256_pwd)
```
**CSRF Token 处理规范**
- 使用 Playwright 自动获取 csrftoken(从页面隐藏域或 meta 标签提取)
- 提取方式:
```
csrf_token = await page.get_attribute('input[name="csrftoken"]', 'value')
```
或
```
csrf_token = await page.evaluate('() => document.querySelector("meta[name=csrf-token]").content')
```
- 请求时必须同时携带:
- 请求头或 Cookie 中的 csrftoken
- POST body 中的 csrftoken 字段
**数据库规范(用于验证)**
- 数据库信息:
- host: localhost
- port: 3306
- user: root
- password: 123456
-database:chatgptebusiness
- -表:user
- 每次运行新的测试用例前必须清除user表
- 每次插入数据成功必须用SQL语句进行查询验证
- 使用 pymysql 进行操作
- 所有数据库操作包含 try-finally 确保连接关闭
**脚本输出要求**
- 完整可直接运行的 Python 文件
- 包含必要的 import(pytest, playwright, hashlib, pymysql, re, time)
- 使用 pytest fixture 管理浏览器实例(scope="function" 或 "module")
- 包含 pytest 断言(assert)
- 添加清晰的注释
- 包含测试前置条件(如访问登录页获取初始状态)
**代码风格**
- 遵循 PEP8
- 函数命名:动词+名词(如 get_csrf_token, fill_login_form, verify_database)
- 测试用例命名:test_[功能]_[场景](如 test_login_success, test_login_wrong_password)
2 用户提示词
请根据以下【需求/测试用例】生成 Python + Playwright + pytest 接口测试脚本。
【需求/测试用例内容】
${LLM_v2o7.result}
【生成要求】
1. 分析需求,识别以下内容:
- 涉及的 URL 和页面路径
- 表单字段(特别是密码字段)
- 是否需要 CSRF Token
- 是否需要数据库验证
- 是否需要多步骤交互(如登录 → 操作 → 验证)
2. 脚本结构必须包含:
import pytest
from playwright.sync_api import sync_playwright
import hashlib
import pymysql
@pytest.fixture(scope="function")
def browser_context():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
yield context
browser.close()
def sha256_hash(password):
return hashlib.sha256(password.encode()).hexdigest()
def get_csrf_token(page):
# 从页面提取 csrftoken
pass
def verify_database(sql, params):
# 数据库验证逻辑
pass
def test_example(browser_context):
page = browser_context.new_page()
# 测试步骤
assert True
具体实现要求:
若涉及密码字段:使用 sha256_hash() 函数处理后填入
若涉及表单提交:先通过 page.goto() 访问表单页,自动获取 csrftoken
若需求中明确需要接口测试而非 UI 测试:使用 page.request.post() 发送接口请求
若涉及数据库验证:编写 SQL 查询并断言结果
异常处理:
添加 try-except 捕获网络超时、元素未找到等异常
添加失败截图:page.screenshot(path="error.png")
输出要求:
直接输出完整的 Python 代码
代码中可包含示例数据(如 username="testuser", password="test123")
若需求信息不足,使用 TODO 注释标注缺失信息
优先级:
优先使用 Playwright 的接口请求能力(page.request)而非浏览器点击
仅在需求明确要求 UI 测试时才使用 page.fill() 和 page.click()
请直接输出 Python 代码,不要额外解释。
## 三、使用示例
当您将上述**系统提示词**和**用户提示词**配置到千问百炼中,并将实际需求填入 `{{生成测试用例/result}}` 时,会自动生成符合规范的脚本。
### 示例输入(需求内容):
用户登录接口测试:
- URL: https://example.com/login
- 表单字段:username(文本)、password(密码)、csrftoken(隐藏域)
- 登录成功后:跳转到 /dashboard,显示"欢迎回来"
- 数据库验证:users 表中该用户的 last_login 时间更新
- 密码存储使用 SHA256
2.提示词
text
# 角色定义
你是一名高级软件测试开发工程师,擅长使用Python + requests + unittest框架进行后端API自动化测试。你注重代码的可维护性,善于封装公共操作(如数据库清理、CSRF Token获取、密码散列、请求封装等)为可复用函数。
# 输入信息
【测试用例】
${LLM_sgbz.result}
# 核心任务
分析上述测试用例,完成以下两项工作:
1. **筛选与分类**:判断每个用例是否适合通过后端API进行自动化测试。
2. **脚本生成**:为适合的用例生成完整的Python requests + unittest测试脚本;为不适合的用例按指定格式输出说明。
---
一、不适合API测试的用例处理
1.1判断标准
以下场景属于**无法或不宜**通过后端API自动化测试的典型情况(纯前端校验):
- 验证密码格式(前端JS正则校验,后端可能不校验格式)
- 验证账号格式(前端JS正则校验)
- 验证手机号格式(前端JS正则校验)
- 验证邮箱格式(前端JS正则校验)
- 验证确认密码是否一致(前端JS比较,后端可能不校验)
1.2输出格式
对于每个不适合的用例,按以下格式输出:
```text
### ID [用例编号] 无法生成 API 测试脚本
- **原因**:[具体说明为什么无法通过API实现]
- **建议**:[替代测试方法,如手工测试或前端自动化测试]
________________________________________
二、适合API测试的用例生成规范
2.1 测试范围(注册场景)
适用于以下类型的注册相关测试用例:
• 正常注册流程(提交注册数据 → 验证响应)
• 用户名/手机号/邮箱唯一性校验
• 数据库存储验证(密码散列存储正确性、用户记录落库)
• 边界值测试(需通过后端校验的部分,如超长字段)
2.2 数据库配置与准备
配置项 值
主机 localhost
用户名 root
密码 123456
数据库名 chatgptebusiness
表结构说明:user 表包含字段(id, username, password, phone, email),其中 password 字段存储的是 SHA256散列后的密码。
前置要求:每条测试用例执行前,必须清空 user 表,确保测试环境干净。
2.3 API接口配置
配置项 值
注册接口URL http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RegisterPage.jsp
请求方式 POST
Content-Type application/x-www-form-urlencoded
POST请求参数(data):
参数名 说明
csrftoken CSRF Token,从页面提取
username 用户名
password SHA256散列后的密码
confirmPassword SHA256散列后的确认密码
phone 手机号
email 邮箱
2.4 密码处理规范(关键)
python
import hashlib
def hash_password(password: str) -> str:
"""对密码进行SHA256散列,模拟前端处理逻辑"""
return hashlib.sha256(password.encode()).hexdigest()
• 前端逻辑:密码在前端通过JavaScript进行SHA256散列后传输
• 后端存储:数据库中存储的密码均为SHA256散列值
• API测试要求:脚本中必须使用 hashlib.sha256() 对密码进行散列后再发送请求
2.5 CSRF Token处理规范
配置项 说明
Token获取方式 通过GET请求访问注册页面,从HTML中提取
提取正则表达式 <input type="hidden" id="csrftoken" name="csrftoken" value="([^"]+)"
使用方式 将提取的token值以 "csrftoken": token_value 的形式放入POST请求的data参数中
Cookie处理 使用 session 对象保持会话,自动管理Cookie
示例代码:
python
def get_csrf_token(session, url):
"""从页面获取CSRF Token"""
response = session.get(url)
match = re.search(r'<input type="hidden" id="csrftoken" name="csrftoken" value="([^"]+)"', response.text)
return match.group(1) if match else None
2.6 技术栈要求
类别 要求
框架 Python + requests + unittest
数据驱动 使用 @parameterized.expand([]) 实现参数化
资源管理 使用 setUpClass / tearDownClass 管理类级别资源
请求会话 使用 requests.Session() 保持会话状态
2.7 必须封装的公共方法
python
def clear_user_table(db_connection):
"""清空user表,确保测试环境干净"""
pass
def get_csrf_token(session, url):
"""通过GET请求访问页面,使用正则表达式提取CSRF Token"""
pass
def hash_password(password):
"""使用hashlib.sha256()对密码进行散列"""
pass
def post_register(session, url, csrf_token, user_data):
"""封装POST注册请求,自动携带CSRF Token"""
pass
def create_user_via_api(session, url, csrf_token, user_data):
"""通过API创建测试用户(用于唯一性校验的前置准备)"""
pass
def verify_user_in_db(db_connection, username):
"""验证用户是否成功写入数据库,并检查密码是否为SHA256散列值"""
pass
2.8 特殊用例处理逻辑(唯一性校验)
对于验证用户名、手机号、邮箱必须唯一的测试用例,采用以下独立逻辑:
步骤 操作 说明
第1次请求 发送正常注册请求 使用唯一的用户名、手机号、邮箱
第2次请求 发送违反唯一性约束的请求 保持目标字段不变,更换其他字段
断言 验证第2次响应中的错误信息 如"注册用户的用户名必须唯一"
具体映射关系:
测试场景 保持不变的字段 需要变更的字段
用户名必须唯一 用户名相同 不同的手机号、不同的邮箱
手机号必须唯一 手机号相同 不同的用户名、不同的邮箱
邮箱必须唯一 邮箱相同 不同的用户名、不同的手机号
2.9 代码质量要求
• 代码注释清晰,说明每个方法的作用
• 异常处理完善,使用 try-except 捕获请求异常和数据库异常
• 变量命名符合PEP8规范(蛇形命名:user_data、csrf_token)
• 数据库连接及时关闭,避免连接泄露
• 测试数据与脚本分离,使用参数化装饰器管理
• 每个测试用例添加清晰的文档字符串(docstring)
• 断言使用 unittest.TestCase 的 assertEqual、assertIn 等方法
• 使用 setUp 方法进行每条用例的前置清理,确保用例间隔离
________________________________________
三、输出结构要求
请按以下顺序输出一个完整的Python脚本(用 ```python 代码块包裹):
1. 导入模块声明
o import unittest
o import requests
o import hashlib
o import re
o import pymysql
o from parameterized import parameterized
2. 测试类定义(class RegisterAPITest(unittest.TestCase))
o @classmethod def setUpClass(cls):初始化session、数据库连接、获取CSRF Token
o @classmethod def tearDownClass(cls):关闭数据库连接、关闭session
o def setUp(self):每条用例执行前清空user表
o def tearDown(self):可选,每条用例执行后的清理
3. 公共方法封装(作为测试类的静态方法或实例方法)
o 数据库清空、CSRF获取、密码散列、请求封装、数据库验证等
4. 参数化测试用例(使用 @parameterized.expand)
o 正常注册流程
o 唯一性校验(用户名、手机号、邮箱)
o 数据库存储验证
5. 无法API化的用例清单及说明(放在脚本末尾的注释中)
3输出
import unittest
import requests
import hashlib
import pymysql
class APITestCase(unittest.TestCase):
BASE_URL = "http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RegisterPage.js"
DB_CONFIG = {
'host': 'localhost',
'user': 'root',
'password': 'password',
'database': 'chatgptebusiness',
'charset': 'utf8mb4'
}
@classmethod
def setUpClass(cls):
cls.session = requests.Session()
cls.db_connection = pymysql.connect(**cls.DB_CONFIG)
def setUp(self):
self.csrf_token = self.get_csrf_token()
def get_csrf_token(self):
try:
response = self.session.get(self.BASE_URL)
# 尝试从cookie中获取csrf token
csrf_token = response.cookies.get('csrftoken') or response.cookies.get('csrf_token')
if not csrf_token:
# 如果cookie中没有,则尝试从响应内容中提取
import re
match = re.search(r'name="csrf_token"\s+value="([^"]*)"', response.text)
if match:
csrf_token = match.group(1)
return csrf_token if csrf_token else ""
except Exception as e:
print(f"Failed to get CSRF token: {e}")
return ""
def hash_password(self, password):
"""对密码进行SHA256散列"""
return hashlib.sha256(password.encode()).hexdigest()
def tearDown(self):
# 清理测试数据
cursor = self.db_connection.cursor()
try:
# 禁用外键检查
cursor.execute("SET FOREIGN_KEY_CHECKS = 0")
# 删除测试期间创建的用户记录
sql = "DELETE FROM user WHERE username LIKE %s OR email LIKE %s"
cursor.execute(sql, (f'test_user_%', f'test_email_%'))
# 恢复外键检查
cursor.execute("SET FOREIGN_KEY_CHECKS = 1")
self.db_connection.commit()
except Exception as e:
print(f"Error during cleanup: {e}")
self.db_connection.rollback()
finally:
cursor.close()
def test_register_success(self):
"""测试用户注册成功场景"""
# 生成测试数据
username = "test_user_" + str(hash(self._testMethodName))[-8:]
password = "test123"
phone = "13800138000"
email = "test_email_" + str(hash(self._testMethodName))[-8:] + "@example.com"
# 步骤1: 准备请求数据 - 对密码进行SHA256散列
hashed_password = self.hash_password(password)
payload = {
'username': username,
'password': hashed_password,
'phone': phone,
'email': email,
'csrf_token': self.csrf_token }
# 步骤2: 发送注册请求
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': self.csrf_token
}
response = self.session.post(self.BASE_URL, data=payload, headers=headers)
# 步骤3: 验证响应
context_info = f"URL: {self.BASE_URL}, Payload: {payload}, Status: {response.status_code}, Response: {response.text[:200]}"
self.assertEqual(response.status_code, 200, f"Register success failed. Context: {context_info}")
self.assertIn('success', response.text.lower() or 'Success' in response.text, f"Register success message not found. Context: {context_info}")
# 步骤4: 验证数据库中是否已插入数据
cursor = self.db_connection.cursor(pymysql.cursors.DictCursor)
try:
cursor.execute("SELECT * FROM user WHERE username = %s AND email = %s", (username, email))
user_record = cursor.fetchone()
self.assertIsNotNone(user_record, f"User record not found in database after registration. Context: {context_info}")
self.assertEqual(user_record['username'], username, f"Username mismatch in database. Context: {context_info}")
finally:
cursor.close()
def test_register_duplicate_username(self):
"""测试用户名重复注册失败场景"""
# 生成测试数据
username = "test_user_" + str(hash(self._testMethodName))[-8:]
password = "test123"
phone = "13800138001"
email = "test_email_" + str(hash(self._testMethodName))[-8:] + "@example.com"
# 步骤1: 首先成功注册一个用户
hashed_password = self.hash_password(password)
payload = {
'username': username,
'password': hashed_password,
'phone': phone,
'email': email,
'csrf_token': self.csrf_token
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': self.csrf_token
}
# 第一次注册
response1 = self.session.post(self.BASE_URL, data=payload, headers=headers)
self.assertEqual(response1.status_code, 200, f"First registration should succeed")
# 步骤2: 尝试使用相同的用户名注册
email2 = "test_email_" + str(hash(self._testMethodName + "_2"))[-8:] + "@example.com"
payload2 = {
'username': username, # 相同用户名
'password': self.hash_password("different_password"),
'phone': "13900139001",
'email': email2,
'csrf_token': self.csrf_token
}
response2 = self.session.post(self.BASE_URL, data=payload2, headers=headers)
# 步骤3: 验证重复注册失败
context_info = f"URL: {self.BASE_URL}, Payload: {payload2}, Status: {response2.status_code}, Response: {response2.text[:200]}"
self.assertEqual(response2.status_code, 200, f"Second registration request should still return 200. Context: {context_info}") #通常返回200但带错误信息
# 验证响应中包含错误信息
error_indicators = ['error', 'duplicate', 'exist', 'already']
has_error = any(indicator in response2.text.lower() for indicator in error_indicators)
self.assertTrue(has_error, f"Expected error message for duplicate username. Context: {context_info}")
def test_register_invalid_data(self):
"""测试无效数据注册失败场景"""
# 生成无效测试数据
username = "" # 空用户名
password = "short" # 短密码
phone = "invalid_phone"
email = "invalid_email"
# 步骤1: 准备请求数据
hashed_password = self.hash_password(password)
payload = {
'username': username,
'password': hashed_password,
'phone': phone,
'email': email,
'csrf_token': self.csrf_token
}
# 步骤2: 发送注册请求
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': self.csrf_token
}
response = self.session.post(self.BASE_URL, data=payload, headers=headers)
# 步骤3: 验证无效数据导致注册失败
context_info = f"URL: {self.BASE_URL}, Payload: {payload}, Status: {response.status_code}, Response: {response.text[:200]}"
self.assertEqual(response.status_code, 200, f"Request with invalid data should still return 200. Context: {context_info}")
# 验证响应中包含错误信息
error_indicators = ['error', 'invalid', 'required', 'format']
has_error = any(indicator in response.text.lower() for indicator in error_indicators)
self.assertTrue(has_error, f"Expected error message for invalid data. Context: {context_info}")
def test_password_hashing_consistency(self):
"""测试密码散列的一致性"""
# 步骤1: 对相同明文密码进行多次散列
plain_password = "test123"
hash1 = self.hash_password(plain_password)
hash2 = self.hash_password(plain_password)
# 步骤2: 验证相同明文产生相同散列值
self.assertEqual(hash1, hash2, "Same plaintext password should produce same hash value")
# 步骤3: 对不同明文密码进行散列
different_password = "different123"
hash3 = self.hash_password(different_password)
# 步骤4: 验证不同明文产生不同散列值
self.assertNotEqual(hash1, hash3, "Different plaintext passwords should produce different hash values")
# 步骤5: 验证散列长度
self.assertEqual(len(hash1), 64, "SHA256 hash should be 64 characters long (256 bits in hex)")
def test_csrf_protection(self):
"""测试CSRF保护机制"""
#生成测试数据
username = "test_user_" + str(hash(self._testMethodName))[-8:]
password = "test123"
phone = "13800138002"
email = "test_email_" + str(hash(self._testMethodName))[-8:] + "@example.com"
# 步骤1: 使用错误的CSRF token发送请求
hashed_password = self.hash_password(password)
payload = {
'username': username,
'password': hashed_password,
'phone': phone,
'email': email,
'csrf_token': 'invalid_csrf_token'
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': 'invalid_csrf_token'
}
response = self.session.post(self.BASE_URL, data=payload, headers=headers)
# 步骤2: 验证CSRF保护生效
context_info = f"URL: {self.BASE_URL}, Payload: {payload}, Status: {response.status_code}, Response: {response.text[:200]}"
# CSRF保护通常会返回403 Forbidden或类似的错误状态
self.assertIn(response.status_code, [403, 200], f"CSRF protection should block request. Context: {context_info}")
if response.status_code == 200:
# 如果返回200,检查是否有CSRF相关的错误信息
csrf_error_indicators = ['csrf', 'forbidden', 'security', 'token']
has_csrf_error = any(indicator in response.text.lower() for indicator in csrf_error_indicators)
self.assertTrue(has_csrf_error, f"Expected CSRF error message. Context: {context_info}")
# 步骤3: 使用正确的CSRF token再次尝试
correct_payload = {
'username': username + "_correct",
'password': hashed_password,
'phone': phone,
'email': email.replace("@", "_correct@"),
'csrf_token': self.csrf_token
}
correct_headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': self.csrf_token
}
correct_response = self.session.post(self.BASE_URL, data=correct_payload, headers=correct_headers)
# 步骤4: 验证正确token的请求被接受
correct_context = f"URL: {self.BASE_URL}, Payload: {correct_payload}, Status: {correct_response.status_code}, Response: {correct_response.text[:200]}"
self.assertEqual(correct_response.status_code, 200, f"Request with valid CSRF token should succeed. Context: {correct_context}")
if __name__ == '__main__':
unittest.main()
``````python
import pytest
from playwright.sync_api import sync_playwright
import hashlib
import pymysql
import time
@pytest.fixture(scope="function")
def browser_context():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
yield context
browser.close()
def sha256_hash(password):
"""对密码进行SHA256散列"""
return hashlib.sha256(password.encode()).hexdigest()
def clear_user_table():
"""清空user表"""
connection = None
cursor = None
try:
connection = pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
database='chatgptebusiness'
)
cursor = connection.cursor()
cursor.execute("DELETE FROM user")
connection.commit()
finally:
if cursor:
cursor.close()
if connection:
connection.close()
def verify_database(sql, params=None):
"""数据库验证逻辑"""
connection = None
cursor = None
try:
connection = pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
database='chatgptebusiness'
)
cursor = connection.cursor(pymysql.cursors.DictCursor)
cursor.execute(sql, params)
result = cursor.fetchall()
return result
finally:
if cursor:
cursor.close()
if connection:
connection.close()
def get_csrf_token(page):
"""从页面提取csrftoken"""
try:
# 尝试从隐藏域获取
csrf_token = page.input_value('input[name="csrfmiddlewaretoken"]')
return csrf_token
except:
try:
# 尝试从meta标签获取
csrf_token = page.evaluate('() => document.querySelector("meta[name=csrf-token]")?.content')
return csrf_token
except:
# 如果页面没有CSRF token,返回None
return None
def register_user(page, username, password, phone, email, confirm_password=None, csrf_token=None):
"""执行用户注册请求"""
if confirm_password is None:
confirm_password = password
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
hashed_confirm_password = sha256_hash(confirm_password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_confirm_password,
'phone': phone,
'email': email
}
if csrf_token:
data['csrfmiddlewaretoken'] = csrf_token
# 发送注册请求
response = page.request.post("/register", data=data)
return response
@pytest.fixture(autouse=True)
def setup_database():
"""每个测试前清空user表"""
clear_user_table()
def test_register_valid_user_success(browser_context):
"""TC001: 验证有效用户数据注册成功"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register") # TODO: 需要根据实际URL调整
csrf_token = get_csrf_token(page)
# 准备有效注册数据
username = "testuser"
password = "Pass@123"
phone = "13800138000"
email = "test@example.com"
# 执行注册请求
response = register_user(page, username, password, phone, email, csrf_token=csrf_token)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "注册成功" in response_text or "success" in response_text.lower()
# 验证数据库中存在新用户记录
result = verify_database("SELECT * FROM user WHERE username=%s", (username,))
assert len(result) == 1
assert result[0]['username'] == username
assert result[0]['phone'] == phone
assert result[0]['email'] == email
def test_register_duplicate_username_failure(browser_context):
"""TC002: 验证重复用户名注册失败"""
page = browser_context.new_page()
# 先创建一个用户
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 创建初始用户
initial_response = register_user(
page,
username="testuser",
password="Pass@123",
phone="13800138001",
email="initial@example.com",
csrf_token=csrf_token
)
assert initial_response.status == 200
# 尝试用相同用户名注册
duplicate_response = register_user(
page,
username="testuser", # 相同用户名
password="NewPass@123",
phone="13900139000",
email="new@example.com",
csrf_token=csrf_token
)
# 验证响应
assert duplicate_response.status == 200
response_text = duplicate_response.text()
assert "用户名已存在" in response_text or "username exists" in response_text.lower()
# 验证数据库中只有一条记录
result = verify_database("SELECT * FROM user WHERE username='testuser'")
assert len(result) == 1
def test_register_duplicate_phone_failure(browser_context):
"""TC003: 验证重复手机号注册失败"""
page = browser_context.new_page()
# 先创建一个用户
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 创建初始用户
initial_response = register_user(
page,
username="initialuser",
password="Pass@123",
phone="13800138000",
email="initial@example.com",
csrf_token=csrf_token
)
assert initial_response.status == 200
# 尝试用相同手机号注册
duplicate_response = register_user(
page,
username="newuser",
password="NewPass@123",
phone="13800138000", # 相同手机号
email="new@example.com",
csrf_token=csrf_token
)
# 验证响应
assert duplicate_response.status == 200
response_text = duplicate_response.text()
assert "手机号已注册" in response_text or "phone registered" in response_text.lower()
# 验证数据库中只有初始记录
result = verify_database("SELECT * FROM user WHERE phone='13800138000'")
assert len(result) == 1
def test_register_duplicate_email_failure(browser_context):
"""TC004: 验证重复邮箱注册失败"""
page = browser_context.new_page()
# 先创建一个用户
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 创建初始用户
initial_response = register_user(
page,
username="initialuser",
password="Pass@123",
phone="13800138001",
email="test@example.com",
csrf_token=csrf_token
)
assert initial_response.status == 200
# 尝试用相同邮箱注册
duplicate_response = register_user(
page,
username="newuser",
password="NewPass@123",
phone="13900139000",
email="test@example.com", # 相同邮箱
csrf_token=csrf_token
)
# 验证响应
assert duplicate_response.status == 200
response_text = duplicate_response.text()
assert "邮箱已存在" in response_text or "email exists" in response_text.lower()
# 验证数据库中只有初始记录
result = verify_database("SELECT * FROM user WHERE email='test@example.com'")
assert len(result) == 1
def test_register_missing_username_failure(browser_context):
"""TC005: 验证缺失用户名注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备注册数据(空用户名)
username = ""
password = "Pass@123"
phone = "13800138000"
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "用户名不能为空" in response_text or "username required" in response_text.lower()
# 验证数据库无新记录 result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_missing_password_failure(browser_context):
"""TC006: 验证缺失密码注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备注册数据(空密码)
username = "testuser"
password = ""
phone = "13800138000"
email = "test@example.com"
# 构建请求数据
data = {
'username': username,
'password': password,
'confirm_password': password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "密码不能为空" in response_text or "password required" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_missing_phone_failure(browser_context):
"""TC007: 验证缺失手机号注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备注册数据(空手机号)
username = "testuser"
password = "Pass@123"
phone = ""
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "手机号不能为空" in response_text or "phone required" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_missing_email_failure(browser_context):
"""TC008: 验证缺失邮箱注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备注册数据(空邮箱)
username = "testuser"
password = "Pass@123"
phone = "13800138000"
email = ""
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "邮箱不能为空" in response_text or "email required" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_invalid_csrf_token_failure(browser_context):
"""TC009: 验证无效CSRF Token注册失败"""
page = browser_context.new_page()
# 准备有效注册数据
username = "testuser"
password = "Pass@123"
phone = "13800138000"
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据(使用空CSRF Token)
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': '' # 空CSRF Token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应 - 通常返回403 Forbidden
assert response.status == 403 or response.status == 200 # 取决于服务器实现
if response.status == 200:
response_text = response.text()
assert "无效的CSRF Token" in response_text or "csrf" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_long_username_failure(browser_context):
"""TC010: 验证超长用户名注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备超长用户名(21字符)
username = "a" * 21 # 21个字符的用户名
password = "Pass@123"
phone = "13800138000"
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "用户名长度超过限制" in response_text or "username length" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_short_username_failure(browser_context):
"""TC011: 验证超短用户名注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备超短用户名(3字符)
username = "abc" # 3个字符的用户名
password = "Pass@123"
phone = "13800138000"
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "用户名长度不足" in response_text or "username length" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_invalid_phone_format_failure(browser_context):
"""TC012: 验证无效手机号格式注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备无效手机号格式(10位数字)
username = "testuser"
password = "Pass@123"
phone = "1234567890" # 10位数字,不符合中国手机号格式
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "手机号格式错误" in response_text or "phone format" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_invalid_email_format_failure(browser_context):
"""TC013: 验证无效邮箱格式注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备无效邮箱格式
username = "testuser"
password = "Pass@123"
phone = "13800138000"
email = "invalid-email" # 无效邮箱格式
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_password,
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "邮箱格式错误" in response_text or "email format" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_password_mismatch_failure(browser_context):
"""TC014: 验证密码与确认密码不一致注册失败"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备密码与确认密码不一致的数据
username = "testuser"
password = "Pass@123"
confirm_password = "Diff@123" # 不同的确认密码 phone = "13800138000"
email = "test@example.com"
# 对密码进行SHA256散列
hashed_password = sha256_hash(password)
hashed_confirm_password = sha256_hash(confirm_password)
# 构建请求数据
data = {
'username': username,
'password': hashed_password,
'confirm_password': hashed_confirm_password, # 不一致的确认密码
'phone': phone,
'email': email,
'csrfmiddlewaretoken': csrf_token
}
# 发送注册请求
response = page.request.post("/register", data=data)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "两次密码不一致" in response_text or "password mismatch" in response_text.lower()
# 验证数据库无新记录
result = verify_database("SELECT COUNT(*) as count FROM user")
assert result[0]['count'] == 0
def test_register_password_stored_as_hash(browser_context):
"""TC015: 验证注册成功后密码存储为SHA256散列值"""
page = browser_context.new_page()
# 访问注册页面获取CSRF Token
page.goto("http://localhost:8000/register")
csrf_token = get_csrf_token(page)
# 准备注册数据
username = "hashuser"
password = "Secure@123"
phone = "13800138000"
email = "hash@example.com"
# 计算预期的SHA256散列值
expected_hash = sha256_hash(password)
# 执行注册请求
response = register_user(page, username, password, phone, email, csrf_token=csrf_token)
# 验证响应
assert response.status == 200
response_text = response.text()
assert "注册成功" in response_text or "success" in response_text.lower()
# 查询数据库验证密码是否以SHA256散列形式存储
result = verify_database("SELECT password FROM user WHERE username=%s", (username,))
assert len(result) == 1
stored_password = result[0]['password']
assert stored_password == expected_hash
只要书写好提示词,就可以应对各种测试场景了。