Skip to content

Commit 7f623e2

Browse files
authored
Merge pull request #82 from MGdaasLab/v2.3.0-github
V2.3.0 GitHub
2 parents b07ee04 + f186a4d commit 7f623e2

72 files changed

Lines changed: 14277 additions & 341 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ DJANGO_ADMIN_PASSWORD=admin123456
1414
# ===============================
1515
# 数据库类型: postgres(默认,生产推荐) 或 sqlite(本地开发)
1616
DATABASE_TYPE=postgres
17+
# 可选:按环境/分支切换数据库。支持:github、pro、dev
18+
POSTGRES_DB_VARIANT=
1719

1820
# PostgreSQL 配置(DATABASE_TYPE=postgres 时使用)
1921
# Docker 容器内使用服务名和内部端口
@@ -48,11 +50,23 @@ WHARTTEST_BACKEND_URL=http://backend:8000
4850
# 创建新密钥:登录后台 -> 系统管理 -> API密钥 -> 创建新密钥
4951
WHARTTEST_API_KEY=wharttest-default-mcp-key-2025
5052

53+
# DOCX Editor 集成配置(主项目 -> docx-editor)
54+
# 当前仓库的 docx-editor Docker 默认示例使用宿主机端口 8910
55+
DOCX_EDITOR_BASE_URL=http://host.docker.internal:8910
56+
# 浏览器端实际打开的 docx-editor 地址;服务端走内网地址时必须额外配置
57+
DOCX_EDITOR_PUBLIC_BASE_URL=http://127.0.0.1:8910
58+
DOCX_EDITOR_SERVICE_KEY=docx-editor-default-integration-key-2026
59+
5160
# Qdrant 向量数据库配置
5261
# Docker 环境使用: http://qdrant:6333
5362
# 本地开发使用: http://localhost:8918
5463
QDRANT_URL=http://qdrant:6333
5564

65+
# 官方微信插件宿主地址
66+
# Docker Compose 内部固定使用: http://weixin-plugin-host:3001
67+
# 本地手动运行宿主时可使用: http://127.0.0.1:8922
68+
# WEIXIN_PLUGIN_HOST_URL=http://weixin-plugin-host:3001
69+
5670
# ===============================
5771
# Docker 构建/部署加速配置
5872
# ===============================
@@ -97,6 +111,7 @@ QDRANT_URL=http://qdrant:6333
97111
# DOCKER_BACKEND_IMAGE=ghcr.io/mgdaaslab/wharttest-backend:latest
98112
# DOCKER_FRONTEND_IMAGE=ghcr.io/mgdaaslab/wharttest-frontend:latest
99113
# DOCKER_MCP_IMAGE=ghcr.io/mgdaaslab/wharttest-mcp:latest
114+
# DOCKER_NODE_22_BASE_IMAGE=node:22-alpine
100115
# DOCKER_POSTGRES_IMAGE=postgres:16-alpine
101116
# DOCKER_REDIS_IMAGE=redis:7-alpine
102117
# DOCKER_QDRANT_IMAGE=qdrant/qdrant:latest

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ data
3333
temp/
3434
tmp/
3535
docker-images
36-
36+
PandaWiki-skill/
3737
# Cache directories
3838
.cache/
3939
.npm/
@@ -70,3 +70,4 @@ WHartTest_Django/chat_history.sqlite-wal
7070
WHartTest_Actuator/browsers/
7171
.github/copilot-instructions.md
7272
WHartTest_Django/celerybeat-schedule
73+
AGENTS.md

README.md

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,82 @@
44

55
## 项目简介
66

7-
WHartTest 是一个基于 Django REST Framework 构建的AI驱动测试自动化平台,核心功能是通过AI智能生成测试用例。平台集成了 LangChain、MCP(Model Context Protocol)工具调用、项目管理、需求评审、测试用例管理以及先进的知识库管理和文档理解功能。利用大语言模型和多种嵌入服务(OpenAI、Azure OpenAI、Ollama等)的能力,自动化生成高质量的测试用例,并结合知识库提供更精准的测试辅助,为测试团队提供一个完整的智能测试管理解决方案
7+
WHartTest 是基于 **Django 5.2 + DRF** 与现代大模型技术打造的 **AI 驱动智能测试平台**。平台采用前后端分离的 Monorepo 架构,由 6 个子项目组成(Django 后端、Vue 前端、UI 自动化执行器、MCP 工具服务、Agent 技能库、在线文档编辑器),聚合自然语言理解、知识库检索与嵌入搜索能力,结合 **LangChain/LangGraph****MCP(Model Context Protocol)** 工具调用,实现从需求到可执行测试用例的自动化生成、管理与执行,为测试团队提供完整的智能测试管理解决方案
88

