Commit e16d6e0d authored by Cai Wei's avatar Cai Wei

feat(*): 整理不需要的业务组件

parent 78f35887
Pipeline #1316 failed
# Custom Environment - 自定义测试环境
CYPRESS_BASE_URL=https://your-custom-url.com
CYPRESS_API_URL=https://your-custom-api-url.com
CYPRESS_USERNAME=custom_user@example.com
CYPRESS_PASSWORD=custom_password
CYPRESS_REPORT_TITLE=Custom Tests
CYPRESS_REPORT_PAGE_TITLE=自定义环境测试报告
\ No newline at end of file
......@@ -3,8 +3,14 @@
# 开发环境
VITE_ENV = development
# Development Environment
CYPRESS_BASE_URL=http://localhost:3000
CYPRESS_API_URL=http://localhost:3000
CYPRESS_USERNAME=test@example.com
CYPRESS_PASSWORD=testpassword
CYPRESS_REPORT_TITLE=Development Tests
CYPRESS_REPORT_PAGE_TITLE=开发环境测试报告
# 开发环境下,后端接口的基础URL
# base api
......
......@@ -2,9 +2,15 @@
# 开发环境
VITE_ENV = production
# Production Environment
CYPRESS_BASE_URL=https://app.example.com
CYPRESS_API_URL=https://api.example.com
CYPRESS_USERNAME=prod_user@example.com
CYPRESS_PASSWORD=prod_password
CYPRESS_REPORT_TITLE=Production Tests
CYPRESS_REPORT_PAGE_TITLE=生产环境测试报告
# 开发环境下,后端接口的基础URL
# base api
......
# Staging Environment
CYPRESS_BASE_URL=https://staging.example.com
CYPRESS_API_URL=https://staging-api.example.com
CYPRESS_USERNAME=staging_user@example.com
CYPRESS_PASSWORD=staging_password
CYPRESS_REPORT_TITLE=Staging Tests
CYPRESS_REPORT_PAGE_TITLE=预发布环境测试报告
\ No newline at end of file
# Cypress Studio 启动器使用说明
## 概述
DC-TOM 项目提供了跨平台的 Cypress Studio 启动器,自动处理开发服务器启动和 Cypress Studio 的启动顺序。
## 功能特性
**自动启动开发服务器** - 确保测试环境就绪
**智能端口检测** - 自动检测端口占用情况
**跨平台支持** - Windows/macOS/Linux 全平台兼容
**进程管理** - 自动清理和停止相关进程
**错误处理** - 详细的错误提示和解决方案
## 快速开始
### 通用方法 (推荐)
```bash
# 启动 Cypress Studio (自动启动开发服务器)
npm run studio
# 查看帮助信息
npm run studio:help
# 使用原有命令
npm run cy:open:studio
```
### Windows 专用方法
```cmd
# 方法1: 使用 npm 脚本
npm run studio:win
# 方法2: 直接运行批处理文件
start-studio.bat
# 方法3: 双击 start-studio.bat 文件
```
### macOS/Linux 方法
```bash
# 使用 Node.js 脚本
node start-studio.cjs
# 如果仍有 bash 脚本 (需要可执行权限)
./start-studio.sh
```
## 启动流程
1. **环境检查** - 验证 Node.js、npm、npx 是否可用
2. **端口检测** - 检查 localhost:3000 是否被占用
3. **启动开发服务器** - 如果端口空闲,运行 `npm run dev`
4. **等待就绪** - 等待开发服务器完全启动并响应
5. **启动 Cypress Studio** - 开发环境就绪后启动 Studio
## 故障排除
### 常见问题
**问题**: Windows 下无法执行 `.sh` 脚本
**解决**: 使用 `npm run studio``start-studio.bat`
**问题**: 端口 3000 被占用
**解决**:
- 启动器会自动跳过开发服务器启动
- 手动停止占用端口的进程
- 或修改项目配置使用其他端口
**问题**: Node.js 或 npm 未找到
**解决**:
- 安装 Node.js: https://nodejs.org
- 确保 Node.js 在系统 PATH 中
- 重新打开终端/命令提示符
**问题**: Cypress 启动失败
**解决**:
- 运行 `npm install` 安装项目依赖
- 检查 Cypress 是否正确安装
- 尝试单独运行 `npx cypress open`
### 手动清理
如果启动器意外退出,可能需要手动清理进程:
```bash
# macOS/Linux
pkill -f "vite"
pkill -f "cypress"
# Windows (命令提示符)
taskkill /f /im node.exe
taskkill /f /im cypress.exe
```
## 文件说明
| 文件名 | 用途 | 平台支持 |
|--------|------|----------|
| `start-studio.cjs` | Node.js 启动脚本 | 全平台 |
| `start-studio.bat` | Windows 批处理文件 | Windows |
| `start-studio.sh` | Bash 脚本 (如存在) | macOS/Linux |
## 自定义配置
### 修改端口
如果需要修改开发服务器端口,编辑 `vite.config.js`:
```javascript
export default defineConfig({
server: {
port: 3001 // 修改为其他端口
}
})
```
同时更新启动器脚本中的端口检测逻辑。
### 添加启动参数
`package.json` 中自定义脚本:
```json
{
"scripts": {
"studio:custom": "node start-studio.cjs --custom-arg"
}
}
```
## 技术细节
- **进程管理**: 使用 Node.js `child_process` 模块
- **端口检测**: HTTP 服务器监听测试
- **跨平台**: 自动检测操作系统并调整命令
- **错误处理**: 完整的错误捕获和用户友好提示
## 支持
如果遇到问题,请:
1. 检查 Node.js 版本 (推荐 18+)
2. 确保项目依赖已安装 (`npm install`)
3. 查看启动器输出的错误信息
4. 参考本文档的故障排除部分
---
**注意**: 该启动器专为 DC-TOM 项目设计,但可以适配其他 Vue + Vite + Cypress 项目。
\ No newline at end of file
# DC-TOM 项目
# Cypress 独立测试工具
基于 Vue 3 + Vite + Element Plus 的现代化前端项目,专为除尘器监控和管理系统设计
一个独立的 Cypress 端到端测试工具,可用于测试任何前端应用程序
## 🚀 快速开始
......@@ -9,16 +9,6 @@
npm install
```
### 开发模式
```bash
npm run dev
```
### 构建生产版本
```bash
npm run build
```
## 🧪 测试
### Cypress 端到端测试
......@@ -29,149 +19,100 @@ npm run cy:open
# 运行所有测试
npm run cy:run
# 运行特定测试套件
npm run cy:run:basic # 基础测试
npm run cy:run:business # 业务测试
npm run cy:run:data # 数据测试
# 运行特定环境的测试
npm run cy:run:dev # 开发环境测试
npm run cy:run:staging # 预发布环境测试
npm run cy:run:prod # 生产环境测试
npm run cy:run:custom # 自定义环境测试
# 使用特定浏览器运行测试
npm run cy:run:chrome # Chrome浏览器
npm run cy:run:firefox # Firefox浏览器
```
### 本地 CI 测试
### 批量测试
```bash
# 运行完整的本地 CI 流程
npm run ci:local
# 快速测试
npm run test:quick
# 运行所有环境的测试
npm run test:all
```
## 🔧 测试生成器
## 🔧 配置
### 自动化测试生成工具
### 环境配置文件
项目包含了一个强大的测试生成器,可以从JSON描述自动生成Cypress测试代码。
项目包含多个环境配置文件:
- `.env.development` - 开发环境配置
- `.env.staging` - 预发布环境配置
- `.env.production` - 生产环境配置
- `.env.custom` - 自定义环境配置(可随时修改用于测试不同地址)
#### 初始化测试生成器
```bash
npm run test-gen:setup
```
### 环境变量
#### 生成示例文件
```bash
# JSON格式示例
npm run test-gen:example
# 自然语言描述示例
npm run test-gen:nl-example
```
支持以下环境变量:
- `CYPRESS_BASE_URL` - 测试目标的基础URL
- `CYPRESS_API_URL` - API接口的基础URL
- `CYPRESS_USERNAME` - 测试用户名
- `CYPRESS_PASSWORD` - 测试密码
- `CYPRESS_REPORT_TITLE` - 测试报告标题
- `CYPRESS_REPORT_PAGE_TITLE` - 测试报告页面标题
#### 验证测试描述
```bash
npm run test-gen:validate
```
### 脚本执行
#### 生成测试代码
也可以使用脚本执行测试:
```bash
# 从JSON文件生成
npm run test-gen:generate
# 使用脚本执行测试
./run-tests.sh [environment] [browser]
# 从自然语言描述生成
npm run test-gen:parse
# 示例
./run-tests.sh staging chrome
./run-tests.sh custom chrome
./run-tests.sh https://your-website.com chrome # 直接指定URL
```
#### 运行生成的测试
```bash
npm run test-gen:run
```
## 📁 项目结构
#### 启动Web界面
```bash
npm run test-gen:web
```
cypress/
├── e2e/ # 端到端测试用例
├── support/ # 测试支持文件
│ └── page-objects/ # 页面对象模型
└── fixtures/ # 测试数据
### 测试描述格式
项目支持两种测试描述方式:
#### 1. 自然语言描述(推荐)
使用中文自然语言直接描述测试场景:
```
目标页面:首页
测试内容:
1,在告警总览内查询今日布袋告警数据。
2,查询布袋总数
3,在首页查看顶部区域综合健康度的布袋健康度
.env.development # 开发环境配置
.env.staging # 预发布环境配置
.env.production # 生产环境配置
.env.custom # 自定义环境配置
run-tests.sh # 测试执行脚本
```
支持的操作:查询、查看、点击、输入、验证等
支持的模块:首页、告警总览、设备管理、布袋周期等
支持的时间:今日、昨日、本周、本月等
## 🛠️ 技术栈
#### 2. JSON格式描述
- **测试框架**: Cypress 13.6+
- **报告生成**: Mochawesome
使用JSON格式描述测试场景,支持多种测试类型:
- UI验证测试
- 交互功能测试
- 数据验证测试
- 错误处理测试
- 性能测试
- 响应式设计测试
## 📖 使用指南
详细使用说明请参考:
- [测试生成器文档](cypress/test-generator/README.md)
- [自然语言测试指南](NATURAL_LANGUAGE_TESTING_GUIDE.md)
### 1. 配置测试目标
## 📁 项目结构
修改 `.env.development` 文件中的 `CYPRESS_BASE_URL` 为您的开发环境地址。
```
src/
├── components/ # 公共组件
├── layout/ # 布局组件
├── views/ # 页面组件
├── pinia/ # 状态管理
├── router/ # 路由配置
├── request/ # API 请求
└── utils/ # 工具函数
要测试不同的地址,您可以:
- 修改现有的环境配置文件
- 创建新的环境配置文件
- 直接在运行脚本时指定URL
cypress/
├── e2e/ # 端到端测试
├── test-generator/ # 测试生成器
├── support/ # 测试支持文件
└── fixtures/ # 测试数据
```
### 2. 编写测试用例
## 🛠️ 技术栈
`cypress/e2e/` 目录下创建测试文件,文件名以 `.cy.js` 结尾。
- **前端框架**: Vue 3.5+ (Composition API + `<script setup>`)
- **构建工具**: Vite 6.3+
- **UI组件库**: Element Plus 2.9+
- **状态管理**: Pinia 3.0+
- **路由**: Vue Router 4.5+
- **图表库**: ECharts 5.6+
- **HTTP客户端**: Axios 1.9+
- **测试框架**: Cypress 13.6+
- **样式预处理**: Sass 1.88+
### 3. 运行测试
## 📖 开发指南
使用 npm 脚本或直接运行 Cypress 命令执行测试。
### 代码规范
- 使用 Vue 3 Composition API
- 遵循 `<script setup>` 语法
- 使用 TypeScript (可选)
- 统一的代码格式化
### 4. 查看报告
### 测试策略
- 分层测试:基础、业务、数据
- 自动化测试生成
- CI/CD 集成
- 测试报告生成
测试执行完成后,报告将生成在 `cypress/reports/` 目录中。
## 🔗 相关链接
- [Vue 3 文档](https://vuejs.org/)
- [Vite 文档](https://vitejs.dev/)
- [Element Plus 文档](https://element-plus.org/)
- [Cypress 文档](https://docs.cypress.io/)
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
- [Cypress 文档](https://docs.cypress.io/)
\ No newline at end of file
#!/bin/bash
# DC-TOM 测试报告清理脚本
# 专门用于清理历史测试报告、视频和截图文件
# 默认保留最近3次测试结果
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 配置
DEFAULT_KEEP_COUNT=3
REPORTS_DIR="cypress/reports"
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 显示使用说明
show_usage() {
echo "用法: $0 [保留数量]"
echo ""
echo "参数:"
echo " 保留数量 可选,指定保留最近几次测试结果 (默认: $DEFAULT_KEEP_COUNT)"
echo ""
echo "示例:"
echo " $0 # 保留最近 $DEFAULT_KEEP_COUNT 次测试结果"
echo " $0 5 # 保留最近 5 次测试结果"
echo " $0 1 # 仅保留最新的测试结果"
echo ""
echo "清理内容:"
echo " - cypress/reports/ 目录下的 HTML 和 JSON 报告文件"
echo " - cypress/videos/ 目录下的测试执行视频"
echo " - cypress/screenshots/ 目录下的失败截图"
echo " - public/reports/ 目录下的合并报告(可选)"
echo ""
echo "注意事项:"
echo " - 此脚本会根据文件修改时间排序,保留最新的文件"
echo " - 建议在项目根目录下运行此脚本"
echo " - 清理操作不可逆,请谨慎使用"
}
# 检查环境
check_environment() {
if [ ! -f "package.json" ]; then
log_error "请在项目根目录运行此脚本"
exit 1
fi
if [ ! -d "cypress" ]; then
log_error "未找到 cypress 目录,请确认这是一个 Cypress 项目"
exit 1
fi
}
# 清理历史报告
cleanup_reports() {
local keep_count=$1
log_info "开始清理历史测试报告,保留最近 $keep_count 次结果..."
local total_cleaned=0
# 清理 cypress/reports 目录中的历史报告
if [ -d "$REPORTS_DIR" ]; then
log_info "清理 $REPORTS_DIR 目录..."
# 清理 HTML 报告文件(按时间戳排序)
local html_files=($REPORTS_DIR/mochawesome_*.html)
if [ ${#html_files[@]} -gt 0 ] && [ -f "${html_files[0]}" ]; then
log_info "发现 ${#html_files[@]} 个 HTML 报告文件"
# 按文件修改时间排序,删除旧文件
local files_to_delete=$(printf '%s\n' "${html_files[@]}" | xargs ls -t | tail -n +$((keep_count + 1)))
if [ -n "$files_to_delete" ]; then
echo "$files_to_delete" | while read file; do
log_info "删除旧 HTML 报告: $(basename "$file")"
rm -f "$file"
((total_cleaned++))
done
else
log_info "HTML 报告文件数量未超过保留限制"
fi
else
log_info "未找到 HTML 报告文件"
fi
# 清理 JSON 报告文件
local json_files=($REPORTS_DIR/mochawesome_*.json)
if [ ${#json_files[@]} -gt 0 ] && [ -f "${json_files[0]}" ]; then
log_info "发现 ${#json_files[@]} 个 JSON 报告文件"
local files_to_delete=$(printf '%s\n' "${json_files[@]}" | xargs ls -t | tail -n +$((keep_count + 1)))
if [ -n "$files_to_delete" ]; then
echo "$files_to_delete" | while read file; do
log_info "删除旧 JSON 报告: $(basename "$file")"
rm -f "$file"
((total_cleaned++))
done
else
log_info "JSON 报告文件数量未超过保留限制"
fi
else
log_info "未找到 JSON 报告文件"
fi
else
log_warning "$REPORTS_DIR 目录不存在"
fi
# 清理测试视频
if [ -d "cypress/videos" ]; then
log_info "清理测试视频..."
local video_files=(cypress/videos/*.mp4)
if [ ${#video_files[@]} -gt 0 ] && [ -f "${video_files[0]}" ]; then
log_info "发现 ${#video_files[@]} 个视频文件"
local files_to_delete=$(printf '%s\n' "${video_files[@]}" | xargs ls -t | tail -n +$((keep_count + 1)))
if [ -n "$files_to_delete" ]; then
echo "$files_to_delete" | while read file; do
log_info "删除旧视频: $(basename "$file")"
rm -f "$file"
((total_cleaned++))
done
else
log_info "视频文件数量未超过保留限制"
fi
else
log_info "未找到视频文件"
fi
else
log_info "未找到 cypress/videos 目录"
fi
# 清理截图目录(按目录修改时间)
if [ -d "cypress/screenshots" ]; then
log_info "清理失败截图..."
# 获取所有截图子目录
local screenshot_dirs=(cypress/screenshots/*/)
if [ ${#screenshot_dirs[@]} -gt 0 ] && [ -d "${screenshot_dirs[0]}" ]; then
log_info "发现 ${#screenshot_dirs[@]} 个截图目录"
local dirs_to_delete=$(printf '%s\n' "${screenshot_dirs[@]}" | xargs ls -td | tail -n +$((keep_count + 1)))
if [ -n "$dirs_to_delete" ]; then
echo "$dirs_to_delete" | while read dir; do
log_info "删除旧截图目录: $(basename "$dir")"
rm -rf "$dir"
((total_cleaned++))
done
else
log_info "截图目录数量未超过保留限制"
fi
else
log_info "未找到截图子目录"
fi
else
log_info "未找到 cypress/screenshots 目录"
fi
# 可选:清理 public/reports 目录
if [ -d "public/reports" ]; then
log_info "清理合并报告..."
local old_reports=$(find public/reports -name "*.html" -o -name "*.json" | wc -l)
if [ "$old_reports" -gt 0 ]; then
log_warning "发现 $old_reports 个合并报告文件"
read -p "是否清理 public/reports 目录中的文件? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -f public/reports/*.html public/reports/*.json
log_info "已清理 public/reports 目录"
((total_cleaned += old_reports))
else
log_info "跳过 public/reports 目录清理"
fi
else
log_info "public/reports 目录为空"
fi
fi
log_success "清理完成,共清理了约 $total_cleaned 个文件/目录"
}
# 显示当前状态
show_status() {
log_info "当前测试文件状态:"
echo
echo "📊 报告文件:"
if [ -d "$REPORTS_DIR" ]; then
local html_count=$(find $REPORTS_DIR -name "mochawesome_*.html" 2>/dev/null | wc -l)
local json_count=$(find $REPORTS_DIR -name "mochawesome_*.json" 2>/dev/null | wc -l)
echo " HTML 报告: $html_count 个"
echo " JSON 报告: $json_count 个"
else
echo " 报告目录不存在"
fi
echo
echo "🎥 视频文件:"
if [ -d "cypress/videos" ]; then
local video_count=$(find cypress/videos -name "*.mp4" 2>/dev/null | wc -l)
echo " 测试视频: $video_count 个"
else
echo " 视频目录不存在"
fi
echo
echo "📸 截图文件:"
if [ -d "cypress/screenshots" ]; then
local screenshot_dirs=$(find cypress/screenshots -maxdepth 1 -type d 2>/dev/null | grep -v "^cypress/screenshots$" | wc -l)
echo " 截图目录: $screenshot_dirs 个"
else
echo " 截图目录不存在"
fi
echo
}
# 主函数
main() {
# 处理帮助和状态选项
case ${1:-} in
"help"|"-h"|"--help")
show_usage
exit 0
;;
"status"|"-s"|"--status")
check_environment
show_status
exit 0
;;
esac
local keep_count=${1:-$DEFAULT_KEEP_COUNT}
# 参数验证
if ! [[ "$keep_count" =~ ^[0-9]+$ ]] || [ "$keep_count" -lt 1 ]; then
log_error "保留数量必须是大于0的整数"
show_usage
exit 1
fi
echo "========================================"
echo " DC-TOM 测试报告清理工具"
echo "========================================"
echo
check_environment
log_info "准备清理历史测试文件,保留最近 $keep_count 次测试结果"
# 显示当前状态
show_status
# 确认操作
read -p "确认继续清理? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "操作已取消"
exit 0
fi
# 执行清理
cleanup_reports "$keep_count"
echo
log_success "报告清理完成!"
echo
echo "💡 提示:"
echo " - 运行 '$0 status' 查看当前文件状态"
echo " - 运行 '$0 help' 查看详细使用说明"
}
# 运行主函数
main "$@"
\ No newline at end of file
......@@ -2,28 +2,28 @@ import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: process.env.CYPRESS_baseUrl || 'http://localhost:3000',
// 通过环境变量设置基础URL
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots',
defaultCommandTimeout: 15000, // 增加超时时间适应CI环境
defaultCommandTimeout: 15000,
requestTimeout: 15000,
responseTimeout: 15000,
pageLoadTimeout: 30000,
// Cypress Studio配置
experimentalStudio: true,
experimentalInteractiveRunEvents: true,
chromeWebSecurity: false, // 允许Studio跨域交互
chromeWebSecurity: false,
modifyObstructiveCode: false,
retries: {
runMode: 2, // CI环境重试2次
openMode: 0 // 开发环境不重试
runMode: 2,
openMode: 0
},
setupNodeEvents(on, config) {
// CI环境特殊配置
// 根据环境变量动态设置配置
if (config.isTextTerminal) {
config.video = true
config.screenshotOnRunFailure = true
......@@ -31,22 +31,22 @@ export default defineConfig({
return config
},
env: {
// 环境变量
apiUrl: process.env.CYPRESS_baseUrl || 'http://localhost:3000',
username: 'test@example.com',
password: 'testpassword'
// 支持多环境配置
apiUrl: process.env.CYPRESS_API_URL || process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
username: process.env.CYPRESS_USERNAME || 'test@example.com',
password: process.env.CYPRESS_PASSWORD || 'testpassword'
},
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.js',
reporter: 'mochawesome',
reporterOptions: {
reportDir: 'cypress/reports',
overwrite: false, // 不覆盖旧报告,由 local-ci.sh 自动清理历史文件
overwrite: false,
html: true,
json: true,
timestamp: 'mmddyyyy_HHMMss', // 时间戳格式用于文件排序和清理
reportTitle: 'DC-TOM Cypress Tests',
reportPageTitle: 'DC-TOM 测试报告'
timestamp: 'mmddyyyy_HHMMss',
reportTitle: process.env.CYPRESS_REPORT_TITLE || 'Cypress Tests',
reportPageTitle: process.env.CYPRESS_REPORT_PAGE_TITLE || '测试报告'
}
},
component: {
......
// cypress/e2e/generic-login.cy.js
describe('通用登录测试', () => {
beforeEach(() => {
cy.visit('/')
})
it('应该能够成功登录', () => {
// 使用环境变量中的凭据登录
const username = Cypress.env('username')
const password = Cypress.env('password')
cy.get('[data-testid="login-username-input"]').type(username)
cy.get('[data-testid="login-password-input"]').type(password)
cy.get('[data-testid="login-captcha-input"]').type('8888')
cy.get('[data-testid="login-submit-button"]').click()
// 验证登录成功(根据实际应用调整)
cy.url().should('not.include', '/login')
cy.get('[data-testid="dashboard-container"]').should('be.visible')
})
})
\ No newline at end of file
......@@ -25,13 +25,39 @@ Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
// 自定义命令示例
// 通用的登录命令
Cypress.Commands.add('login', (username, password) => {
cy.get('[data-testid="login-username-input"]').type(username)
cy.get('[data-testid="login-password-input"]').type(password)
const user = username || Cypress.env('username')
const pass = password || Cypress.env('password')
cy.get('[data-testid="login-username-input"]').type(user)
cy.get('[data-testid="login-password-input"]').type(pass)
cy.get('[data-testid="login-captcha-input"]').type('8888')
cy.get('[data-testid="login-submit-button"]').click()
})
// 通用的导航命令
Cypress.Commands.add('navigateTo', (menuTestId) => {
cy.get(`[data-testid="${menuTestId}"]`).click()
})
// 通用的表单填写命令
Cypress.Commands.add('fillForm', (formData) => {
Object.keys(formData).forEach(field => {
cy.get(`[data-testid="${field}"]`).type(formData[field])
})
})
// 通用的表单提交命令
Cypress.Commands.add('submitForm', (buttonTestId) => {
cy.get(`[data-testid="${buttonTestId}"]`).click()
})
// 通用的数据验证命令
Cypress.Commands.add('verifyData', (selector, expectedValue) => {
cy.get(selector).should('contain.text', expectedValue)
})
// 等待页面加载完成
Cypress.Commands.add('waitForPageLoad', () => {
cy.get('body').should('be.visible')
......
// cypress/support/page-objects/dashboardPage.js
class DashboardPage {
get collectorListMenu() { return cy.get('[data-testid="menu-item-collectorList"]') }
get dustOverviewMenu() { return cy.get('[data-testid="menu-item-dust-overview"]') }
navigateToCollectorList() {
this.collectorListMenu.click()
}
navigateToDustOverview() {
this.dustOverviewMenu.click()
}
}
export default new DashboardPage()
\ No newline at end of file
// cypress/support/page-objects/loginPage.js
class LoginPage {
get usernameInput() { return cy.get('[data-testid="login-username-input"]') }
get passwordInput() { return cy.get('[data-testid="login-password-input"]') }
get captchaInput() { return cy.get('[data-testid="login-captcha-input"]') }
get submitButton() { return cy.get('[data-testid="login-submit-button"]') }
login(username, password) {
this.usernameInput.type(username)
this.passwordInput.type(password)
this.captchaInput.type('8888')
this.submitButton.click()
}
}
export default new LoginPage()
\ No newline at end of file
# DC-TOM 测试生成器
一个强大的测试描述转测试脚本的自动化工具,支持通过JSON描述快速生成Cypress测试代码。
## 🚀 功能特性
- **JSON驱动**: 使用标准化的JSON格式描述测试场景
- **多种测试类型**: 支持UI验证、交互测试、错误处理、性能测试等
- **模板化生成**: 基于丰富的模板库自动生成测试代码
- **Web界面**: 提供可视化的测试生成界面
- **CLI工具**: 支持命令行批量生成测试
- **Schema验证**: 内置JSON格式验证和错误提示
## 📁 项目结构
```
cypress/test-generator/
├── config.js # 核心配置文件
├── schema.json # JSON测试描述Schema
├── template-engine.js # 测试模板引擎
├── test-code-generator.js # 测试代码生成器
├── cli.js # 命令行工具
├── web-interface.html # Web可视化界面
└── README.md # 使用说明文档
```
## 🔧 安装与配置
### 1. 环境要求
- Node.js 18+
- 已安装的DC-TOM项目依赖
### 2. 使用方式
#### Web界面方式(推荐)
1. 在浏览器中打开 `web-interface.html`
2. 在左侧输入JSON测试描述
3. 点击"生成测试代码"按钮
4. 在右侧查看生成的Cypress测试代码
5. 复制代码或下载测试文件
#### 命令行方式
```bash
# 进入测试生成器目录
cd cypress/test-generator
# 生成示例JSON文件
node cli.js example
# 验证JSON文件格式
node cli.js validate test-description.json
# 生成测试代码
node cli.js generate test-description.json
# 指定输出文件
node cli.js generate test-description.json -o my-test.cy.js
# 查看帮助
node cli.js help
```
## 📋 JSON测试描述格式
### 基本结构
```json
{
"testSuite": {
"name": "测试套件名称",
"module": "模块名称",
"description": "测试套件描述",
"beforeEach": {
"login": true,
"visit": "页面路径",
"waitFor": ["等待的元素选择器"]
},
"scenarios": [
{
"name": "测试场景名称",
"type": "测试类型",
"description": "场景描述",
"priority": "优先级",
"steps": [],
"expectedResults": []
}
]
}
}
```
### 支持的模块
- `登录` - 用户登录功能
- `仪表盘` - 数据仪表盘
- `除尘器总览` - 除尘器管理
- `布袋周期` - 布袋周期管理
- `除尘监测` - 实时监测
- `设备管理` - 设备管理
- `闭环管理` - 工作流管理
- `告警总览` - 告警管理
### 测试类型
- `ui` - UI组件验证
- `interaction` - 用户交互测试
- `data` - 数据验证测试
- `error` - 错误处理测试
- `performance` - 性能测试
- `responsive` - 响应式设计测试
### 操作类型
| 操作 | 描述 | 参数 |
|------|------|------|
| `click` | 点击元素 | `target`: 目标选择器 |
| `type` | 输入文本 | `target`: 输入框选择器, `value`: 输入值 |
| `select` | 选择下拉选项 | `target`: 选择器, `value`: 选项值 |
| `verify` | 验证元素 | `target`: 选择器, `value.assertion`: 断言类型 |
| `wait` | 等待 | `value`: 等待时间(毫秒) |
| `intercept` | API拦截 | `value`: 拦截配置 |
| `custom` | 自定义命令 | `customCommand`: 自定义命令名 |
### 断言类型
- `be.visible` - 元素可见
- `exist` - 元素存在
- `contain` - 包含文本
- `have.attr` - 具有属性
- `have.value` - 具有值
- `have.class` - 具有CSS类
- `have.length` - 具有长度
## 📖 使用示例
### 示例1: UI验证测试
```json
{
"testSuite": {
"name": "布袋周期页面UI验证",
"module": "布袋周期",
"scenarios": [
{
"name": "应该显示页面核心组件",
"type": "ui",
"steps": [
{
"action": "verify",
"target": "{container}",
"value": { "assertion": "be.visible" },
"description": "检查主容器"
},
{
"action": "verify",
"target": "{searchForm}",
"value": { "assertion": "be.visible" },
"description": "检查搜索表单"
}
]
}
]
}
}
```
### 示例2: 交互功能测试
```json
{
"testSuite": {
"name": "搜索功能测试",
"module": "布袋周期",
"scenarios": [
{
"name": "应该能够搜索数据",
"type": "interaction",
"steps": [
{
"action": "type",
"target": "[data-testid=\"collector-compart-input\"]",
"value": "测试仓室",
"options": { "clear": true },
"description": "输入仓室名称"
},
{
"action": "click",
"target": "{searchButton}",
"description": "点击搜索按钮"
}
],
"expectedResults": [
{
"target": "{table}",
"assertion": "be.visible"
}
]
}
]
}
}
```
### 示例3: 错误处理测试
```json
{
"testSuite": {
"name": "错误处理测试",
"module": "布袋周期",
"scenarios": [
{
"name": "应该处理API错误",
"type": "error",
"steps": [
{
"action": "intercept",
"value": {
"method": "GET",
"url": "**/bag/cycle/getReplaceListPage",
"statusCode": 500,
"error": "服务器错误",
"alias": "apiError"
}
},
{
"action": "click",
"target": "{searchButton}"
}
],
"expectedResults": [
{
"target": "{container}",
"assertion": "be.visible"
}
]
}
]
}
}
```
## 🎯 选择器占位符
为了简化选择器编写,系统提供了以下占位符:
| 占位符 | 替换为 | 说明 |
|--------|--------|------|
| `{module}` | 模块ID | 当前模块的测试ID前缀 |
| `{container}` | `[data-testid="{module}-container"]` | 主容器选择器 |
| `{searchForm}` | `[data-testid="{module}-search-form"]` | 搜索表单选择器 |
| `{table}` | `[data-testid="{module}-common-table"]` | 表格选择器 |
| `{searchButton}` | `[data-testid="{module}-search-button"]` | 搜索按钮选择器 |
| `{resetButton}` | `[data-testid="{module}-reset-button"]` | 重置按钮选择器 |
## 🔧 自定义配置
可以通过修改 `config.js` 文件来自定义配置:
```javascript
module.exports = {
// 添加新的模块路由
moduleRoutes: {
'新模块': 'new-module'
},
// 添加新的选择器模板
commonSelectors: {
customButton: '[data-testid="{module}-custom-button"]'
},
// 修改输出配置
output: {
directory: 'cypress/e2e/auto-generated',
fileExtension: '.spec.js'
}
}
```
## 📝 最佳实践
### 1. 测试组织
- 按功能模块组织测试
- 使用清晰的测试场景命名
- 合理设置测试优先级
### 2. 选择器策略
- 优先使用 `data-testid` 属性
- 使用占位符简化选择器编写
- 避免依赖CSS类名和ID
### 3. 测试数据
- 使用有意义的测试数据
- 避免硬编码,使用配置化数据
- 考虑边界条件和异常情况
### 4. 错误处理
- 为每个功能添加错误处理测试
- 模拟网络异常和超时
- 验证错误提示和用户体验
## 🐛 故障排除
### 常见问题
1. **JSON格式错误**
- 检查JSON语法是否正确
- 使用Web界面的验证功能
- 查看详细错误信息
2. **模块不支持**
- 检查模块名称是否在支持列表中
- 参考config.js中的moduleRoutes配置
3. **选择器无效**
- 验证选择器是否正确
- 检查占位符替换是否正确
- 确认目标元素存在对应的data-testid
4. **生成的代码无法运行**
- 检查Cypress环境是否正确配置
- 验证自定义命令是否已定义
- 检查测试数据是否有效
## 🔄 集成到CI/CD
生成的测试可以直接集成到现有的CI/CD流程:
```bash
# 生成测试文件
node cypress/test-generator/cli.js generate test-descriptions/*.json
# 运行生成的测试
npm run test:generated
# 或使用本地CI脚本
./local-ci.sh business
```
## 📈 扩展开发
### 添加新的测试类型
1.`template-engine.js` 中添加新模板
2.`test-code-generator.js` 中添加处理逻辑
3. 更新 `schema.json` 中的类型定义
### 添加新的操作类型
1.`generateStep` 方法中添加新的case
2. 更新JSON Schema中的操作类型枚举
3. 在Web界面中添加相应的示例
## 🤝 贡献指南
欢迎提交问题报告和功能请求!
1. Fork 项目
2. 创建功能分支
3. 提交更改
4. 推送到分支
5. 创建Pull Request
## 📄 许可证
本项目基于 MIT 许可证开源。
\ No newline at end of file
This diff is collapsed.
/**
* 测试生成器核心配置
* DC-TOM 项目测试描述转测试脚本配置文件
*/
module.exports = {
// 项目基础配置
project: {
name: 'DC-TOM',
baseUrl: 'http://localhost:3000',
timeout: 10000
},
// 模块路由映射
moduleRoutes: {
'登录': 'login',
'仪表盘': 'dashboard',
'除尘器总览': 'dust-overview',
'布袋周期': 'collectorList',
'除尘监测': 'monitor',
'设备管理': 'management/device-management',
'闭环管理': 'my-loop',
'告警总览': 'alerts'
},
// 测试数据标识符前缀映射
moduleTestIds: {
'登录': 'login',
'仪表盘': 'dashboard',
'除尘器总览': 'dust',
'布袋周期': 'collector',
'除尘监测': 'dust-monitoring',
'设备管理': 'equipment',
'闭环管理': 'my-loop',
'告警总览': 'alerts'
},
// 常用组件选择器
commonSelectors: {
container: '[data-testid="{module}-container"]',
searchForm: '[data-testid="{module}-search-form"]',
table: '[data-testid="{module}-common-table"]',
searchButton: '[data-testid="{module}-search-button"]',
resetButton: '[data-testid="{module}-reset-button"]',
addButton: '[data-testid="{module}-add-button"]',
editButton: '[data-testid="{module}-edit-button"]',
deleteButton: '[data-testid="{module}-delete-button"]'
},
// Element Plus 组件选择器
elementSelectors: {
dialog: '.el-dialog',
dialogTitle: '.el-dialog__title',
dialogClose: '.el-dialog__headerbtn',
table: '.el-table',
tableHeader: '.el-table__header',
tableBody: '.el-table__body',
pagination: '.el-pagination',
select: '.el-select',
selectDropdown: '.el-select-dropdown',
selectItem: '.el-select-dropdown__item',
datePicker: '.el-date-editor',
pickerPanel: '.el-picker-panel',
button: '.el-button',
input: '.el-input__inner',
form: '.el-form',
formItem: '.el-form-item'
},
// 输出配置
output: {
directory: 'cypress/e2e/generated',
fileExtension: '.cy.js',
encoding: 'utf8'
}
}
\ No newline at end of file
This diff is collapsed.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DC-TOM 测试描述Schema",
"description": "用于描述DC-TOM项目测试场景的JSON结构规范",
"type": "object",
"required": ["testSuite"],
"properties": {
"testSuite": {
"type": "object",
"required": ["name", "module", "scenarios"],
"properties": {
"name": {
"type": "string",
"description": "测试套件名称",
"examples": ["布袋周期管理功能测试", "仪表盘功能测试"]
},
"module": {
"type": "string",
"description": "模块名称",
"enum": ["登录", "仪表盘", "除尘器总览", "布袋周期", "除尘监测", "设备管理", "闭环管理", "告警总览"]
},
"description": {
"type": "string",
"description": "测试套件描述"
},
"beforeEach": {
"type": "object",
"properties": {
"login": {
"type": "boolean",
"default": true,
"description": "是否需要登录"
},
"visit": {
"type": "string",
"description": "访问的页面路径"
},
"waitFor": {
"type": "array",
"items": {
"type": "string"
},
"description": "等待的元素"
}
}
},
"scenarios": {
"type": "array",
"items": {
"$ref": "#/definitions/testScenario"
}
}
}
}
},
"definitions": {
"testScenario": {
"type": "object",
"required": ["name", "type", "steps"],
"properties": {
"name": {
"type": "string",
"description": "测试场景名称"
},
"type": {
"type": "string",
"enum": ["ui", "interaction", "data", "error", "performance", "responsive"],
"description": "测试类型"
},
"description": {
"type": "string",
"description": "测试场景描述"
},
"priority": {
"type": "string",
"enum": ["high", "medium", "low"],
"default": "medium"
},
"steps": {
"type": "array",
"items": {
"$ref": "#/definitions/testStep"
}
},
"expectedResults": {
"type": "array",
"items": {
"$ref": "#/definitions/assertion"
}
}
}
},
"testStep": {
"type": "object",
"required": ["action"],
"properties": {
"action": {
"type": "string",
"enum": ["visit", "click", "type", "select", "wait", "verify", "intercept", "custom"],
"description": "操作类型"
},
"target": {
"type": "string",
"description": "目标元素选择器或自定义命令"
},
"value": {
"type": ["string", "number", "boolean", "object"],
"description": "输入值或参数"
},
"options": {
"type": "object",
"description": "额外选项"
},
"description": {
"type": "string",
"description": "步骤描述"
}
}
},
"assertion": {
"type": "object",
"required": ["target", "assertion"],
"properties": {
"target": {
"type": "string",
"description": "断言目标"
},
"assertion": {
"type": "string",
"enum": ["be.visible", "exist", "contain", "have.attr", "have.value", "have.class", "have.length"],
"description": "断言类型"
},
"value": {
"type": ["string", "number", "boolean"],
"description": "期望值"
},
"not": {
"type": "boolean",
"default": false,
"description": "是否为否定断言"
}
}
}
}
}
\ No newline at end of file
/**
* 测试模板引擎
* 负责管理和渲染测试模板
*/
const fs = require('fs');
const path = require('path');
class TestTemplateEngine {
constructor() {
this.templates = new Map();
this.loadTemplates();
}
/**
* 加载所有测试模板
*/
loadTemplates() {
const templatesDir = path.join(__dirname, 'templates');
// 基础模板
this.templates.set('base', this.createBaseTemplate());
this.templates.set('beforeEach', this.createBeforeEachTemplate());
this.templates.set('uiValidation', this.createUIValidationTemplate());
this.templates.set('interaction', this.createInteractionTemplate());
this.templates.set('dataValidation', this.createDataValidationTemplate());
this.templates.set('errorHandling', this.createErrorHandlingTemplate());
this.templates.set('responsive', this.createResponsiveTemplate());
this.templates.set('performance', this.createPerformanceTemplate());
}
/**
* 基础测试模板
*/
createBaseTemplate() {
return `/// <reference types="cypress" />
describe('{{testSuiteName}}', () => {
{{beforeEachBlock}}
{{testScenarios}}
})`;
}
/**
* beforeEach模板
*/
createBeforeEachTemplate() {
return `beforeEach(() => {
{{#if login}}
// 模拟登录状态
cy.mockLogin()
{{/if}}
{{#if visit}}
// 访问页面
cy.visit('/#/{{visit}}')
{{/if}}
{{#each waitFor}}
// 等待页面加载
cy.get('{{this}}').should('be.visible')
{{/each}}
})`;
}
/**
* UI验证模板
*/
createUIValidationTemplate() {
return `it('{{scenarioName}}', () => {
{{#each steps}}
{{#if (eq action "verify")}}
// {{description}}
cy.get('{{target}}').should('{{assertion.assertion}}'{{#if assertion.value}}, '{{assertion.value}}'{{/if}})
{{/if}}
{{/each}}
})`;
}
/**
* 交互测试模板
*/
createInteractionTemplate() {
return `it('{{scenarioName}}', () => {
{{#each steps}}
{{#if (eq action "click")}}
// {{description}}
cy.get('{{target}}').click()
{{/if}}
{{#if (eq action "type")}}
// {{description}}
cy.get('{{target}}'){{#if options.clear}}.clear(){{/if}}.type('{{value}}')
{{/if}}
{{#if (eq action "select")}}
// {{description}}
cy.get('{{target}}').click()
cy.get('.el-select-dropdown__item').contains('{{value}}').click()
{{/if}}
{{#if (eq action "wait")}}
// {{description}}
cy.wait({{value}})
{{/if}}
{{/each}}
{{#each expectedResults}}
// 验证结果
cy.get('{{target}}').should('{{assertion}}'{{#if value}}, '{{value}}'{{/if}})
{{/each}}
})`;
}
/**
* 数据验证模板
*/
createDataValidationTemplate() {
return `it('{{scenarioName}}', () => {
{{#each steps}}
{{#if (eq action "intercept")}}
// 模拟API响应
cy.intercept('{{value.method}}', '{{value.url}}', {{#if value.response}}{{json value.response}}{{else}}{ fixture: '{{value.fixture}}' }{{/if}}).as('{{value.alias}}')
{{/if}}
{{/each}}
{{#each steps}}
{{#unless (eq action "intercept")}}
{{> (concat action "Step") this}}
{{/unless}}
{{/each}}
{{#each expectedResults}}
cy.get('{{target}}').should('{{assertion}}'{{#if value}}, '{{value}}'{{/if}})
{{/each}}
})`;
}
/**
* 错误处理模板
*/
createErrorHandlingTemplate() {
return `it('{{scenarioName}}', () => {
{{#each steps}}
{{#if (eq action "intercept")}}
// 模拟错误响应
cy.intercept('{{value.method}}', '{{value.url}}', {
statusCode: {{value.statusCode}},
body: { error: '{{value.error}}' }
}).as('{{value.alias}}')
{{/if}}
{{/each}}
{{#each steps}}
{{#unless (eq action "intercept")}}
{{> (concat action "Step") this}}
{{/unless}}
{{/each}}
{{#each expectedResults}}
// 验证错误处理
cy.get('{{target}}').should('{{assertion}}'{{#if value}}, '{{value}}'{{/if}})
{{/each}}
})`;
}
/**
* 响应式设计模板
*/
createResponsiveTemplate() {
return `it('{{scenarioName}}', () => {
const viewports = [
{ width: 1920, height: 1080 }, // 桌面
{ width: 1280, height: 720 }, // 笔记本
{ width: 768, height: 1024 }, // 平板
{ width: 375, height: 667 } // 手机
]
viewports.forEach(viewport => {
cy.viewport(viewport.width, viewport.height)
{{#each expectedResults}}
cy.get('{{target}}').should('{{assertion}}'{{#if value}}, '{{value}}'{{/if}})
{{/each}}
})
})`;
}
/**
* 性能测试模板
*/
createPerformanceTemplate() {
return `it('{{scenarioName}}', () => {
// 记录页面加载时间
cy.window().then((win) => {
const performance = win.performance
const navigation = performance.getEntriesByType('navigation')[0]
// 验证页面加载时间
expect(navigation.loadEventEnd - navigation.loadEventStart).to.be.lessThan({{value}})
})
{{#each expectedResults}}
cy.get('{{target}}').should('{{assertion}}'{{#if value}}, '{{value}}'{{/if}})
{{/each}}
})`;
}
/**
* 渲染模板
* @param {string} templateName - 模板名称
* @param {object} context - 模板上下文数据
* @returns {string} 渲染后的代码
*/
render(templateName, context) {
const template = this.templates.get(templateName);
if (!template) {
throw new Error(`Template '${templateName}' not found`);
}
return this.processTemplate(template, context);
}
/**
* 处理模板字符串
* @param {string} template - 模板字符串
* @param {object} context - 上下文数据
* @returns {string} 处理后的字符串
*/
processTemplate(template, context) {
// 简单的模板处理逻辑(可以替换为更强大的模板引擎如Handlebars)
let result = template;
// 处理基本变量替换 {{variable}}
result = result.replace(/\{\{([^}]+)\}\}/g, (match, variable) => {
const value = this.getNestedValue(context, variable.trim());
return value !== undefined ? value : match;
});
// 处理条件语句 {{#if condition}}
result = this.processConditionals(result, context);
// 处理循环语句 {{#each array}}
result = this.processLoops(result, context);
return result;
}
/**
* 获取嵌套对象的值
*/
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined;
}, obj);
}
/**
* 处理条件语句
*/
processConditionals(template, context) {
return template.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, condition, content) => {
const value = this.getNestedValue(context, condition.trim());
return value ? content : '';
});
}
/**
* 处理循环语句
*/
processLoops(template, context) {
return template.replace(/\{\{#each\s+([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (match, arrayPath, content) => {
const array = this.getNestedValue(context, arrayPath.trim());
if (!Array.isArray(array)) return '';
return array.map((item, index) => {
const itemContext = { ...context, this: item, '@index': index };
return this.processTemplate(content, itemContext);
}).join('\n');
});
}
/**
* 获取所有可用模板
*/
getAvailableTemplates() {
return Array.from(this.templates.keys());
}
}
module.exports = TestTemplateEngine;
\ No newline at end of file
/**
* 测试代码生成器
* 负责将JSON测试描述转换为Cypress测试代码
*/
const fs = require('fs');
const path = require('path');
const TestTemplateEngine = require('./template-engine');
const config = require('./config');
class TestCodeGenerator {
constructor() {
this.templateEngine = new TestTemplateEngine();
this.config = config;
}
/**
* 从JSON描述生成测试代码
* @param {object} testDescription - JSON测试描述
* @returns {string} 生成的测试代码
*/
generateFromJSON(testDescription) {
// 验证JSON结构
this.validateTestDescription(testDescription);
const { testSuite } = testDescription;
// 构建模板上下文
const context = this.buildTemplateContext(testSuite);
// 生成测试代码
const testCode = this.generateTestCode(context);
return testCode;
}
/**
* 验证测试描述JSON结构
*/
validateTestDescription(testDescription) {
if (!testDescription.testSuite) {
throw new Error('测试描述必须包含 testSuite 字段');
}
const { testSuite } = testDescription;
if (!testSuite.name) {
throw new Error('测试套件必须包含 name 字段');
}
if (!testSuite.module) {
throw new Error('测试套件必须包含 module 字段');
}
if (!Array.isArray(testSuite.scenarios)) {
throw new Error('测试套件必须包含 scenarios 数组');
}
// 验证模块是否在支持列表中
if (!this.config.moduleRoutes[testSuite.module]) {
throw new Error(`不支持的模块: ${testSuite.module}`);
}
}
/**
* 构建模板上下文
*/
buildTemplateContext(testSuite) {
const moduleId = this.config.moduleTestIds[testSuite.module];
const routePath = this.config.moduleRoutes[testSuite.module];
const context = {
testSuiteName: testSuite.name,
moduleId: moduleId,
routePath: routePath,
beforeEach: this.buildBeforeEachContext(testSuite.beforeEach, routePath, moduleId),
scenarios: testSuite.scenarios.map(scenario => this.buildScenarioContext(scenario, moduleId))
};
return context;
}
/**
* 构建beforeEach上下文
*/
buildBeforeEachContext(beforeEach, routePath, moduleId) {
const defaultBeforeEach = {
login: true,
visit: routePath,
waitFor: [`[data-testid="${moduleId}-container"]`]
};
return { ...defaultBeforeEach, ...beforeEach };
}
/**
* 构建场景上下文
*/
buildScenarioContext(scenario, moduleId) {
return {
name: scenario.name,
type: scenario.type,
description: scenario.description,
priority: scenario.priority || 'medium',
steps: this.processSteps(scenario.steps, moduleId),
expectedResults: this.processExpectedResults(scenario.expectedResults, moduleId)
};
}
/**
* 处理测试步骤
*/
processSteps(steps, moduleId) {
return steps.map(step => {
const processedStep = { ...step };
// 处理目标选择器
if (step.target) {
processedStep.target = this.processSelector(step.target, moduleId);
}
// 处理特定操作的附加逻辑
switch (step.action) {
case 'search':
processedStep.action = 'custom';
processedStep.customCommand = `cy.search${this.capitalize(moduleId)}Data`;
break;
case 'reset':
processedStep.action = 'custom';
processedStep.customCommand = `cy.reset${this.capitalize(moduleId)}Search`;
break;
case 'openDialog':
processedStep.action = 'custom';
processedStep.customCommand = `cy.openAnalysisDialog`;
break;
}
return processedStep;
});
}
/**
* 处理期望结果
*/
processExpectedResults(expectedResults, moduleId) {
if (!expectedResults) return [];
return expectedResults.map(result => {
const processedResult = { ...result };
if (result.target) {
processedResult.target = this.processSelector(result.target, moduleId);
}
return processedResult;
});
}
/**
* 处理选择器,将占位符替换为实际值
*/
processSelector(selector, moduleId) {
// 替换 {module} 占位符
let processedSelector = selector.replace(/\{module\}/g, moduleId);
// 处理通用选择器
Object.keys(this.config.commonSelectors).forEach(key => {
const pattern = new RegExp(`\\{${key}\\}`, 'g');
const selectorTemplate = this.config.commonSelectors[key];
processedSelector = processedSelector.replace(pattern, selectorTemplate.replace(/\{module\}/g, moduleId));
});
return processedSelector;
}
/**
* 生成测试代码
*/
generateTestCode(context) {
let testCode = '';
// 生成文件头部
testCode += `/// <reference types="cypress" />\n\n`;
// 生成测试套件开始
testCode += `describe('${context.testSuiteName}', () => {\n`;
// 生成beforeEach块
testCode += this.generateBeforeEach(context.beforeEach);
// 生成测试场景
context.scenarios.forEach(scenario => {
testCode += this.generateScenario(scenario);
});
// 生成测试套件结束
testCode += '})\n';
return testCode;
}
/**
* 生成beforeEach块
*/
generateBeforeEach(beforeEachContext) {
let beforeEachCode = '\n beforeEach(() => {\n';
if (beforeEachContext.login) {
beforeEachCode += ' // 模拟登录状态\n';
beforeEachCode += ' cy.mockLogin()\n';
}
if (beforeEachContext.visit) {
beforeEachCode += ' \n';
beforeEachCode += ' // 访问页面\n';
beforeEachCode += ` cy.visit('/#/${beforeEachContext.visit}')\n`;
}
if (beforeEachContext.waitFor && beforeEachContext.waitFor.length > 0) {
beforeEachCode += ' \n';
beforeEachCode += ' // 等待页面加载完成\n';
beforeEachContext.waitFor.forEach(selector => {
beforeEachCode += ` cy.get('${selector}').should('be.visible')\n`;
});
}
beforeEachCode += ' })\n\n';
return beforeEachCode;
}
/**
* 生成测试场景
*/
generateScenario(scenario) {
let scenarioCode = ` it('${scenario.name}', () => {\n`;
// 生成测试步骤
scenario.steps.forEach((step, index) => {
if (index > 0) scenarioCode += '\n';
scenarioCode += this.generateStep(step);
});
// 生成期望结果验证
if (scenario.expectedResults && scenario.expectedResults.length > 0) {
scenarioCode += '\n // 验证结果\n';
scenario.expectedResults.forEach(result => {
scenarioCode += this.generateAssertion(result);
});
}
scenarioCode += ' })\n\n';
return scenarioCode;
}
/**
* 生成测试步骤
*/
generateStep(step) {
let stepCode = '';
if (step.description) {
stepCode += ` // ${step.description}\n`;
}
switch (step.action) {
case 'click':
stepCode += ` cy.get('${step.target}').click()\n`;
break;
case 'type':
const clearOption = step.options && step.options.clear ? '.clear()' : '';
stepCode += ` cy.get('${step.target}')${clearOption}.type('${step.value}')\n`;
break;
case 'select':
stepCode += ` cy.get('${step.target}').click()\n`;
stepCode += ` cy.get('.el-select-dropdown__item').contains('${step.value}').click()\n`;
break;
case 'wait':
stepCode += ` cy.wait(${step.value})\n`;
break;
case 'verify':
stepCode += this.generateAssertion({
target: step.target,
assertion: step.value.assertion,
value: step.value.value
});
break;
case 'intercept':
stepCode += this.generateIntercept(step.value);
break;
case 'custom':
if (step.customCommand) {
const params = step.value ? `('${step.value}')` : '()';
stepCode += ` ${step.customCommand}${params}\n`;
}
break;
}
return stepCode;
}
/**
* 生成断言
*/
generateAssertion(assertion) {
const notPrefix = assertion.not ? 'not.' : '';
const valueParam = assertion.value ? `, '${assertion.value}'` : '';
return ` cy.get('${assertion.target}').should('${notPrefix}${assertion.assertion}'${valueParam})\n`;
}
/**
* 生成API拦截
*/
generateIntercept(interceptConfig) {
let interceptCode = ` cy.intercept('${interceptConfig.method}', '${interceptConfig.url}'`;
if (interceptConfig.response) {
interceptCode += `, ${JSON.stringify(interceptConfig.response)}`;
} else if (interceptConfig.fixture) {
interceptCode += `, { fixture: '${interceptConfig.fixture}' }`;
}
interceptCode += `).as('${interceptConfig.alias}')\n`;
return interceptCode;
}
/**
* 首字母大写
*/
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* 生成测试文件
* @param {object} testDescription - JSON测试描述
* @param {string} outputPath - 输出文件路径
*/
generateTestFile(testDescription, outputPath) {
const testCode = this.generateFromJSON(testDescription);
// 确保输出目录存在
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 写入文件
fs.writeFileSync(outputPath, testCode, 'utf8');
return outputPath;
}
}
module.exports = TestCodeGenerator;
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="/tailwindcss/index.js"></script>
<link rel="stylesheet" href="/tailwindcss/index.css">
<title>DCTOM</title>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.diagonal-pattern {
background-image: repeating-linear-gradient(
45deg,
rgba(6, 241, 14, 0.829),
rgba(6, 241, 14, 0.829) 10px,
rgba(6, 241, 14, 0.829) 15px,
rgba(255, 255, 255, 0.1) 20px
);
background-size: 28px 28px;
}
.diagonal-pattern-animation {
animation: slide 1s linear infinite;
}
@keyframes slide {
0% {
background-position: 0 0;
}
100% {
background-position: 28px 0;
}
}
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
{
"name": "dctomproject",
"name": "cypress-testing-tool",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode development",
"build": "vite build",
"preview": "vite preview",
"cy:open": "cypress open",
"cy:open:studio": "node start-studio.cjs",
"studio": "node start-studio.cjs",
"studio:help": "node start-studio.cjs --help",
"studio:win": "start-studio.bat",
"cy:run": "cypress run",
"cy:run:studio": "cypress run --spec 'cypress/e2e/studio-generated/*.cy.js'",
"cy:run:ci": "cypress run --browser chrome --headless",
"cy:run:basic": "cypress run --browser chrome --headless --spec 'cypress/e2e/spec.cy.js'",
"cy:run:full": "cypress run --browser chrome --headless --spec 'cypress/e2e/*.cy.js'",
"cy:run:generated": "cypress run --browser chrome --headless --spec 'cypress/e2e/generated/*.cy.js'",
"test:reports": "mochawesome-merge cypress/reports/**/*.json -o merged-report.json && marge merged-report.json --reportDir reports --inline",
"cleanup:reports": "./cleanup-reports.sh",
"cleanup:reports:1": "./cleanup-reports.sh 1",
"cleanup:reports:5": "./cleanup-reports.sh 5",
"stats:extract": "./extract-stats.sh analyze",
"stats:show": "./extract-stats.sh show",
"ci:local": "./local-ci.sh",
"ci:basic": "./local-ci.sh basic",
"ci:full": "./local-ci.sh full",
"test:quick": "./quick-test.sh",
"test:spec": "./quick-test.sh cypress/e2e/spec.cy.js",
"test-gen:setup": "./test-generator.sh setup",
"test-gen:example": "./test-generator.sh example",
"test-gen:nl-example": "./test-generator.sh nl-example",
"test-gen:validate": "./test-generator.sh validate-all",
"test-gen:generate": "./test-generator.sh generate-all",
"test-gen:parse": "./test-generator.sh parse-all",
"test-gen:run": "./test-generator.sh run-generated",
"test-gen:clean": "./test-generator.sh clean",
"test-gen:web": "./test-generator.sh web",
"test-gen:help": "./test-generator.sh help"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.9.0",
"crypto-js": "^4.2.0",
"echarts": "^5.6.0",
"element-plus": "^2.9.10",
"gsap": "^3.13.0",
"js-cookie": "^3.0.5",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"pinia": "^3.0.2",
"qs": "^6.14.0",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
"cy:run:dev": "CYPRESS_BASE_URL=http://localhost:3000 cypress run",
"cy:run:staging": "CYPRESS_BASE_URL=https://staging.example.com cypress run",
"cy:run:prod": "CYPRESS_BASE_URL=https://app.example.com cypress run",
"cy:run:custom": "source .env.custom && cypress run",
"cy:run:chrome": "cypress run --browser chrome",
"cy:run:firefox": "cypress run --browser firefox",
"test:all": "npm run cy:run:dev && npm run cy:run:staging && npm run cy:run:prod",
"test:reports": "mochawesome-merge cypress/reports/**/*.json -o merged-report.json && marge merged-report.json --reportDir reports --inline"
},
"devDependencies": {
"@types/node": "^22.15.18",
"@vitejs/plugin-vue": "^5.2.3",
"cypress": "^13.6.0",
"mochawesome": "^7.1.3",
"mochawesome-merge": "^4.3.0",
"mochawesome-report-generator": "^6.2.0",
"sass": "^1.88.0",
"sass-loader": "^16.0.5",
"vite": "^6.3.5"
"mochawesome-report-generator": "^6.2.0"
}
}
}
\ No newline at end of file
#!/bin/bash
# DC-TOM 快速测试脚本
# 简化版本,专门用于快速验证和调试
set -e
# 颜色输出
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# 快速环境检查
quick_check() {
log_info "快速环境检查..."
if [ ! -f "package.json" ]; then
echo "错误: 请在项目根目录运行"
exit 1
fi
log_success "环境检查通过"
}
# 快速构建
quick_build() {
log_info "快速构建..."
if [ ! -d "node_modules" ]; then
log_info "安装依赖..."
npm install
fi
if [ ! -d "dist" ]; then
log_info "构建项目..."
npm run build
else
log_warning "使用现有构建 (dist/ 已存在)"
fi
log_success "构建完成"
}
# 运行单个测试
run_single_test() {
local spec_file=$1
log_info "运行测试: $spec_file"
# 启动服务器
npm run preview -- --port 3000 &
SERVER_PID=$!
sleep 10
# 运行测试
npx cypress run \
--browser chrome \
--headless \
--spec "$spec_file" \
--reporter mochawesome \
--reporter-options "reportDir=cypress/reports/quick,overwrite=false,html=true,json=true" \
|| log_warning "测试可能失败,但继续执行"
# 停止服务器
kill $SERVER_PID 2>/dev/null || true
log_success "测试完成"
}
# 生成简单报告
quick_report() {
log_info "生成快速报告..."
mkdir -p public/quick-reports
if [ -d "cypress/reports/quick" ]; then
# 复制报告文件
cp cypress/reports/quick/*.html public/quick-reports/ 2>/dev/null || true
# 生成简单索引
cat > public/quick-reports/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>快速测试报告</title>
<meta charset="utf-8">
</head>
<body>
<h1>DC-TOM 快速测试报告</h1>
<p>生成时间: <script>document.write(new Date().toLocaleString());</script></p>
<ul>
EOF
# 添加报告链接
for file in public/quick-reports/*.html; do
if [ "$file" != "public/quick-reports/index.html" ]; then
filename=$(basename "$file")
echo " <li><a href=\"$filename\">$filename</a></li>" >> public/quick-reports/index.html
fi
done
cat >> public/quick-reports/index.html << 'EOF'
</ul>
</body>
</html>
EOF
log_success "报告生成完成: public/quick-reports/"
# 打开报告
if command -v open &> /dev/null; then
open public/quick-reports/index.html
fi
else
log_warning "未找到测试报告"
fi
}
# 主函数
main() {
echo "================================"
echo " DC-TOM 快速测试脚本"
echo "================================"
local test_file=${1:-"cypress/e2e/spec.cy.js"}
quick_check
quick_build
run_single_test "$test_file"
quick_report
echo ""
log_success "快速测试完成!"
echo "使用方法: $0 [测试文件路径]"
echo "示例: $0 cypress/e2e/dashboard.cy.js"
}
# 清理函数
cleanup() {
if [ ! -z "$SERVER_PID" ]; then
kill $SERVER_PID 2>/dev/null || true
fi
}
trap cleanup EXIT
main "$@"
\ No newline at end of file
This diff is collapsed.
#!/bin/bash
# 通用测试执行脚本
# 用法: ./run-tests.sh [environment] [browser]
# 示例: ./run-tests.sh staging chrome
ENV=${1:-development}
BROWSER=${2:-chrome}
echo "开始执行 $ENV 环境的测试,使用 $BROWSER 浏览器"
# 根据环境设置变量
case $ENV in
"development"|"dev")
export CYPRESS_BASE_URL="http://localhost:3000"
export CYPRESS_REPORT_TITLE="Development Tests"
;;
"staging")
export CYPRESS_BASE_URL="https://staging.example.com"
export CYPRESS_REPORT_TITLE="Staging Tests"
;;
"production"|"prod")
export CYPRESS_BASE_URL="https://app.example.com"
export CYPRESS_REPORT_TITLE="Production Tests"
;;
"custom")
# 加载自定义环境变量
if [ -f ".env.custom" ]; then
source .env.custom
echo "已加载自定义环境配置"
else
echo "警告: 未找到 .env.custom 文件"
fi
;;
*)
# 支持直接指定URL
if [[ $ENV == http* ]]; then
export CYPRESS_BASE_URL=$ENV
export CYPRESS_REPORT_TITLE="Custom URL Tests"
echo "使用自定义URL: $ENV"
else
echo "未知环境: $ENV"
echo "支持的环境: development, staging, production, custom"
echo "或者直接指定URL,例如: ./run-tests.sh https://your-site.com chrome"
exit 1
fi
;;
esac
# 执行测试
npx cypress run --browser $BROWSER
echo "测试执行完成"
\ No newline at end of file
<template>
<router-view />
</template>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
\ No newline at end of file
<template >
<div class="hamburger" @click="toggleClick" data-testid="hamburger-menu">
<svg
:class="{'is-active':isActive}"
class="hamburger-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
</div>
</template>
<script setup>
import { ref, defineProps,defineEmits } from 'vue';
const emit = defineEmits(['toggleClick']);
const props = defineProps({
isActive: {
type: Boolean,
default: false
},
});
const toggleClick = ()=>{
emit('toggleClick');
}
</script>
<style scoped lang="scss">
.hamburger {
padding: 0 15px;
.hamburger-icon {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.is-active {
transform: rotate(180deg);
}
}
</style>
\ No newline at end of file
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<div class="page-container">
home
</div>
</template>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
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