99
## 平台功能
1010

11-
### AI智能测试用例生成
11+
### AI 智能测试用例生成
1212
基于大语言模型(LLM)技术,实现从需求到测试用例的智能转化:
13-
- **多源输入**:支持需求文档、对话交互、API文档等多种输入方式
13+
- **多源输入**:支持需求文档、AI 对话、知识库检索等多种输入方式
1414
- **结构化输出**:自动生成包含测试步骤、前置条件、输入数据、期望结果、优先级(P0-P3)的完整用例
15-
- **多模型支持**:集成 OpenAI、Azure OpenAI、Ollama、Xinference 等多种嵌入服务
16-
17-
### 知识库管理与文档理解
18-
构建项目级知识库,为AI生成提供精准上下文:
19-
- **多格式支持**:PDF、Word、Excel、PPT、Markdown、网页链接等
20-
- **智能分块**:自动文档解析、分块与向量化存储
21-
- **语义检索**:基于向量相似度的精准知识检索
22-
- **Reranker精排**:支持配置重排序模型提升检索精度
23-
24-
### MCP工具集成
25-
通过 Model Context Protocol 实现AI与测试工具的无缝对接:
26-
- **浏览器自动化**:集成 Playwright,支持页面操作、元素定位、截图录屏
27-
- **多协议支持**:streamable-http、SSE 等传输协议
28-
- **HITL人工审批**:敏感操作支持人工确认机制
29-
- **自定义扩展**:支持接入第三方工具和服务
30-
31-
### 需求评审与风险分析
32-
AI驱动的需求质量评估,提前识别潜在风险:
15+
- **流式对话**:LangGraph 驱动的流式 AI 对话,支持上下文摘要压缩与 Token 用量统计
16+
- **多模型支持**:OpenAI、Azure OpenAI、Ollama、Xinference 等 OpenAI 兼容格式 LLM
17+
18+
### 智能编排(Orchestrator)
19+
AI 驱动的端到端测试生成流水线:
20+
- **自动化编排**:需求→知识库检索→AI 分析→测试用例生成
21+
- **交互式执行**:执行计划确认,支持 Agent Loop 分步执行
22+
- **HITL 审批**:人工审批中间件,关键节点可人工介入
23+
24+
### 知识库管理与文档理解
25+
构建项目级知识库,为 AI 生成提供精准上下文:
26+
- **多格式支持**:PDF、Word、Excel、PPT、Markdown、网页链接
27+
- **智能分块**:自动文档解析、分块与向量化存储,支持图片提取与多模态嵌入
28+
- **混合检索**:Dense + BM25 Sparse 稀疏-稠密混合检索 + RRF 融合排序
29+
- **Reranker 精排**:可配置重排序模型提升检索精度
30+
31+
### MCP 工具集成
32+
通过 Model Context Protocol 实现 AI 与测试工具的无缝对接:
33+
- **远程服务管理**:streamable-http、SSE 等传输协议,支持多服务器配置
34+
- **工具发现与同步**:自动从 MCP 服务器发现并同步可用工具
35+
- **HITL 人工审批**:工具级别的敏感操作人工确认机制
36+
- **内置工具集**:WHartTest 自身 MCP 工具(项目/模块/用例 CRUD)及第三方平台集成
37+
38+
### 需求评审与风险分析
39+
AI 驱动的需求质量评估,提前识别潜在风险:
3340
- **六维评分体系**:完整度、清晰度、一致性、可测性、可行性、逻辑性
34-
- **问题追踪**:自动识别需求中的模糊点、矛盾点、遗漏点
35-
- **改进建议**:提供针对性的需求优化和测试策略建议
41+
- **状态流转**:上传→处理→模块拆分→用户调整→待评审→评审中→完成
42+
- **专项分析报告**:8 种分析维度的独立报告
43+
- **在线文档编辑**:集成 ONLYOFFICE 文档编辑器,支持 AI 补全与 AI 分析插件
3644

37-
### 测试用例管理
45+
### 测试用例管理
3846
全生命周期的测试用例管理能力:
39-
- **多级模块**:支持5级子模块的用例组织结构
47+
- **5 级模块树**:灵活的用例组织结构
48+
- **7 种测试类型**:覆盖功能、接口、性能、安全等测试场景
4049
- **用例审核**:待审核、通过、优化、不可用等状态流转
4150
- **测试套件**:灵活组合用例,支持并发执行配置
51+
- **Excel 导入/导出**:可配置字段映射的模板系统
4252
- **批量操作**:用例的批量创建、编辑、移动、删除
4353

44-
### UI自动化测试
45-
低代码UI自动化,降低自动化测试门槛:
46-
- **多定位策略**:CSS、XPath、Text、Role、Label 等元素定位方式
47-
- **可视化编排**:拖拽式步骤编排,支持条件判断、循环、断言
48-
- **环境管理**:多浏览器支持(Chromium/Firefox/WebKit)、环境配置切换
49-
- **执行记录**:完整的截图、视频、Trace追踪、执行日志
54+
### UI 自动化测试
55+
低代码 UI 自动化,降低自动化测试门槛:
56+
- **可视化编排**:5 级模块 → 页面管理 → 元素定位 → 步骤编排 → 用例组装
57+
- **9 种定位策略**:CSS、XPath、Text、Role、Label、Placeholder、AltText、Title、TestId
58+
- **6 种步骤类型**:元素操作、断言、SQL 查询、自定义变量、条件判断、Python 代码
59+
- **多浏览器支持**:Chromium / Firefox / WebKit,支持持久化浏览器上下文
60+
- **Trace 追踪**:完整的截图、Trace 录制与回放
5061

51-
### APP自动化测试
62+
### APP 自动化测试
5263
集成 mobile-mcp,支持移动端应用自动化测试:
53-
- **多平台支持**:Android、iOS 移动设备自动化
54-
- **设备管理**:设备连接状态检测、设备信息获取
55-
- **应用操作**:应用安装/卸载/启动/停止
64+
- **多平台支持**:Android、iOS 设备自动化
5665
- **元素交互**:点击、滑动、输入、手势操作
57-
- **屏幕操作**:截图、录屏、屏幕亮度/音量调节
58-
- **系统操作**:通知栏操作、权限管理、网络切换
66+
- **屏幕操作**:截图、录屏、亮度/音量调节
5967

60-
### Skill智能技能系统
61-
可扩展的Agent技能管理框架:
62-
- **多来源导入**:支持 ZIP 文件上传、Git 仓库导入
63-
- **SKILL.md规范**:标准化的技能描述文件
68+
### Skill 智能技能系统
69+
可扩展的 Agent 技能管理框架:
70+
- **多来源导入**:ZIP 文件上传、Git 仓库导入
71+
- **SKILL.md 规范**:标准化的技能描述文件
72+
- **内置技能**:浏览器自动化(Playwright/agent-browser)、图表生成(Draw.io)、知识库查询、平台 CLI 工具
6473
- **安全隔离**:技能文件独立存储,防止路径穿越攻击
6574
- **专属资源**:加入技术交流群获取经过微调优化的 Skills 和 MCP 工具包
66-
### 执行报告与分析
75+
76+
### 任务中心
77+
定时/周期任务调度:
78+
- **4 种调度策略**:一次性 / 每小时 / 每天 / 每周
79+
- 关联 UI 自动化或测试套件执行
80+
- 失败重试策略,基于 Celery Beat 数据库调度器
81+
82+
### 执行报告与分析
6783
全面的测试执行结果分析:
6884
- **实时监控**:执行进度、状态实时更新
6985
- **多维度统计**:通过率、执行时长、失败原因分析

WHartTest_Django/.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ DJANGO_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,http://l
2929
# ================================
3030
# 数据库类型: postgres(默认,生产推荐) 或 sqlite(本地开发)
3131
DATABASE_TYPE=postgres
32+
# 可选:按环境/分支切换数据库。支持:github、pro、dev
33+
POSTGRES_DB_VARIANT=
3234

3335
# PostgreSQL 配置(DATABASE_TYPE=postgres 时使用)
3436
POSTGRES_HOST=localhost
@@ -45,6 +47,22 @@ POSTGRES_PASSWORD=postgres
4547
# Docker 部署时通过 docker-compose.yml 设置为 /app/data/media
4648
MEDIA_ROOT=../data/media
4749

50+
# 微信官方插件宿主地址
51+
# 本地开发默认使用 3001
52+
WEIXIN_PLUGIN_HOST_URL=http://127.0.0.1:3001
53+
54+
# DOCX Editor 集成配置(主项目后端 -> docx-editor)
55+
# `DOCX_EDITOR_BASE_URL` 填主项目后端服务端实际访问 docx-editor 的地址:
56+
# - 主项目源码部署在宿主机:通常使用 `http://127.0.0.1:8910`
57+
# - 主项目跑在容器里:通常使用 `http://host.docker.internal:8910`
58+
DOCX_EDITOR_BASE_URL=http://127.0.0.1:8910
59+
# 可选:覆盖 docx-editor 返回给浏览器的外部地址。必须填写浏览器真实可访问的地址:
60+
# - 浏览器通过本机端口转发访问:`http://127.0.0.1:8910`
61+
# - 浏览器可直连宿主机/域名:填实际可访问地址
62+
DOCX_EDITOR_PUBLIC_BASE_URL=http://127.0.0.1:8910
63+
# 必须与 docx-editor/.env 中的 DOCX_EDITOR_INTEGRATION_KEY 完全一致
64+
DOCX_EDITOR_SERVICE_KEY=
65+
4866
# ================================
4967
# Django 管理员账户(首次启动时创建)
5068
# ================================

WHartTest_Django/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ COPY requirements/ /app/requirements/
221221
COPY skills/ /app/skills/
222222
COPY task_center/ /app/task_center/
223223
COPY ui_automation/ /app/ui_automation/
224+
COPY weixin_integration/ /app/weixin_integration/
224225
COPY testcase_templates/ /app/testcase_templates/
225226
COPY testcases/ /app/testcases/
226227
COPY wharttest_django/ /app/wharttest_django/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Generated by Django 5.2 on 2026-04-14 16:56
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('langgraph_integration', '0022_alter_llmconfig_provider'),
12+
('projects', '0004_remove_project_password_remove_project_system_url_and_more'),
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.AddField(
18+
model_name='chatsession',
19+
name='total_cache_read_tokens',
20+
field=models.BigIntegerField(default=0, help_text='该会话累计缓存命中的 Token 数', verbose_name='累计缓存命中 Token'),
21+
),
22+
migrations.CreateModel(
23+
name='TokenUsageRecord',
24+
fields=[
25+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26+
('session_id', models.CharField(help_text='关联的会话标识(非外键)', max_length=255, verbose_name='会话ID')),
27+
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
28+
('input_tokens', models.IntegerField(default=0, verbose_name='输入 Token')),
29+
('output_tokens', models.IntegerField(default=0, verbose_name='输出 Token')),
30+
('total_tokens', models.IntegerField(default=0, verbose_name='总 Token')),
31+
('cache_read_tokens', models.IntegerField(default=0, verbose_name='缓存命中 Token')),
32+
('project', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='projects.project', verbose_name='关联项目')),
33+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')),
34+
],
35+
options={
36+
'verbose_name': 'Token 使用记录',
37+
'verbose_name_plural': 'Token 使用记录',
38+
'ordering': ['-created_at'],
39+
'indexes': [models.Index(fields=['user', 'created_at'], name='langgraph_i_user_id_6bb484_idx'), models.Index(fields=['created_at'], name='langgraph_i_created_30561a_idx')],
40+
},
41+
),
42+
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
数据迁移:将已有 ChatSession 的 Token 统计回填到 TokenUsageRecord
3+
4+
每个 request_count > 0 的 ChatSession 生成一条合成记录,
5+
日期使用 updated_at,确保历史统计不会因为切换数据源而丢失。
6+
"""
7+
8+
from django.db import migrations
9+
10+
11+
def backfill_token_usage_records(apps, schema_editor):
12+
ChatSession = apps.get_model('langgraph_integration', 'ChatSession')
13+
TokenUsageRecord = apps.get_model('langgraph_integration', 'TokenUsageRecord')
14+
15+
sessions = ChatSession.objects.filter(request_count__gt=0).select_related('user', 'project')
16+
records = []
17+
for session in sessions:
18+
records.append(TokenUsageRecord(
19+
user=session.user,
20+
project=session.project,
21+
session_id=session.session_id,
22+
created_at=session.updated_at,
23+
input_tokens=session.total_input_tokens,
24+
output_tokens=session.total_output_tokens,
25+
total_tokens=session.total_tokens,
26+
cache_read_tokens=0, # 历史数据无缓存信息
27+
))
28+
if records:
29+
TokenUsageRecord.objects.bulk_create(records, batch_size=500)
30+
31+
32+
def reverse_backfill(apps, schema_editor):
33+
# 回滚时删除所有回填记录(cache_read_tokens=0 标识回填数据)
34+
TokenUsageRecord = apps.get_model('langgraph_integration', 'TokenUsageRecord')
35+
TokenUsageRecord.objects.all().delete()
36+
37+
38+
class Migration(migrations.Migration):
39+
40+
dependencies = [
41+
('langgraph_integration', '0023_add_token_usage_record_and_cache_field'),
42+
]
43+
44+
operations = [
45+
migrations.RunPython(backfill_token_usage_records, reverse_backfill),
46+
]

WHartTest_Django/langgraph_integration/models.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ class ChatSession(models.Model):
114114
help_text="该会话累计消耗的输出 Token 数")
115115
total_tokens = models.BigIntegerField(default=0, verbose_name="累计总 Token",
116116
help_text="该会话累计消耗的总 Token 数(输入+输出)")
117+
total_cache_read_tokens = models.BigIntegerField(default=0, verbose_name="累计缓存命中 Token",
118+
help_text="该会话累计缓存命中的 Token 数")
117119
request_count = models.IntegerField(default=0, verbose_name="请求次数",
118120
help_text="该会话的 LLM 请求次数")
119121

@@ -129,6 +131,45 @@ def __str__(self):
129131
return f"{self.user.username} - {self.title}"
130132

131133

134+
class TokenUsageRecord(models.Model):
135+
"""
136+
独立的 Token 使用记录 — 不随会话删除而丢失
137+
138+
每次 LLM 请求产生一条记录。session_id 仅存字符串,不依赖 ChatSession 外键,
139+
因此删除对话不影响统计数据。
140+
"""
141+
user = models.ForeignKey(
142+
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
143+
verbose_name="用户",
144+
)
145+
project = models.ForeignKey(
146+
'projects.Project', on_delete=models.SET_NULL,
147+
null=True, blank=True, verbose_name="关联项目",
148+
)
149+
session_id = models.CharField(
150+
max_length=255, verbose_name="会话ID",
151+
help_text="关联的会话标识(非外键)",
152+
)
153+
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
154+
155+
input_tokens = models.IntegerField(default=0, verbose_name="输入 Token")
156+
output_tokens = models.IntegerField(default=0, verbose_name="输出 Token")
157+
total_tokens = models.IntegerField(default=0, verbose_name="总 Token")
158+
cache_read_tokens = models.IntegerField(default=0, verbose_name="缓存命中 Token")
159+
160+
class Meta:
161+
verbose_name = "Token 使用记录"
162+
verbose_name_plural = "Token 使用记录"
163+
ordering = ['-created_at']
164+
indexes = [
165+
models.Index(fields=['user', 'created_at']),
166+
models.Index(fields=['created_at']),
167+
]
168+
169+
def __str__(self):
170+
return f"{self.user_id} - {self.session_id} - {self.total_tokens} tokens"
171+
172+
132173
class UserToolApproval(models.Model):
133174
"""
134175
用户工具审批偏好 - 记住用户对高风险工具的审批选择

0 commit comments

Comments
 (0)