Commit effe56e8 authored by Cai Wei's avatar Cai Wei

feat(*): 添加cypress测试流程

parent 301c1db6
name: Cypress E2E Tests
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master ]
jobs:
cypress-run:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Start application
run: npm run preview &
- name: Wait for application to start
run: npx wait-on http://localhost:4173 --timeout 60000
- name: Run Cypress tests
uses: cypress-io/github-action@v6
with:
start: npm run preview
wait-on: 'http://localhost:4173'
wait-on-timeout: 120
browser: chrome
record: true
parallel: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload screenshots
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots-${{ matrix.node-version }}
path: cypress/screenshots
- name: Upload videos
uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-videos-${{ matrix.node-version }}
path: cypress/videos
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-results-${{ matrix.node-version }}
path: cypress/reports
lighthouse:
runs-on: ubuntu-latest
needs: cypress-run
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build and start application
run: |
npm run build
npm run preview &
- name: Wait for application
run: npx wait-on http://localhost:4173 --timeout 60000
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli@0.12.x
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
\ No newline at end of file
# Cypress 测试启动指导
## 问题解决
你遇到的 `cy.visit() failed trying to load: http://localhost:5173/login` 错误已经修复。主要修复包括:
### 1. 路由修复
项目使用 Hash 路由模式,正确的 URL 格式应该是:
-`/#/login` (正确)
-`/login` (错误)
### 2. 权限控制处理
项目有路由守卫,需要 localStorage 中有 `menuList` 才能访问非登录页面。现在测试中使用 `cy.mockLogin()` 来模拟登录状态。
## 启动步骤
### 1. 启动开发服务器
```bash
npm run dev
```
确保服务器在 `http://localhost:5173` 启动
### 2. 运行 Cypress 测试
**选项A: 图形界面模式**
```bash
npm run cy:open
```
**选项B: 无头模式运行**
```bash
npm run cy:run
```
### 3. 验证修复
现在运行登录测试应该能够:
1. 正确访问 `/#/login` 页面
2. 显示登录表单元素
3. 处理路由守卫逻辑
## 测试覆盖
修复后的测试套件包括:
### 📋 登录功能 (`login.cy.js`)
- ✅ 登录页面元素显示
- ✅ 表单验证
- ✅ 密码可见性切换
- ✅ 验证码处理
- ✅ 路由守卫测试
### 📊 仪表板功能 (`dashboard.cy.js`)
- ✅ 核心组件渲染
- ✅ 健康度指标
- ✅ 图表和地图组件
- ✅ 响应式布局
### 📋 除尘器概览 (`dust-overview.cy.js`)
- ✅ 数据表格
- ✅ 搜索筛选
- ✅ 操作按钮
- ✅ 分页功能
### 🧭 导航菜单 (`navigation.cy.js`)
- ✅ 菜单显示
- ✅ 路由跳转
- ✅ 权限控制
## 注意事项
1. **确保开发服务器已启动**: 测试前必须先运行 `npm run dev`
2. **Hash 路由**: 所有 URL 都需要包含 `#` 符号
3. **权限模拟**: 使用 `cy.mockLogin()` 设置必要的 localStorage 数据
4. **网络请求**: 测试可能会产生实际的 API 请求,考虑使用 cy.intercept() 进行模拟
现在你可以重新运行 Cypress 测试,登录功能测试应该能够正常工作了!
\ No newline at end of file
# Cypress UI 自动化测试文档
## 概述
本项目使用 Cypress 进行 E2E (端到端) UI 自动化测试,覆盖了 DCTOM 除尘器智能管控平台的核心功能。
## 测试架构
### 技术栈
- **测试框架**: Cypress 13.6.0
- **前端框架**: Vue 3 + Element Plus
- **构建工具**: Vite
- **CI/CD**: GitHub Actions
### 测试标识符策略
所有可测试的 UI 元素都使用 `data-testid` 属性进行标识,遵循以下命名规范:
- 页面容器: `{page-name}-container`
- 表单输入: `{form-name}-{field-name}-input`
- 按钮: `{action-name}-button`
- 表格: `{table-name}-table`
- 卡片组件: `{card-name}-card`
### 目录结构
```
cypress/
├── e2e/ # E2E 测试用例
│ ├── login.cy.js # 登录功能测试
│ ├── dashboard.cy.js # 仪表板功能测试
│ ├── dust-overview.cy.js # 除尘器概览测试
│ └── navigation.cy.js # 导航菜单测试
├── fixtures/ # 测试数据
│ └── testData.json # 静态测试数据
├── support/ # 支持文件
│ ├── commands.js # 自定义命令
│ ├── e2e.js # E2E 配置
│ └── component.js # 组件测试配置
└── screenshots/ # 失败时的截图
└── videos/ # 测试录制视频
```
## 核心测试用例
### 1. 登录功能测试 (login.cy.js)
**测试范围:**
- 登录表单元素显示
- 表单验证
- 密码可见性切换
- 验证码刷新
- 记住密码功能
- 键盘导航
- 响应式设计
**关键测试点:**
- 用户名/密码输入验证
- 验证码图片加载和刷新
- 登录状态处理
### 2. 仪表板功能测试 (dashboard.cy.js)
**测试范围:**
- 核心组件渲染
- 健康度指标显示
- 图表数据加载
- 地图组件交互
- 数据更新响应
**关键测试点:**
- 健康度数值格式验证
- 进度条颜色状态
- 图表内容检查
- 响应式布局
### 3. 除尘器概览测试 (dust-overview.cy.js)
**测试范围:**
- 统计卡片显示
- 搜索和筛选功能
- 数据表格操作
- 分页功能
- 新增/编辑操作
**关键测试点:**
- 搜索功能验证
- 表格数据加载
- 操作按钮交互
- 弹窗处理
### 4. 导航菜单测试 (navigation.cy.js)
**测试范围:**
- 菜单项显示
- 导航跳转
- 子菜单展开/收起
- 活跃状态高亮
- 移动端适配
**关键测试点:**
- 菜单权限控制
- 路由跳转验证
- 菜单状态管理
## 自定义命令
项目提供了以下自定义 Cypress 命令:
### 登录相关
```javascript
cy.loginWithUI(username, password) // UI 登录
```
### 导航相关
```javascript
cy.navigateToPage(pageName) // 页面导航
```
### 表格相关
```javascript
cy.checkTableDataLoaded(selector) // 检查表格数据加载
cy.searchInTable(searchText) // 表格搜索
```
### 响应式测试
```javascript
cy.checkResponsive() // 检查响应式设计
```
### 健康度验证
```javascript
cy.verifyHealthIndicators() // 验证健康度指标
```
## 运行测试
### 本地开发
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 打开 Cypress 测试界面
npm run cy:open
# 无头模式运行所有测试
npm run cy:run
```
### CI/CD 环境
测试会在以下情况自动触发:
- 推送到 master 或 develop 分支
- 创建 Pull Request 到 master 分支
```bash
# CI 环境运行命令
npm run cy:run:ci
```
## 配置说明
### Cypress 配置 (cypress.config.js)
```javascript
{
baseUrl: 'http://localhost:5173', // 应用基础URL
viewportWidth: 1280, // 默认视口宽度
viewportHeight: 720, // 默认视口高度
defaultCommandTimeout: 10000, // 命令超时时间
video: true, // 录制视频
screenshotOnRunFailure: true // 失败时截图
}
```
### 环境变量
```javascript
env: {
apiUrl: 'http://localhost:3000', // API 地址
username: 'test@example.com', // 测试用户名
password: 'testpassword' // 测试密码
}
```
## 最佳实践
### 1. 测试标识符使用
- 优先使用 `data-testid` 属性
- 避免依赖 CSS 类名或 ID
- 保持标识符语义化和一致性
### 2. 等待策略
```javascript
// ✅ 好的做法
cy.get('[data-testid="submit-button"]').should('be.visible')
// ❌ 避免硬编码等待
cy.wait(5000)
```
### 3. 测试数据管理
- 使用 fixtures 管理静态测试数据
- 在测试前重置数据状态
- 避免测试间的数据依赖
### 4. 断言策略
```javascript
// 检查元素存在性
cy.get('[data-testid="element"]').should('exist')
// 检查元素可见性
cy.get('[data-testid="element"]').should('be.visible')
// 检查元素内容
cy.get('[data-testid="element"]').should('contain', 'Expected Text')
```
## 报告和分析
### 测试结果
- 测试通过/失败统计
- 执行时间分析
- 错误截图和视频录制
### 性能监控
- 集成 Lighthouse CI 进行性能测试
- 监控页面加载时间
- 检查可访问性和 SEO 指标
### CI/CD 集成
- GitHub Actions 自动运行测试
- 测试结果通知
- 失败时的调试信息收集
## 故障排除
### 常见问题
1. **元素未找到**
- 检查 `data-testid` 是否正确
- 确认元素是否在 DOM 中渲染
- 添加适当的等待条件
2. **测试超时**
- 增加命令超时时间
- 优化等待策略
- 检查网络请求响应时间
3. **跨浏览器兼容性**
- 在多个浏览器中测试
- 检查浏览器特定的行为差异
### 调试技巧
```javascript
// 添加调试断点
cy.debug()
// 暂停测试执行
cy.pause()
// 在浏览器控制台输出
cy.window().then(console.log)
```
## 扩展计划
1. **组件测试**: 添加 Vue 组件的单元测试
2. **API 测试**: 集成 API 接口测试
3. **视觉回归测试**: 添加 UI 视觉对比测试
4. **性能测试**: 扩展性能监控覆盖范围
5. **移动端测试**: 增加移动设备测试场景
## 维护指南
### 定期维护任务
- 更新测试数据
- 清理过时的测试用例
- 优化测试执行时间
- 更新文档和最佳实践
### 团队协作
- 代码审查测试用例
- 分享测试经验和技巧
- 建立测试用例编写标准
- 定期评估测试覆盖率
\ No newline at end of file
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000,
experimentalStudio: true,
setupNodeEvents(on, config) {
// 实现node事件监听器
},
env: {
// 环境变量
apiUrl: 'http://localhost:3000',
username: 'test@example.com',
password: 'testpassword'
},
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.js'
},
component: {
devServer: {
framework: 'vue',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx,vue}',
supportFile: 'cypress/support/component.js'
}
})
\ No newline at end of file
# CollectorList 组件 Cypress 测试说明
## 概述
本项目为 CollectorList(布袋周期管理)组件提供了完整的 Cypress 端到端测试覆盖,包括基础功能测试、高级功能测试和简化测试。
## 测试文件结构
```
cypress/e2e/
├── collector-list.cy.js # 基础功能测试
├── collector-list-advanced.cy.js # 高级功能测试(包含API模拟)
├── collector-list-simple.cy.js # 简化测试(使用自定义命令)
└── README-collector-list.md # 本说明文档
```
## 测试覆盖范围
### 1. 基础功能测试 (`collector-list.cy.js`)
**测试场景:**
- 页面组件显示验证
- 搜索表单字段验证
- 搜索功能测试
- 重置功能测试
- 日期选择器测试
- 表格数据显示
- 分析对话框功能
- 分页功能验证
- 表格列标题验证
- 空数据处理
- 响应式设计验证
- 输入验证
- 表格交互性
- 页面性能测试
- 错误处理
- 数据刷新功能
### 2. 高级功能测试 (`collector-list-advanced.cy.js`)
**测试场景:**
- API 模拟和数据验证
- 精确搜索功能
- 分页数据处理
- 对话框完整操作流程
- 日期范围搜索
- 空搜索结果处理
- API 错误处理
- 表格排序功能
- 数据完整性验证
- 性能指标验证
- 输入验证边界测试
- 响应式布局测试
### 3. 简化测试 (`collector-list-simple.cy.js`)
**测试场景:**
- 使用自定义命令的基础功能测试
- 页面组件验证
- 搜索和重置功能
- 对话框操作
- 表格验证
- 响应式设计
- 性能测试
## 自定义命令
`cypress/support/commands.js` 中添加了以下专门用于 CollectorList 测试的自定义命令:
### 导航命令
- `navigateToCollectorList()` - 导航到布袋周期页面
### 搜索命令
- `searchCollectorData(compart, dusterName, dateRange)` - 搜索布袋周期数据
- `resetCollectorSearch()` - 重置搜索条件
### 对话框命令
- `openAnalysisDialog(method)` - 打开更换周期分析对话框
- `closeAnalysisDialog()` - 关闭分析对话框
### 验证命令
- `verifyCollectorTableData(expectedData)` - 验证表格数据完整性
- `verifyCollectorTableHeaders()` - 验证表格列标题
### API 模拟命令
- `mockCollectorAPI(apiType, responseData)` - 模拟 API 响应
### 工具命令
- `waitForPageLoad()` - 等待页面加载完成
## 测试数据
`cypress/fixtures/testData.json` 中添加了专门的测试数据:
### collectorListData
包含布袋周期列表的模拟数据,包括:
- 除尘器名称
- 仓室信息
- 布袋位置
- 更换时间
- 更换人
- 更换周期
### dustAnalysisData
包含分析相关的模拟数据,包括:
- 除尘器列表
- 分析图表数据
## 运行测试
### 运行所有 CollectorList 测试
```bash
npx cypress run --spec "cypress/e2e/collector-list*.cy.js"
```
### 运行特定测试文件
```bash
# 基础功能测试
npx cypress run --spec "cypress/e2e/collector-list.cy.js"
# 高级功能测试
npx cypress run --spec "cypress/e2e/collector-list-advanced.cy.js"
# 简化测试
npx cypress run --spec "cypress/e2e/collector-list-simple.cy.js"
```
### 在浏览器中运行测试
```bash
npx cypress open
```
然后在 Cypress 测试运行器中选择相应的测试文件。
## 测试环境要求
1. **登录状态模拟** - 测试使用 `mockLogin()` 命令模拟用户登录状态
2. **API 模拟** - 高级测试使用 `cy.intercept()` 模拟 API 响应
3. **测试数据** - 使用 `cy.fixture()` 加载测试数据
## 注意事项
1. **data-testid 属性** - 确保 CollectorList 组件中的元素包含正确的 `data-testid` 属性
2. **API 路径** - 测试中的 API 路径需要与实际后端 API 路径匹配
3. **日期选择器** - 日期选择器的测试可能需要根据实际的 Element UI 实现进行调整
4. **响应式测试** - 响应式测试在不同浏览器中可能有不同的表现
## 维护和扩展
### 添加新的测试场景
1. 在相应的测试文件中添加新的 `it()` 测试用例
2. 如果需要,在 `commands.js` 中添加新的自定义命令
3.`testData.json` 中添加相应的测试数据
### 修改现有测试
1. 确保修改后的测试仍然覆盖原有的功能
2. 更新相关的自定义命令和测试数据
3. 验证测试在不同环境下的稳定性
### 调试测试
1. 使用 `cy.debug()``cy.pause()` 进行调试
2. 查看 Cypress 测试运行器的实时日志
3. 使用 `cy.screenshot()` 捕获测试失败时的截图
## 最佳实践
1. **测试隔离** - 每个测试用例应该是独立的,不依赖其他测试的状态
2. **数据清理** - 在测试完成后清理测试数据
3. **错误处理** - 测试应该能够处理各种错误情况
4. **性能考虑** - 避免不必要的等待时间,使用适当的断言
5. **可读性** - 使用描述性的测试名称和清晰的测试结构
/// <reference types="cypress" />
describe('布袋周期管理高级功能测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 模拟API响应
cy.fixture('testData').then((testData) => {
// 模拟获取布袋周期列表API
cy.intercept('GET', '**/bag/cycle/getReplaceListPage', {
statusCode: 200,
body: {
code: 200,
data: testData.collectorListData,
message: 'success'
}
}).as('getCollectorList')
// 模拟获取除尘器列表API
cy.intercept('GET', '**/bag/cycle/getDusterList', {
statusCode: 200,
body: {
code: 200,
data: testData.dustAnalysisData.dusterList,
message: 'success'
}
}).as('getDusterList')
// 模拟获取分析数据API
cy.intercept('GET', '**/bag/cycle/getReplaceAnalysis', {
statusCode: 200,
body: {
code: 200,
data: testData.dustAnalysisData.analysisData,
message: 'success'
}
}).as('getAnalysisData')
})
// 访问布袋周期页面
cy.visit('/#/collectorList')
// 等待页面加载完成
cy.get('[data-testid="collector-list-container"]').should('be.visible')
})
it('应该正确加载和显示表格数据', () => {
// 等待API请求完成
cy.wait('@getCollectorList')
// 验证表格数据
cy.get('[data-testid="collector-common-table"]').within(() => {
// 检查表格行数量
cy.get('.el-table__body tr').should('have.length', 3)
// 验证第一行数据
cy.get('.el-table__body tr').first().within(() => {
cy.get('td').eq(1).should('contain', '1#除尘器')
cy.get('td').eq(2).should('contain', 'A仓室')
cy.get('td').eq(3).should('contain', '1排/1列')
cy.get('td').eq(4).should('contain', '2024-02-15')
cy.get('td').eq(5).should('contain', '2024-01-15')
cy.get('td').eq(6).should('contain', '张三')
cy.get('td').eq(7).should('contain', '30天')
})
})
})
it('应该能够进行精确搜索', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 模拟搜索API响应
cy.intercept('GET', '**/bag/cycle/getReplaceListPage*', {
statusCode: 200,
body: {
code: 200,
data: {
records: [
{
id: 1,
dusterName: '1#除尘器',
compart: 'A仓室',
bagLocation: '1排/1列',
bagChangeNextTime: '2024-02-15 10:00:00',
bagChangeTime: '2024-01-15 10:00:00',
bagChangeAuthor: '张三',
bagChangePeriod: '30天'
}
],
total: 1,
pageNo: 1,
pageSize: 20
},
message: 'success'
}
}).as('searchCollectorList')
// 输入搜索条件
cy.get('[data-testid="collector-compart-input"]').type('A仓室')
cy.get('[data-testid="collector-duster-name-input"]').type('1#除尘器')
// 点击搜索
cy.get('[data-testid="collector-search-button"]').click()
// 等待搜索结果
cy.wait('@searchCollectorList')
// 验证搜索结果
cy.get('[data-testid="collector-common-table"]').within(() => {
cy.get('.el-table__body tr').should('have.length', 1)
cy.get('.el-table__body tr').first().within(() => {
cy.get('td').eq(1).should('contain', '1#除尘器')
cy.get('td').eq(2).should('contain', 'A仓室')
})
})
})
it('应该能够处理分页功能', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 模拟第二页数据
cy.intercept('GET', '**/bag/cycle/getReplaceListPage*pageNo=2*', {
statusCode: 200,
body: {
code: 200,
data: {
records: [
{
id: 4,
dusterName: '4#除尘器',
compart: 'D仓室',
bagLocation: '2排/2列',
bagChangeNextTime: '2024-03-01 10:00:00',
bagChangeTime: '2024-02-01 10:00:00',
bagChangeAuthor: '赵六',
bagChangePeriod: '28天'
}
],
total: 4,
pageNo: 2,
pageSize: 20
},
message: 'success'
}
}).as('getPage2Data')
// 点击下一页
cy.get('[data-testid="collector-common-table"]').within(() => {
cy.get('.el-pagination .btn-next').click()
})
// 等待第二页数据加载
cy.wait('@getPage2Data')
// 验证第二页数据
cy.get('[data-testid="collector-common-table"]').within(() => {
cy.get('.el-table__body tr').should('have.length', 1)
cy.get('.el-table__body tr').first().within(() => {
cy.get('td').eq(1).should('contain', '4#除尘器')
cy.get('td').eq(2).should('contain', 'D仓室')
})
})
})
it('应该能够打开并操作分析对话框', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
cy.wait('@getDusterList')
cy.wait('@getAnalysisData')
// 双击除尘器名称打开对话框
cy.get('[data-testid="collector-duster-name-link"]').first().dblclick()
// 验证对话框打开
cy.get('.dustListDialog').should('be.visible')
cy.get('.el-dialog__title').should('contain', '更换周期分析')
// 验证对话框内容
cy.get('.dustListDialog').within(() => {
// 检查除尘器选择器
cy.get('.input-group').should('be.visible')
cy.get('.el-select').should('be.visible')
// 检查图表区域
cy.get('.echartBox').should('be.visible')
})
// 测试除尘器切换
cy.get('.dustListDialog .el-select').click()
cy.get('.el-select-dropdown').should('be.visible')
cy.get('.el-select-dropdown .el-select-dropdown__item').eq(1).click()
// 验证选择器关闭
cy.get('.el-select-dropdown').should('not.exist')
// 关闭对话框
cy.get('.dustListDialog .el-dialog__headerbtn').click()
cy.get('.dustListDialog').should('not.exist')
})
it('应该能够处理日期范围搜索', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 模拟日期搜索API响应
cy.intercept('GET', '**/bag/cycle/getReplaceListPage*', {
statusCode: 200,
body: {
code: 200,
data: {
records: [
{
id: 2,
dusterName: '2#除尘器',
compart: 'B仓室',
bagLocation: '2排/1列',
bagChangeNextTime: '2024-02-20 10:00:00',
bagChangeTime: '2024-01-20 10:00:00',
bagChangeAuthor: '李四',
bagChangePeriod: '25天'
}
],
total: 1,
pageNo: 1,
pageSize: 20
},
message: 'success'
}
}).as('dateSearchResult')
// 设置日期范围
cy.get('[data-testid="collector-date-picker"]').click()
cy.get('.el-picker-panel').should('be.visible')
// 选择日期范围(这里需要根据实际的日期选择器实现调整)
cy.get('.el-picker-panel').within(() => {
// 选择开始日期
cy.get('.el-date-table td.available').first().click()
// 选择结束日期
cy.get('.el-date-table td.available').eq(5).click()
})
// 点击搜索
cy.get('[data-testid="collector-search-button"]').click()
// 等待搜索结果
cy.wait('@dateSearchResult')
// 验证搜索结果
cy.get('[data-testid="collector-common-table"]').within(() => {
cy.get('.el-table__body tr').should('have.length', 1)
})
})
it('应该能够处理空搜索结果', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 模拟空搜索结果
cy.intercept('GET', '**/bag/cycle/getReplaceListPage*', {
statusCode: 200,
body: {
code: 200,
data: {
records: [],
total: 0,
pageNo: 1,
pageSize: 20
},
message: 'success'
}
}).as('emptySearchResult')
// 输入不存在的搜索条件
cy.get('[data-testid="collector-compart-input"]').type('不存在的仓室')
cy.get('[data-testid="collector-search-button"]').click()
// 等待搜索结果
cy.wait('@emptySearchResult')
// 验证空数据状态
cy.get('[data-testid="collector-common-table"]').should('be.visible')
cy.get('[data-testid="collector-common-table"]').within(() => {
// 检查是否有空数据提示或空表格
cy.get('.el-table__body').should('exist')
})
})
it('应该能够处理API错误', () => {
// 模拟API错误
cy.intercept('GET', '**/bag/cycle/getReplaceListPage', {
statusCode: 500,
body: {
code: 500,
message: '服务器内部错误'
}
}).as('apiError')
// 刷新页面或触发搜索
cy.get('[data-testid="collector-search-button"]').click()
// 等待错误响应
cy.wait('@apiError')
// 验证页面仍然可用
cy.get('[data-testid="collector-list-container"]').should('be.visible')
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该验证表格排序功能', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 检查表格列头是否可点击(排序功能)
cy.get('[data-testid="collector-common-table"]').within(() => {
// 检查除尘器名称列是否可以排序
cy.get('.el-table__header th').contains('除尘器名称').should('be.visible')
// 检查仓室列是否可以排序
cy.get('.el-table__header th').contains('仓室').should('be.visible')
})
})
it('应该验证表格数据的完整性', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 验证所有必需的列都存在
cy.get('[data-testid="collector-common-table"]').within(() => {
const expectedColumns = [
'序号',
'除尘器名称',
'仓室',
'布袋位置(排/列)',
'布袋更换提醒时间',
'更换时间',
'更换人',
'更换周期(与上次更换比)'
]
expectedColumns.forEach(columnName => {
cy.get('.el-table__header').should('contain', columnName)
})
})
})
it('应该验证除尘器名称链接的交互性', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 验证除尘器名称链接的样式和交互
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'health-score')
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'green-color')
// 验证链接可以点击
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.css', 'cursor', 'pointer')
})
it('应该验证页面性能指标', () => {
// 等待页面完全加载
cy.wait('@getCollectorList')
// 检查页面性能
cy.window().then((win) => {
const performance = win.performance
const navigation = performance.getEntriesByType('navigation')[0]
// 验证DOM内容加载时间
expect(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart).to.be.lessThan(3000)
// 验证页面完全加载时间
expect(navigation.loadEventEnd - navigation.loadEventStart).to.be.lessThan(5000)
})
})
it('应该验证搜索表单的验证功能', () => {
// 测试输入框的最大长度限制
const longText = 'a'.repeat(1000)
cy.get('[data-testid="collector-compart-input"]').type(longText)
cy.get('[data-testid="collector-compart-input"]').should('have.value', longText)
cy.get('[data-testid="collector-duster-name-input"]').type(longText)
cy.get('[data-testid="collector-duster-name-input"]').should('have.value', longText)
// 清除输入
cy.get('[data-testid="collector-compart-input"]').clear()
cy.get('[data-testid="collector-duster-name-input"]').clear()
})
it('应该验证表格的响应式布局', () => {
// 等待初始数据加载
cy.wait('@getCollectorList')
// 测试不同屏幕尺寸下的表格显示
const viewports = [
{ width: 1920, height: 1080 },
{ width: 1280, height: 720 },
{ width: 768, height: 1024 }
]
viewports.forEach(viewport => {
cy.viewport(viewport.width, viewport.height)
// 验证表格在不同尺寸下仍然可见
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 验证表格内容可以滚动(如果需要)
cy.get('[data-testid="collector-common-table"]').within(() => {
cy.get('.el-table__body-wrapper').should('be.visible')
})
})
})
})
/// <reference types="cypress" />
describe('布袋周期管理简化测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 导航到布袋周期页面
cy.navigateToCollectorList()
})
it('应该正确显示页面组件', () => {
// 验证页面组件
cy.get('[data-testid="collector-list-container"]').should('be.visible')
cy.get('[data-testid="collector-list-search-form"]').should('be.visible')
cy.get('[data-testid="collector-table-container"]').should('be.visible')
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该验证搜索表单字段', () => {
// 验证搜索表单字段
cy.get('[data-testid="collector-compart-input"]').should('be.visible')
cy.get('[data-testid="collector-duster-name-input"]').should('be.visible')
cy.get('[data-testid="collector-date-picker"]').should('be.visible')
cy.get('[data-testid="collector-reset-button"]').should('be.visible')
cy.get('[data-testid="collector-search-button"]').should('be.visible')
cy.get('[data-testid="collector-analysis-button"]').should('be.visible')
})
it('应该能够进行搜索操作', () => {
// 使用自定义命令进行搜索
cy.searchCollectorData('测试仓室', '测试除尘器')
// 验证表格仍然可见
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该能够重置搜索条件', () => {
// 先输入一些搜索条件
cy.get('[data-testid="collector-compart-input"]').type('测试数据')
cy.get('[data-testid="collector-duster-name-input"]').type('测试数据')
// 使用自定义命令重置
cy.resetCollectorSearch()
// 验证表格仍然可见
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该能够打开分析对话框', () => {
// 使用自定义命令打开对话框
cy.openAnalysisDialog('button')
// 验证对话框内容
cy.get('.dustListDialog').within(() => {
cy.get('.input-group').should('be.visible')
cy.get('.el-select').should('be.visible')
cy.get('.echartBox').should('be.visible')
})
// 关闭对话框
cy.closeAnalysisDialog()
})
it('应该能够通过双击除尘器名称打开对话框', () => {
// 使用自定义命令通过双击打开对话框
cy.openAnalysisDialog('dblclick')
// 关闭对话框
cy.closeAnalysisDialog()
})
it('应该验证表格列标题', () => {
// 使用自定义命令验证表格列标题
cy.verifyCollectorTableHeaders()
})
it('应该验证页面响应式设计', () => {
// 使用现有的响应式检查命令
cy.checkResponsive()
})
it('应该验证除尘器名称链接样式', () => {
// 验证除尘器名称链接的样式
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'health-score')
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'green-color')
})
it('应该验证页面加载性能', () => {
// 验证页面加载性能
cy.window().then((win) => {
const performance = win.performance
const navigation = performance.getEntriesByType('navigation')[0]
// 验证页面加载时间在合理范围内
expect(navigation.loadEventEnd - navigation.loadEventStart).to.be.lessThan(10000)
})
})
})
/// <reference types="cypress" />
describe('布袋周期管理功能测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 访问布袋周期页面
cy.visit('/#/collectorList')
// 等待页面加载完成
cy.get('[data-testid="collector-list-container"]').should('be.visible')
})
it('应该显示布袋周期页面的所有核心组件', () => {
// 检查主容器
cy.get('[data-testid="collector-list-container"]').should('be.visible')
// 检查内容区域
cy.get('[data-testid="collector-list-content"]').should('be.visible')
// 检查搜索表单
cy.get('[data-testid="collector-list-search-form"]').should('be.visible')
// 检查表格容器
cy.get('[data-testid="collector-table-container"]').should('be.visible')
// 检查通用表格组件
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该显示搜索表单的所有输入字段', () => {
// 检查仓室输入框
cy.get('[data-testid="collector-compart-input"]')
.should('be.visible')
.and('have.attr', 'placeholder', '请输入仓室名称')
// 检查除尘器名称输入框
cy.get('[data-testid="collector-duster-name-input"]')
.should('be.visible')
.and('have.attr', 'placeholder', '请输入除尘器名称')
// 检查日期选择器
cy.get('[data-testid="collector-date-picker"]').should('be.visible')
// 检查按钮
cy.get('[data-testid="collector-reset-button"]').should('be.visible')
cy.get('[data-testid="collector-search-button"]').should('be.visible')
cy.get('[data-testid="collector-analysis-button"]').should('be.visible')
})
it('应该能够进行搜索操作', () => {
// 输入仓室名称
cy.get('[data-testid="collector-compart-input"]')
.clear()
.type('测试仓室')
// 输入除尘器名称
cy.get('[data-testid="collector-duster-name-input"]')
.clear()
.type('测试除尘器')
// 点击查询按钮
cy.get('[data-testid="collector-search-button"]').click()
// 等待搜索结果加载
cy.wait(1000)
// 验证表格仍然可见
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该能够重置搜索条件', () => {
// 先输入一些搜索条件
cy.get('[data-testid="collector-compart-input"]').type('测试数据')
cy.get('[data-testid="collector-duster-name-input"]').type('测试数据')
// 点击重置按钮
cy.get('[data-testid="collector-reset-button"]').click()
// 验证输入框已清空
cy.get('[data-testid="collector-compart-input"]').should('have.value', '')
cy.get('[data-testid="collector-duster-name-input"]').should('have.value', '')
// 验证表格仍然可见
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该能够设置日期范围', () => {
// 点击日期选择器
cy.get('[data-testid="collector-date-picker"]').click()
// 等待日期选择器弹出
cy.get('.el-picker-panel').should('be.visible')
// 选择开始日期(这里需要根据实际的日期选择器实现调整)
cy.get('.el-picker-panel').within(() => {
// 选择今天的日期作为开始日期
cy.get('.el-date-table td.available').first().click()
// 选择明天的日期作为结束日期
cy.get('.el-date-table td.available').eq(1).click()
})
// 验证日期选择器关闭
cy.get('.el-picker-panel').should('not.exist')
})
it('应该显示表格数据', () => {
// 等待表格加载
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 检查表格是否有数据行
cy.get('[data-testid="collector-common-table"]').within(() => {
// 检查表格行是否存在(即使为空数据)
cy.get('.el-table__body-wrapper').should('be.visible')
})
})
it('应该能够点击除尘器名称打开分析对话框', () => {
// 等待表格加载
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 查找除尘器名称链接并双击
cy.get('[data-testid="collector-duster-name-link"]').first().dblclick()
// 验证对话框打开
cy.get('.dustListDialog').should('be.visible')
cy.get('.el-dialog__title').should('contain', '更换周期分析')
// 验证对话框内容
cy.get('.dustListDialog').within(() => {
// 检查除尘器名称选择器
cy.get('.input-group').should('be.visible')
cy.get('.el-select').should('be.visible')
// 检查图表区域
cy.get('.echartBox').should('be.visible')
})
})
it('应该能够通过分析按钮打开对话框', () => {
// 点击更换周期分析按钮
cy.get('[data-testid="collector-analysis-button"]').click()
// 验证对话框打开
cy.get('.dustListDialog').should('be.visible')
cy.get('.el-dialog__title').should('contain', '更换周期分析')
})
it('应该能够关闭分析对话框', () => {
// 打开对话框
cy.get('[data-testid="collector-analysis-button"]').click()
cy.get('.dustListDialog').should('be.visible')
// 点击关闭按钮
cy.get('.dustListDialog .el-dialog__headerbtn').click()
// 验证对话框关闭
cy.get('.dustListDialog').should('not.exist')
})
it('应该能够切换除尘器选择', () => {
// 打开对话框
cy.get('[data-testid="collector-analysis-button"]').click()
cy.get('.dustListDialog').should('be.visible')
// 点击除尘器选择器
cy.get('.dustListDialog .el-select').click()
// 等待下拉选项出现
cy.get('.el-select-dropdown').should('be.visible')
// 选择第一个选项(如果存在)
cy.get('.el-select-dropdown .el-select-dropdown__item').first().click()
// 验证选择器关闭
cy.get('.el-select-dropdown').should('not.exist')
})
it('应该验证表格分页功能', () => {
// 等待表格加载
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 检查分页组件是否存在
cy.get('[data-testid="collector-common-table"]').within(() => {
// 查找分页组件
cy.get('.el-pagination').should('be.visible')
})
})
it('应该验证表格列标题', () => {
// 等待表格加载
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 检查表格列标题
cy.get('[data-testid="collector-common-table"]').within(() => {
// 检查主要列标题
cy.get('.el-table__header').should('contain', '序号')
cy.get('.el-table__header').should('contain', '除尘器名称')
cy.get('.el-table__header').should('contain', '仓室')
cy.get('.el-table__header').should('contain', '布袋位置(排/列)')
cy.get('.el-table__header').should('contain', '布袋更换提醒时间')
cy.get('.el-table__header').should('contain', '更换时间')
cy.get('.el-table__header').should('contain', '更换人')
cy.get('.el-table__header').should('contain', '更换周期(与上次更换比)')
})
})
it('应该处理空数据状态', () => {
// 输入一个不存在的搜索条件
cy.get('[data-testid="collector-compart-input"]').type('不存在的仓室')
cy.get('[data-testid="collector-search-button"]').click()
// 等待搜索结果
cy.wait(1000)
// 验证表格仍然可见(即使没有数据)
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
it('应该验证页面响应式设计', () => {
// 测试不同屏幕尺寸
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)
// 验证主要组件仍然可见
cy.get('[data-testid="collector-list-container"]').should('be.visible')
cy.get('[data-testid="collector-list-search-form"]').should('be.visible')
cy.get('[data-testid="collector-table-container"]').should('be.visible')
})
})
it('应该验证搜索表单的输入验证', () => {
// 测试输入框的清除功能
cy.get('[data-testid="collector-compart-input"]').type('测试输入')
cy.get('[data-testid="collector-compart-input"]').clear()
cy.get('[data-testid="collector-compart-input"]').should('have.value', '')
cy.get('[data-testid="collector-duster-name-input"]').type('测试输入')
cy.get('[data-testid="collector-duster-name-input"]').clear()
cy.get('[data-testid="collector-duster-name-input"]').should('have.value', '')
})
it('应该验证表格数据的交互性', () => {
// 等待表格加载
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 检查除尘器名称链接的样式
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'health-score')
cy.get('[data-testid="collector-duster-name-link"]').first().should('have.class', 'green-color')
})
it('应该验证页面加载性能', () => {
// 记录页面加载时间
cy.window().then((win) => {
const performance = win.performance
const navigation = performance.getEntriesByType('navigation')[0]
// 验证页面加载时间在合理范围内(小于10秒)
expect(navigation.loadEventEnd - navigation.loadEventStart).to.be.lessThan(10000)
})
})
it('应该验证错误处理', () => {
// 模拟网络错误(通过拦截API请求)
cy.intercept('GET', '**/bag/cycle/getReplaceListPage', {
statusCode: 500,
body: { error: '服务器错误' }
}).as('getCollectorListError')
// 触发搜索操作
cy.get('[data-testid="collector-search-button"]').click()
// 等待错误响应
cy.wait('@getCollectorListError')
// 验证页面仍然可用
cy.get('[data-testid="collector-list-container"]').should('be.visible')
})
it('应该验证数据刷新功能', () => {
// 记录初始数据
cy.get('[data-testid="collector-common-table"]').should('be.visible')
// 等待一段时间后验证页面仍然响应
cy.wait(2000)
// 验证页面仍然可用
cy.get('[data-testid="collector-list-container"]').should('be.visible')
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
})
/// <reference types="cypress" />
describe('仪表板功能测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 访问仪表板
cy.visit('/#/dashboard')
})
it('应该显示仪表板的所有核心组件', () => {
// 检查主容器
cy.get('[data-testid="dashboard-container"]').should('be.visible')
// 检查头部区域
cy.get('[data-testid="dashboard-header"]').should('be.visible')
// 检查消息框
cy.get('[data-testid="dashboard-msg-box"]').should('be.visible')
cy.get('[data-testid="dashboard-msg-item"]').should('be.visible')
// 检查指标框
cy.get('[data-testid="dashboard-indicators-box"]').should('be.visible')
cy.get('[data-testid="dashboard-health-score"]').should('be.visible')
// 检查图表区域
cy.get('[data-testid="dashboard-chart-box"]').should('be.visible')
cy.get('[data-testid="dashboard-chart-line"]').should('be.visible')
// 检查地图区域
cy.get('[data-testid="dashboard-map-box"]').should('be.visible')
cy.get('[data-testid="dashboard-map-svg"]').should('be.visible')
})
it('应该显示健康度指标', () => {
cy.verifyHealthIndicators()
// 检查健康度数值格式
cy.get('[data-testid="dashboard-health-score"]')
.should('be.visible')
.and('contain', '%')
// 检查进度条
cy.get('[data-testid="dashboard-bag-progress"]').should('be.visible')
cy.get('[data-testid="dashboard-pulse-valve-progress"]').should('be.visible')
cy.get('[data-testid="dashboard-poppet-valve-progress"]').should('be.visible')
})
it('应该加载并显示图表数据', () => {
// 等待图表组件加载
cy.get('[data-testid="dashboard-chart-line"]').should('be.visible')
// 检查图表是否有内容(这里需要根据实际图表实现调整)
cy.get('[data-testid="dashboard-chart-line"]').within(() => {
// 检查图表容器内是否有内容
cy.get('*').should('have.length.gt', 0)
})
})
it('应该显示地图组件', () => {
// 检查地图组件
cy.get('[data-testid="dashboard-map-svg"]').should('be.visible')
// 检查地图是否有内容
cy.get('[data-testid="dashboard-map-svg"]').within(() => {
cy.get('*').should('have.length.gt', 0)
})
})
it('应该响应数据更新', () => {
// 记录初始健康度值
cy.get('[data-testid="dashboard-health-score"]').then(($el) => {
const initialValue = $el.text()
// 等待一段时间,检查数据是否可能更新
cy.wait(2000)
// 验证元素仍然存在并且可能有更新
cy.get('[data-testid="dashboard-health-score"]').should('be.visible')
})
})
it('应该处理消息列表', () => {
// 检查消息组件
cy.get('[data-testid="dashboard-msg-item"]').should('be.visible')
// 如果有消息项,检查其结构
cy.get('[data-testid="dashboard-msg-item"]').within(() => {
// 检查是否有消息内容
cy.get('*').should('have.length.gte', 0)
})
})
it('应该验证布局响应式', () => {
// 测试不同屏幕尺寸下的布局
const viewports = [
{ width: 1920, height: 1080 },
{ width: 1280, height: 720 },
{ width: 1024, height: 768 }
]
viewports.forEach(viewport => {
cy.viewport(viewport.width, viewport.height)
cy.wait(500)
// 验证主要组件在不同尺寸下仍然可见
cy.get('[data-testid="dashboard-container"]').should('be.visible')
cy.get('[data-testid="dashboard-header"]').should('be.visible')
cy.get('[data-testid="dashboard-map-box"]').should('be.visible')
})
})
it('应该验证颜色主题', () => {
// 检查健康度指标的颜色是否根据数值变化
cy.get('[data-testid="dashboard-health-score"]').then(($el) => {
const healthScore = parseInt($el.text().replace('%', ''))
// 根据健康度检查颜色
if (healthScore >= 90) {
cy.get('[data-testid="dashboard-health-score"]')
.should('have.css', 'color')
.and('not.equal', 'rgba(0, 0, 0, 0)')
}
})
})
it('应该处理数据加载状态', () => {
// 重新加载页面检查加载状态
cy.reload()
// 等待页面完全加载
cy.waitForPageLoad()
// 验证所有关键元素都已加载
cy.get('[data-testid="dashboard-container"]').should('be.visible')
cy.get('[data-testid="dashboard-health-score"]').should('be.visible')
})
it('应该正确处理无权限用户的重定向', () => {
// 清除localStorage模拟无权限状态
cy.window().then((win) => {
win.localStorage.clear()
})
// 尝试访问仪表板,应该被重定向到登录页
cy.visit('/#/dashboard')
cy.url().should('include', '/#/login')
})
})
\ No newline at end of file
/// <reference types="cypress" />
describe('除尘器概览功能测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 访问除尘器概览页面
cy.visit('/#/dust-overview')
})
it('应该显示概览页面的所有核心元素', () => {
// 检查主容器
cy.get('[data-testid="dust-overview-container"]').should('be.visible')
// 检查头部统计卡片
cy.get('[data-testid="dust-overview-header"]').should('be.visible')
cy.get('[data-testid="dust-leak-alarm-card"]').should('be.visible')
cy.get('[data-testid="dust-health-card"]').should('be.visible')
cy.get('[data-testid="dust-close-loop-card"]').should('be.visible')
// 检查统计数据
cy.get('[data-testid="dust-leak-alarm-count"]').should('be.visible')
cy.get('[data-testid="dust-health-percent"]').should('be.visible')
cy.get('[data-testid="dust-close-loop-count"]').should('be.visible')
})
it('应该显示搜索表单', () => {
// 检查搜索表单
cy.get('[data-testid="dust-search-form"]').should('be.visible')
// 检查搜索控件
cy.get('[data-testid="dust-production-line-select"]').should('be.visible')
cy.get('[data-testid="dust-device-name-input"]').should('be.visible')
cy.get('[data-testid="dust-reset-button"]').should('be.visible')
cy.get('[data-testid="dust-search-button"]').should('be.visible')
cy.get('[data-testid="dust-add-button"]').should('be.visible')
})
it('应该显示数据表格', () => {
// 检查表格容器
cy.get('[data-testid="dust-table-container"]').should('be.visible')
cy.get('[data-testid="dust-table"]').should('be.visible')
// 检查表格是否已加载
cy.checkTableDataLoaded('[data-testid="dust-table"]')
})
it('应该能够执行搜索功能', () => {
// 使用设备名称搜索
cy.get('[data-testid="dust-device-name-input"]').clear().type('测试设备')
cy.get('[data-testid="dust-search-button"]').click()
// 等待搜索结果
cy.wait(1000)
// 验证表格仍然可见
cy.get('[data-testid="dust-table"]').should('be.visible')
})
it('应该能够使用工序筛选', () => {
// 点击工序选择框
cy.get('[data-testid="dust-production-line-select"]').click()
// 选择第一个选项(除了"全部")
cy.get('.el-select-dropdown__item').then($items => {
if ($items.length > 1) {
cy.wrap($items[1]).click()
// 点击搜索
cy.get('[data-testid="dust-search-button"]').click()
cy.wait(1000)
}
})
})
it('应该能够重置搜索条件', () => {
// 填写搜索条件
cy.get('[data-testid="dust-device-name-input"]').type('测试')
// 点击重置按钮
cy.get('[data-testid="dust-reset-button"]').click()
// 验证输入框已清空
cy.get('[data-testid="dust-device-name-input"]').should('have.value', '')
})
it('应该能够点击统计卡片进行导航', () => {
// 点击泄漏告警卡片
cy.get('[data-testid="dust-leak-alarm-card"]').click()
// 验证URL变化或者页面响应
cy.url().should('not.contain', '/dust-overview')
// 返回概览页面继续测试
cy.go('back')
cy.get('[data-testid="dust-overview-container"]').should('be.visible')
// 点击闭环卡片
cy.get('[data-testid="dust-close-loop-card"]').click()
cy.url().should('not.contain', '/dust-overview')
cy.go('back')
})
it('应该能够点击表格中的操作按钮', () => {
// 等待表格数据加载
cy.checkTableDataLoaded('[data-testid="dust-table"]')
// 检查是否有数据行
cy.get('[data-testid="dust-table"] tbody tr').then($rows => {
if ($rows.length > 0) {
// 点击第一行的查看按钮
cy.get('[data-testid="dust-view-button"]').first().click()
// 验证跳转或弹窗
cy.wait(1000)
// 如果是页面跳转,返回
cy.url().then(url => {
if (!url.includes('/dust-overview')) {
cy.go('back')
}
})
// 点击编辑按钮
cy.get('[data-testid="dust-edit-button"]').first().click()
cy.wait(1000)
}
})
})
it('应该能够点击仓室数量和电磁阀数量链接', () => {
// 等待表格加载
cy.checkTableDataLoaded('[data-testid="dust-table"]')
// 检查仓室数量链接
cy.get('[data-testid="compartment-count-link"]').then($links => {
if ($links.length > 0) {
cy.wrap($links[0]).click()
cy.wait(1000)
}
})
// 检查电磁阀数量链接
cy.get('[data-testid="valve-count-link"]').then($links => {
if ($links.length > 0) {
cy.wrap($links[0]).click()
cy.wait(1000)
}
})
})
it('应该能够打开新增弹窗', () => {
// 点击新增按钮
cy.get('[data-testid="dust-add-button"]').click()
// 验证弹窗是否打开
cy.get('.el-dialog').should('be.visible')
// 关闭弹窗
cy.get('.el-dialog__close').click()
})
it('应该验证分页功能', () => {
// 检查分页组件
cy.get('[data-testid="pagination-container"]').should('be.visible')
cy.get('[data-testid="el-pagination"]').should('be.visible')
// 如果有多页数据,测试分页
cy.get('.el-pagination__total').then($total => {
const totalText = $total.text()
if (totalText && totalText.includes('条') && parseInt(totalText) > 20) {
// 点击下一页
cy.get('.btn-next').click()
cy.wait(1000)
// 点击上一页
cy.get('.btn-prev').click()
cy.wait(1000)
}
})
})
it('应该验证响应式设计', () => {
cy.checkResponsive()
// 验证在不同屏幕尺寸下表格和卡片的显示
cy.viewport(768, 1024)
cy.get('[data-testid="dust-overview-header"]').should('be.visible')
cy.get('[data-testid="dust-table-container"]').should('be.visible')
})
it('应该处理数据加载状态', () => {
// 重新加载页面
cy.reload()
cy.waitForPageLoad()
// 验证页面元素正常加载
cy.get('[data-testid="dust-overview-container"]').should('be.visible')
cy.get('[data-testid="dust-leak-alarm-count"]').should('be.visible')
cy.get('[data-testid="dust-health-percent"]').should('be.visible')
cy.get('[data-testid="dust-close-loop-count"]').should('be.visible')
})
})
\ No newline at end of file
/// <reference types="cypress" />
describe('登录功能测试', () => {
beforeEach(() => {
// 清理localStorage确保每次测试都是干净的状态
cy.window().then((win) => {
win.localStorage.clear()
})
cy.visit('/#/login')
})
it('应该显示登录页面的所有元素', () => {
// 检查登录页面基本元素
cy.get('[data-testid="login-username-input"]').should('be.visible')
cy.get('[data-testid="login-password-input"]').should('be.visible')
cy.get('[data-testid="login-submit-button"]').should('be.visible')
cy.get('[data-testid="login-remember-checkbox"]').should('be.visible')
// 检查验证码相关元素(如果存在)
cy.get('body').then(($body) => {
if ($body.find('[data-testid="login-captcha-input"]').length > 0) {
cy.get('[data-testid="login-captcha-input"]').should('be.visible')
cy.get('[data-testid="login-captcha-image"]').should('be.visible')
}
})
})
it('应该验证必填字段', () => {
// 点击登录按钮而不填写任何信息
cy.get('[data-testid="login-submit-button"]').click()
// 检查错误提示(这里需要根据实际的错误提示元素调整)
// 由于Element Plus的验证提示可能不在DOM中持久存在,我们可以检查表单是否仍然可见
cy.get('[data-testid="login-username-input"]').should('be.visible')
})
it('应该能够切换密码可见性', () => {
cy.get('[data-testid="login-password-input"]').type('testpassword')
// Element Plus的密码显示/隐藏按钮 - 使用更具体的选择器
cy.get('[data-testid="login-password-input"]').find('.el-input__suffix-inner .el-input__icon').click({ force: true })
// 验证密码是否变为可见
cy.get('[data-testid="login-password-input"] input').should('have.attr', 'type', 'text')
})
it('应该能够刷新验证码', () => {
// 如果验证码图片存在,测试点击刷新
cy.get('body').then(($body) => {
if ($body.find('[data-testid="login-captcha-image"]').length > 0) {
cy.get('[data-testid="login-captcha-image"]')
.should('be.visible')
.and('have.attr', 'src')
// 点击验证码图片刷新
cy.get('[data-testid="login-captcha-image"]').click()
// 验证图片src发生变化(这里需要更复杂的逻辑来验证)
cy.get('[data-testid="login-captcha-image"]').should('have.attr', 'src')
}
})
})
it('应该处理登录表单提交', () => {
// 填写登录信息
cy.get('[data-testid="login-username-input"]').type('testuser')
cy.get('[data-testid="login-password-input"]').type('testpassword')
// 如果有验证码输入框,填写验证码
cy.get('body').then(($body) => {
if ($body.find('[data-testid="login-captcha-input"]').length > 0) {
cy.get('[data-testid="login-captcha-input"]').type('1234')
}
})
// 提交表单
cy.get('[data-testid="login-submit-button"]').click()
// 验证是否有加载状态或者跳转(这里需要根据实际情况调整)
cy.get('[data-testid="login-submit-button"]').should('be.visible')
})
it('应该验证响应式设计', () => {
cy.checkResponsive()
})
it('应该正确处理路由跳转到登录页', () => {
// 尝试直接访问需要权限的页面,应该被重定向到登录页
// 模拟登录状态
cy.mockLogin()
// 等待1秒
cy.wait(2000)
// 访问仪表板
cy.visit('/#/dashboard')
cy.url().should('include', '/#/dashboard')
// 验证登录页面元素存在
cy.get('[data-testid="login-username-input"]').should('be.visible')
})
})
\ No newline at end of file
/// <reference types="cypress" />
describe('导航菜单功能测试', () => {
beforeEach(() => {
// 模拟登录状态
cy.mockLogin()
// 访问包含菜单的页面
cy.visit('/#/dashboard')
})
it('应该显示侧边栏菜单', () => {
// 检查菜单容器
cy.get('[data-testid="sidebar-menu"]').should('be.visible')
// 检查菜单是否有菜单项
cy.get('[data-testid="sidebar-menu"] .el-menu-item, [data-testid="sidebar-menu"] .el-sub-menu').should('have.length.gt', 0)
})
it('应该能够点击菜单项进行导航', () => {
// 获取所有菜单项
cy.get('[data-testid^="menu-item-"]').then($items => {
if ($items.length > 0) {
// 点击第一个菜单项
const firstItemTestId = $items[0].getAttribute('data-testid')
cy.get(`[data-testid="${firstItemTestId}"]`).click()
// 验证页面跳转
cy.wait(1000)
cy.url().should('not.be.empty')
}
})
})
it('应该支持子菜单展开和收起', () => {
// 查找子菜单
cy.get('[data-testid^="menu-submenu-"]').then($submenus => {
if ($submenus.length > 0) {
const firstSubmenuTestId = $submenus[0].getAttribute('data-testid')
// 点击子菜单标题展开
cy.get(`[data-testid="${firstSubmenuTestId}"] .el-sub-menu__title`).click()
// 验证子菜单展开
cy.get(`[data-testid="${firstSubmenuTestId}"]`).should('have.class', 'is-opened')
// 再次点击收起
cy.get(`[data-testid="${firstSubmenuTestId}"] .el-sub-menu__title`).click()
// 验证子菜单收起
cy.get(`[data-testid="${firstSubmenuTestId}"]`).should('not.have.class', 'is-opened')
}
})
})
it('应该高亮当前激活的菜单项', () => {
// 检查当前URL对应的菜单项是否被激活
cy.url().then(currentUrl => {
const path = new URL(currentUrl).pathname.replace('/', '')
if (path) {
// 查找对应的菜单项
cy.get(`[data-testid="menu-item-${path}"]`).then($item => {
if ($item.length > 0) {
// 验证菜单项有活跃状态
cy.get(`[data-testid="menu-item-${path}"]`).should('have.class', 'is-active')
}
})
}
})
})
it('应该显示菜单图标', () => {
// 检查菜单项是否有图标
cy.get('[data-testid^="menu-item-"] .menu-icon, [data-testid^="menu-submenu-"] .menu-icon').should('have.length.gt', 0)
// 验证图标是否正常显示
cy.get('.menu-icon').each($icon => {
cy.wrap($icon).should('be.visible')
cy.wrap($icon).should('have.attr', 'src')
})
})
it('应该支持菜单收起状态', () => {
// 如果有收起按钮,测试收起功能
cy.get('body').then($body => {
// 查找可能的收起按钮(这里需要根据实际实现调整)
if ($body.find('.hamburger, .menu-toggle').length > 0) {
cy.get('.hamburger, .menu-toggle').click()
// 验证菜单收起状态
cy.get('[data-testid="sidebar-menu"]').should('have.class', 'el-menu--collapse')
// 再次点击展开
cy.get('.hamburger, .menu-toggle').click()
cy.get('[data-testid="sidebar-menu"]').should('not.have.class', 'el-menu--collapse')
}
})
})
it('应该支持键盘导航', () => {
// 使用Tab键在菜单项之间导航
cy.get('[data-testid="sidebar-menu"]').focus()
// 使用方向键导航
cy.get('[data-testid="sidebar-menu"]').type('{downarrow}')
cy.wait(500)
cy.get('[data-testid="sidebar-menu"]').type('{uparrow}')
// 使用Enter键选择
cy.get('[data-testid="sidebar-menu"]').type('{enter}')
})
it('应该在不同页面显示正确的菜单状态', () => {
// 导航到不同页面并验证菜单状态
const testPages = ['dashboard', 'dust-overview']
testPages.forEach(page => {
cy.visit(`/${page}`)
cy.waitForPageLoad()
// 验证菜单仍然可见
cy.get('[data-testid="sidebar-menu"]').should('be.visible')
// 验证对应的菜单项被激活
cy.get(`[data-testid="menu-item-${page}"]`).then($item => {
if ($item.length > 0) {
cy.get(`[data-testid="menu-item-${page}"]`).should('have.class', 'is-active')
}
})
})
})
it('应该处理菜单权限控制', () => {
// 验证菜单项是否根据权限显示
cy.get('[data-testid^="menu-item-"], [data-testid^="menu-submenu-"]').each($item => {
// 验证菜单项是可见的(说明用户有权限访问)
cy.wrap($item).should('be.visible')
})
})
it('应该在移动端正确显示', () => {
// 测试移动端菜单
cy.viewport(375, 667) // iPhone尺寸
// 菜单应该仍然可见或者有移动端适配
cy.get('[data-testid="sidebar-menu"]').should('exist')
// 恢复桌面端
cy.viewport(1280, 720)
cy.get('[data-testid="sidebar-menu"]').should('be.visible')
})
it('应该处理菜单项的hover状态', () => {
// 测试菜单项的鼠标悬停效果
cy.get('[data-testid^="menu-item-"]').first().then($item => {
if ($item.length > 0) {
cy.wrap($item).trigger('mouseover')
// 验证hover状态(这里需要根据实际CSS类名调整)
cy.wrap($item).should('have.css', 'background-color')
cy.wrap($item).trigger('mouseout')
}
})
})
})
\ No newline at end of file
{
"testUser": {
"username": "testuser",
"password": "testpass123",
"email": "test@example.com"
},
"adminUser": {
"username": "admin",
"password": "admin123",
"email": "admin@example.com"
},
"dustCollectors": [
{
"id": 1,
"name": "除尘器001",
"deviceNo": "DC001",
"productionLine": "生产线A",
"compartNum": 12,
"valveNum": 24,
"healthPercent": "85%",
"status": "正常"
},
{
"id": 2,
"name": "除尘器002",
"deviceNo": "DC002",
"productionLine": "生产线B",
"compartNum": 16,
"valveNum": 32,
"healthPercent": "92%",
"status": "正常"
}
],
"healthData": {
"average": 88,
"bag": 85,
"pulseValve": 90,
"poppetValve": 89
},
"alarmData": [
{
"id": 1,
"type": "泄漏告警",
"deviceName": "除尘器001",
"severity": "高",
"time": "2024-01-15 10:30:00",
"description": "检测到设备泄漏"
},
{
"id": 2,
"type": "压差异常",
"deviceName": "除尘器002",
"severity": "中",
"time": "2024-01-15 11:45:00",
"description": "压差超出正常范围"
}
],
"collectorListData": {
"records": [
{
"id": 1,
"dusterName": "1#除尘器",
"compart": "A仓室",
"bagLocation": "1排/1列",
"bagChangeNextTime": "2024-02-15 10:00:00",
"bagChangeTime": "2024-01-15 10:00:00",
"bagChangeAuthor": "张三",
"bagChangePeriod": "30天"
},
{
"id": 2,
"dusterName": "2#除尘器",
"compart": "B仓室",
"bagLocation": "2排/1列",
"bagChangeNextTime": "2024-02-20 10:00:00",
"bagChangeTime": "2024-01-20 10:00:00",
"bagChangeAuthor": "李四",
"bagChangePeriod": "25天"
},
{
"id": 3,
"dusterName": "3#除尘器",
"compart": "C仓室",
"bagLocation": "1排/2列",
"bagChangeNextTime": "2024-02-25 10:00:00",
"bagChangeTime": "2024-01-25 10:00:00",
"bagChangeAuthor": "王五",
"bagChangePeriod": "35天"
}
],
"total": 3,
"pageNo": 1,
"pageSize": 20
},
"dustAnalysisData": {
"dusterList": [
{
"dusterNo": "DC001",
"dusterName": "1#除尘器"
},
{
"dusterNo": "DC002",
"dusterName": "2#除尘器"
},
{
"dusterNo": "DC003",
"dusterName": "3#除尘器"
}
],
"analysisData": [
{
"date": "2024-01-01",
"value": 85
},
{
"date": "2024-01-15",
"value": 90
},
{
"date": "2024-02-01",
"value": 88
}
]
}
}
\ No newline at end of file
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// 模拟登录状态
Cypress.Commands.add('mockLogin', () => {
cy.window().then((win) => {
const mockMenuList = [
{ name: '首页', url: '/dashboard' },
{ name: '除尘器总览', url: '/dust-overview' },
{ name: '除尘器监控', url: '/monitor' },
{ name: '布袋周期', url: '/collectorList' },
{ name: '告警总览', url: '/alerts' },
{ name: '设备管理', url: '/device-management' },
{ name: '我的闭环', url: '/my-loop' }
]
win.localStorage.setItem('menuList', JSON.stringify(mockMenuList))
// 预设一个json
const json = {
"id": 26,
"customerId": 26,
"officeId": 1,
"name": "纵横钢铁",
"account": "zongheng_admin",
"customerName": "纵横钢铁",
"ultralowBoard": null,
"officeNameList": null,
"roleNameList": [
"admin",
"全厂基础角色",
"dctom"
],
"dataBranchFactoryId": "",
"enable": true
}
// 设置cookie
cy.setCookie('customerName', json.customerName)
cy.setCookie('dataBranchFactoryId', String(json.dataBranchFactoryId))
cy.setCookie('customerId', String(json.customerId))
cy.setCookie('userId', String(json.id))
cy.setCookie('userName', json.name)
cy.setCookie('TOKEN', 'cbb7481f995d46788f42d9aa0dc7847a')
win.sessionStorage.setItem('userId', json.id)
})
})
// 登录命令
Cypress.Commands.add('loginWithUI', (username = 'admin', password = 'password123') => {
cy.visit('/#/login')
cy.get('[data-testid="login-username-input"]').clear().type(username)
cy.get('[data-testid="login-password-input"]').clear().type(password)
// 处理验证码(如果需要)
cy.get('body').then(($body) => {
if ($body.find('[data-testid="login-captcha-input"]').length > 0) {
cy.get('[data-testid="login-captcha-input"]').type('test')
}
})
cy.get('[data-testid="login-submit-button"]').click()
// 等待登录成功跳转
cy.url().should('not.include', '/#/login')
})
// 导航到特定页面
Cypress.Commands.add('navigateToPage', (pageName) => {
cy.get(`[data-testid="menu-item-${pageName}"]`).click()
cy.waitForPageLoad()
})
// 检查表格数据加载
Cypress.Commands.add('checkTableDataLoaded', (tableSelector = '[data-testid="common-table"]') => {
cy.get(tableSelector).should('be.visible')
cy.get(`${tableSelector} [data-testid="el-table"]`).should('be.visible')
})
// 搜索功能
Cypress.Commands.add('searchInTable', (searchText, inputSelector = '[data-testid="dust-device-name-input"]') => {
cy.get(inputSelector).clear().type(searchText)
cy.get('[data-testid="dust-search-button"]').click()
cy.wait(1000) // 等待搜索结果
})
// 检查响应式设计
Cypress.Commands.add('checkResponsive', () => {
// 检查不同屏幕尺寸
const viewports = [
{ width: 1920, height: 1080 }, // 桌面
{ width: 1280, height: 720 }, // 笔记本
{ width: 768, height: 1024 }, // 平板
]
viewports.forEach(viewport => {
cy.viewport(viewport.width, viewport.height)
cy.wait(500)
cy.get('body').should('be.visible')
})
})
// 验证健康状态指示器
Cypress.Commands.add('verifyHealthIndicators', () => {
cy.get('[data-testid="dashboard-health-score"]').should('be.visible')
cy.get('[data-testid="dashboard-bag-progress"]').should('be.visible')
cy.get('[data-testid="dashboard-pulse-valve-progress"]').should('be.visible')
cy.get('[data-testid="dashboard-poppet-valve-progress"]').should('be.visible')
})
// ========== CollectorList 专用命令 ==========
// 导航到布袋周期页面
Cypress.Commands.add('navigateToCollectorList', () => {
cy.visit('/#/collectorList')
cy.get('[data-testid="collector-list-container"]').should('be.visible')
})
// 搜索布袋周期数据
Cypress.Commands.add('searchCollectorData', (compart = '', dusterName = '', dateRange = null) => {
if (compart) {
cy.get('[data-testid="collector-compart-input"]').clear().type(compart)
}
if (dusterName) {
cy.get('[data-testid="collector-duster-name-input"]').clear().type(dusterName)
}
if (dateRange) {
cy.get('[data-testid="collector-date-picker"]').click()
cy.get('.el-picker-panel').should('be.visible')
// 选择日期范围
if (dateRange.startDate) {
cy.get('.el-date-table td.available').first().click()
}
if (dateRange.endDate) {
cy.get('.el-date-table td.available').eq(5).click()
}
}
cy.get('[data-testid="collector-search-button"]').click()
cy.wait(1000) // 等待搜索结果
})
// 重置搜索条件
Cypress.Commands.add('resetCollectorSearch', () => {
cy.get('[data-testid="collector-reset-button"]').click()
// 验证输入框已清空
cy.get('[data-testid="collector-compart-input"]').should('have.value', '')
cy.get('[data-testid="collector-duster-name-input"]').should('have.value', '')
})
// 打开更换周期分析对话框
Cypress.Commands.add('openAnalysisDialog', (method = 'button') => {
if (method === 'button') {
cy.get('[data-testid="collector-analysis-button"]').click()
} else if (method === 'dblclick') {
cy.get('[data-testid="collector-duster-name-link"]').first().dblclick()
}
cy.get('.dustListDialog').should('be.visible')
cy.get('.el-dialog__title').should('contain', '更换周期分析')
})
// 关闭分析对话框
Cypress.Commands.add('closeAnalysisDialog', () => {
cy.get('.dustListDialog .el-dialog__headerbtn').click()
cy.get('.dustListDialog').should('not.exist')
})
// 验证表格数据完整性
Cypress.Commands.add('verifyCollectorTableData', (expectedData) => {
cy.get('[data-testid="collector-common-table"]').within(() => {
if (expectedData.rowCount) {
cy.get('.el-table__body tr').should('have.length', expectedData.rowCount)
}
if (expectedData.firstRow) {
cy.get('.el-table__body tr').first().within(() => {
Object.keys(expectedData.firstRow).forEach((key, index) => {
cy.get('td').eq(index + 1).should('contain', expectedData.firstRow[key])
})
})
}
})
})
// 验证表格列标题
Cypress.Commands.add('verifyCollectorTableHeaders', () => {
const expectedHeaders = [
'序号',
'除尘器名称',
'仓室',
'布袋位置(排/列)',
'布袋更换提醒时间',
'更换时间',
'更换人',
'更换周期(与上次更换比)'
]
cy.get('[data-testid="collector-common-table"]').within(() => {
expectedHeaders.forEach(header => {
cy.get('.el-table__header').should('contain', header)
})
})
})
// 模拟API响应
Cypress.Commands.add('mockCollectorAPI', (apiType, responseData) => {
const apiMap = {
'list': '**/bag/cycle/getReplaceListPage',
'dusterList': '**/bag/cycle/getDusterList',
'analysis': '**/bag/cycle/getReplaceAnalysis'
}
cy.intercept('GET', apiMap[apiType], {
statusCode: 200,
body: {
code: 200,
data: responseData,
message: 'success'
}
}).as(`mock${apiType}API`)
})
// 等待页面加载完成
Cypress.Commands.add('waitForPageLoad', () => {
cy.get('[data-testid="collector-list-container"]').should('be.visible')
cy.get('[data-testid="collector-common-table"]').should('be.visible')
})
\ No newline at end of file
// cypress/support/component.js
// 组件测试支持文件
import { mount } from 'cypress/vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 注册mount命令
Cypress.Commands.add('mount', (component, options = {}) => {
options.global = options.global || {}
options.global.plugins = options.global.plugins || []
// 添加Pinia和ElementPlus
options.global.plugins.push(createPinia())
options.global.plugins.push(ElementPlus)
return mount(component, options)
})
\ No newline at end of file
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
// 全局配置
Cypress.on('uncaught:exception', (err, runnable) => {
// 防止Cypress因为应用程序的未捕获异常而失败
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)
cy.get('[data-testid="login-submit-button"]').click()
})
// 等待页面加载完成
Cypress.Commands.add('waitForPageLoad', () => {
cy.get('body').should('be.visible')
cy.wait(500) // 额外等待500ms确保页面完全加载
})
// 检查数据加载
Cypress.Commands.add('waitForDataLoad', (selector, timeout = 10000) => {
cy.get(selector, { timeout }).should('be.visible')
})
\ No newline at end of file
{
"ci": {
"collect": {
"url": ["http://localhost:4173/"],
"startServerCommand": "npm run preview",
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["warn", {"minScore": 0.7}],
"categories:accessibility": ["error", {"minScore": 0.9}],
"categories:best-practices": ["warn", {"minScore": 0.8}],
"categories:seo": ["warn", {"minScore": 0.8}]
}
},
"upload": {
"target": "temporary-public-storage"
},
"server": {
"port": 9001,
"storage": "./lighthouse-reports"
}
}
}
\ No newline at end of file
......@@ -6,7 +6,10 @@
"scripts": {
"dev": "vite --mode development",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:run:ci": "cypress run --browser chrome --headless"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
......@@ -26,6 +29,7 @@
"devDependencies": {
"@types/node": "^22.15.18",
"@vitejs/plugin-vue": "^5.2.3",
"cypress": "^13.6.0",
"sass": "^1.88.0",
"sass-loader": "^16.0.5",
"vite": "^6.3.5"
......
<template>
<div class="common-table">
<div class="common-table" data-testid="common-table">
<el-table
ref="tableRef"
:data="currentPageData"
v-bind="$attrs"
:resizable="false"
:height="tableHeight"
data-testid="el-table"
>
<template v-for="column in columns" :key="column.prop">
<el-table-column v-bind="column" :align="align">
......@@ -23,7 +24,7 @@
</template>
</el-table>
<div class="pagination-container" v-if="showPagination">
<div class="pagination-container" v-if="showPagination" data-testid="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
......@@ -35,6 +36,7 @@
:total-text="paginationTexts.total || '总计'"
:size-change-text="paginationTexts.sizeChange || '条/页'"
:jumper-text="paginationTexts.jumper || '前往'"
data-testid="el-pagination"
>
</el-pagination>
</div>
......
......@@ -3,6 +3,7 @@
<el-col :span="24">
<el-menu
class="el-menu-vertical-demo"
data-testid="sidebar-menu"
@open="handleOpen"
@close="handleClose"
:collapse="sidebar"
......@@ -16,6 +17,7 @@
:index="item.path"
v-if="item.children && item.children.length > 0"
class="submenu-box"
:data-testid="`menu-submenu-${item.path.replace('/', '')}`"
>
<template #title>
<!-- <el-icon><location /></el-icon> -->
......@@ -27,6 +29,7 @@
:key="item_son.path"
:index="item_son.path"
class="submenu-title-noDropdown"
:data-testid="`menu-item-${item_son.path.replace('/', '')}`"
>
<span>{{getMenuTitle(item_son)}}</span>
</el-menu-item>
......@@ -35,6 +38,7 @@
v-else
:index="item.path"
class="submenu-box"
:data-testid="`menu-item-${item.path.replace('/', '')}`"
>
<!-- <el-icon><setting /></el-icon> -->
<img class="menu-icon" :src="getIcon(item.meta.icon)" alt="">
......
<template>
<div class="my-agency all-select-btn">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<div class="my-agency all-select-btn" data-testid="my-agency-container">
<el-form :inline="true" :model="formInline" class="demo-form-inline" data-testid="my-agency-search-form">
<el-form-item label="事件名称:">
<el-input v-model="formInline.eventName"> </el-input>
<el-input v-model="formInline.eventName" data-testid="my-agency-event-name-input"> </el-input>
</el-form-item>
<el-form-item label="发生位置:">
<el-input v-model="formInline.keyword" placeholder="请输入"></el-input>
<el-input v-model="formInline.keyword" placeholder="请输入" data-testid="my-agency-keyword-input"></el-input>
</el-form-item>
<el-form-item label="所属工序:">
<el-select
v-model="formInline.productionLineId"
placeholder="请选择"
style="width: 180px"
data-testid="my-agency-production-line-select"
>
<el-option
v-for="(item, index) in basicConfiguration.productLineList"
......@@ -26,6 +27,7 @@
v-model="formInline.deviceType"
style="width: 180px"
placeholder="请选择"
data-testid="my-agency-device-type-select"
>
<el-option label="全部" value="" />
<el-option
......@@ -47,21 +49,23 @@
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
popper-class="date-picker-popper"
data-testid="my-agency-date-picker"
/>
</el-form-item>
<el-form-item>
<el-button class="default-btn reset-btn-balck-theme" @click="onReset"
<el-button class="default-btn reset-btn-balck-theme" @click="onReset" data-testid="my-agency-reset-button"
>重置</el-button
>
<el-button
type="default"
class="default-btn search-btn-balck-theme"
@click="onSearch"
data-testid="my-agency-search-button"
>查询</el-button
>
</el-form-item>
</el-form>
<div class="table-box">
<div class="table-box" data-testid="my-agency-table-container">
<common-table
ref="pageRef"
:data="tableData"
......@@ -76,6 +80,7 @@
next: '后一页',
jumper: '跳至',
}"
data-testid="my-agency-common-table"
>
<template #index="{ $index }">
{{ getIndex($index) }}
......@@ -86,15 +91,15 @@
</span>
</template>
<template #operation="{ row }">
<span class="view-btn green-color" @click="handleDone(row)">
<span class="view-btn green-color" @click="handleDone(row)" data-testid="my-agency-process-button">
处理
</span>
<span class="table-btn">|</span>
<span class="view-btn green-color" @click="handleDispatch(row)">
<span class="view-btn green-color" @click="handleDispatch(row)" data-testid="my-agency-dispatch-button">
分派
</span>
<span class="table-btn">|</span>
<span class="view-btn green-color" @click="changeBag(row)">
<span class="view-btn green-color" @click="changeBag(row)" data-testid="my-agency-change-bag-button">
更换布袋
</span>
</template>
......
<template>
<div class="page-container collectorList all-select-btn">
<div class="content-box">
<div class="search">
<div class="page-container collectorList all-select-btn" data-testid="collector-list-container">
<div class="content-box" data-testid="collector-list-content">
<div class="search" data-testid="collector-list-search-form">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="仓室">
<el-input
......@@ -9,6 +9,7 @@
placeholder="请输入仓室名称"
style="width: 180px"
clearable
data-testid="collector-compart-input"
/>
</el-form-item>
<el-form-item label="除尘器名称">
......@@ -17,6 +18,7 @@
placeholder="请输入除尘器名称"
style="width: 180px"
clearable
data-testid="collector-duster-name-input"
/>
</el-form-item>
<el-form-item label="更换时间">
......@@ -28,13 +30,14 @@
end-placeholder="结束时间"
popper-class="date-picker-popper"
:teleported="false"
data-testid="collector-date-picker"
/>
</el-form-item>
<el-form-item>
<el-button type="default" class="reset-btn-balck-theme" @click="onReset"
<el-button type="default" class="reset-btn-balck-theme" @click="onReset" data-testid="collector-reset-button"
>重置</el-button
>
<el-button type="default" class="search-btn-balck-theme" @click="onSubmit"
<el-button type="default" class="search-btn-balck-theme" @click="onSubmit" data-testid="collector-search-button"
>查询</el-button
>
<el-button
......@@ -42,12 +45,13 @@
style="width: 140px"
class="export-btn-balck-theme"
@click="openDialog()"
data-testid="collector-analysis-button"
>更换周期分析</el-button
>
</el-form-item>
</el-form>
</div>
<div class="table-box">
<div class="table-box" data-testid="collector-table-container">
<common-table
ref="pageRef"
:data="tableData.list"
......@@ -62,12 +66,13 @@
next: '后一页',
jumper: '跳至',
}"
data-testid="collector-common-table"
>
<template #index="{ $index }">
{{ getIndex($index) }}
</template>
<template #dusterName="{ row }">
<span class="health-score green-color" @dblclick="openDialog(row.dusterName)">{{
<span class="health-score green-color" @dblclick="openDialog(row.dusterName)" data-testid="collector-duster-name-link">{{
row.dusterName
}}</span>
</template>
......
<template>
<div class="dashboard-container">
<div class="header">
<div class="msg-box">
<div class="dashboard-container" data-testid="dashboard-container">
<div class="header" data-testid="dashboard-header">
<div class="msg-box" data-testid="dashboard-msg-box">
<div class="title">AI智能监测</div>
<msg-item :msgList="msgList"></msg-item>
<msg-item :msgList="msgList" data-testid="dashboard-msg-item"></msg-item>
</div>
<div class="indicators-box">
<div class="indicators-box" data-testid="dashboard-indicators-box">
<div class="title">综合健康度</div>
<div class="indicators-num" :style="{ color: customColorMethod(average) }">{{ average }}%</div>
<div class="indicators-num" :style="{ color: customColorMethod(average) }" data-testid="dashboard-health-score">{{ average }}%</div>
<div>
<div class="indicators-item">布袋健康度</div>
<el-progress :percentage="bag" :color="customColorMethod" />
<el-progress :percentage="bag" :color="customColorMethod" data-testid="dashboard-bag-progress" />
</div>
<div>
<div class="indicators-item">脉冲阀健康度</div>
<el-progress :percentage="pulseValve" :color="customColorMethod" />
<el-progress :percentage="pulseValve" :color="customColorMethod" data-testid="dashboard-pulse-valve-progress" />
</div>
<div>
<div class="indicators-item">提升阀健康度</div>
<el-progress :percentage="poppetValve" :color="customColorMethod" />
<el-progress :percentage="poppetValve" :color="customColorMethod" data-testid="dashboard-poppet-valve-progress" />
</div>
</div>
<div class="line-box">
<div class="line-box" data-testid="dashboard-chart-box">
<div class="title">健康度指数</div>
<chart-line :chartData="chartData"></chart-line>
<chart-line :chartData="chartData" data-testid="dashboard-chart-line"></chart-line>
</div>
</div>
<div class="map-box">
<map-svg :mapList="mapList"></map-svg>
<div class="map-box" data-testid="dashboard-map-box">
<map-svg :mapList="mapList" data-testid="dashboard-map-svg"></map-svg>
</div>
</div>
</template>
......
<template>
<div class="dust-box">
<div class="top-box">
<div class="dust-box" data-testid="dust-monitoring-container">
<div class="top-box" data-testid="dust-monitoring-filters">
<el-form
:model="form"
label-width="auto"
......@@ -13,6 +13,7 @@
placeholder="请选择除尘器"
style="width: 240px"
filterable
data-testid="dust-monitoring-duster-select"
>
<el-option
v-for="item in dusterList"
......@@ -33,18 +34,19 @@
@calendar-change="calendarChange"
@visible-change="visibleChange"
:disabled-date="disabledFn"
data-testid="dust-monitoring-date-picker"
/>
</el-form-item>
</el-form>
</div>
<div class="layout1">
<div class="left-box">
<div class="part1 layout3">
<div class="chart-box" v-for="(item, index) in chartData" :key="item">
<div class="layout1" data-testid="dust-monitoring-main-content">
<div class="left-box" data-testid="dust-monitoring-charts-section">
<div class="part1 layout3" data-testid="dust-monitoring-charts-container">
<div class="chart-box" v-for="(item, index) in chartData" :key="item" :data-testid="`dust-monitoring-chart-${index}`">
<div :id="'chart' + index" class="chart-item"></div>
</div>
</div>
<div class="warn-info">
<div class="warn-info" data-testid="dust-monitoring-warnings">
<warnCom
title="告警"
:listInfo="warnInfoList"
......@@ -52,15 +54,15 @@
></warnCom>
</div>
</div>
<div class="right-box">
<div class="part1">
<div class="battery">
<div class="right-box" data-testid="dust-monitoring-info-section">
<div class="part1" data-testid="dust-monitoring-health-section">
<div class="battery" data-testid="dust-monitoring-health-indicator">
<healthyCom :progress="healthPercent"></healthyCom>
</div>
</div>
<div class="part2">
<div class="dust-title">{{ dusterName }}</div>
<div class="dust-info">
<div class="part2" data-testid="dust-monitoring-details-section">
<div class="dust-title" data-testid="dust-monitoring-title">{{ dusterName }}</div>
<div class="dust-info" data-testid="dust-monitoring-info-grid">
<div class="info-item" v-for="item in dustInfo" :key="item.label">
<span class="label">{{ item.label }}</span>
<span class="value"
......@@ -70,11 +72,11 @@
</div>
<div></div>
</div>
<div class="indicator-box">
<div class="indicator-box" data-testid="dust-monitoring-gauges">
<div class="indicator-item" id="indicatorOne"></div>
<div class="indicator-item" id="indicatorTwo"></div>
</div>
<div class="position-info">
<div class="position-info" data-testid="dust-monitoring-status-matrix">
<div class="left" v-if="detailObj.compartHealthList.length > 0">
<div
class="part"
......@@ -103,7 +105,7 @@
</div>
</div> -->
</div>
<div class="other-info">
<div class="other-info" data-testid="dust-monitoring-compartment-info">
<div class="other-info-item">
<p class="label">反吹仓室:</p>
<p>{{ detailObj.bachflushCompart }}</p>
......@@ -118,7 +120,7 @@
</div>
</div>
</div>
<div class="warn-info">
<div class="warn-info" data-testid="dust-monitoring-closed-loops">
<warnCom
title="闭环"
:listInfo="closedLoopInfoList"
......
<template>
<div class="page-container dust-container all-select-btn">
<div class="header">
<div class="item-box crusor-click" @click="handleDusterLeakNumClick">
<div class="page-container dust-container all-select-btn" data-testid="dust-overview-container">
<div class="header" data-testid="dust-overview-header">
<div class="item-box crusor-click" @click="handleDusterLeakNumClick" data-testid="dust-leak-alert-card">
<img src="@/assets/icons/warn.png" alt="dust" />
<div class="title">
<span>泄漏告警(条)</span>
......@@ -9,7 +9,7 @@
</div>
</div>
<div class="item-box">
<div class="item-box" data-testid="dust-health-card">
<img src="@/assets/icons/health.png" alt="dust" />
<div class="title">
<span>综合健康度</span>
......@@ -17,7 +17,7 @@
</div>
</div>
<div class="item-box crusor-click" @click="handleCloseLoopNumClick">
<div class="item-box crusor-click" @click="handleCloseLoopNumClick" data-testid="dust-close-loop-card">
<img src="@/assets/icons/close.png" alt="dust" />
<div class="title">
<span>闭环(条)</span>
......@@ -26,8 +26,8 @@
</div>
</div>
<div class="content-box">
<div class="search">
<div class="content-box" data-testid="dust-overview-content">
<div class="search" data-testid="dust-search-form">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="工序">
<el-select
......@@ -36,6 +36,7 @@
style="width: 180px"
filterable
:filter-method="filterProductionLine"
data-testid="dust-production-line-select"
>
<el-option key="all" label="全部" value="all" />
<el-option
......@@ -52,26 +53,28 @@
placeholder="请输入除尘器名称"
style="width: 240px"
clearable
data-testid="dust-device-name-input"
/>
</el-form-item>
<el-form-item>
<el-button type="default" class="reset-btn-balck-theme" @click="refreshData"
<el-button type="default" class="reset-btn-balck-theme" @click="refreshData" data-testid="dust-reset-button"
>重置</el-button
>
<el-button type="default" class="search-btn-balck-theme" @click="onSubmit"
<el-button type="default" class="search-btn-balck-theme" @click="onSubmit" data-testid="dust-search-button"
>查询</el-button
>
<el-button
type="default"
class="export-btn-balck-theme"
@click="handleAddDustCollector"
data-testid="dust-add-button"
>新增</el-button
>
</el-form-item>
</el-form>
</div>
<div class="table-box">
<div class="table-box" data-testid="dust-table-container">
<common-table
:data="tableData"
:columns="tableColumns"
......@@ -85,17 +88,18 @@
next: '后一页',
jumper: '跳至',
}"
data-testid="common-table"
>
<template #index="{ $index }">
{{ getIndex($index) }}
</template>
<template #compartNum="{ row }">
<span class="health-score" @click="handleHealthScoreClick(row)">{{
<span class="health-score" @click="handleHealthScoreClick(row)" data-testid="compartment-count-link">{{
row.compartNum
}}</span>
</template>
<template #valveNum="{ row }">
<span class="health-score" @click="handleValveNumClick(row)">{{
<span class="health-score" @click="handleValveNumClick(row)" data-testid="valve-count-link">{{
row.valveNum
}}</span>
</template>
......@@ -133,8 +137,8 @@
</template>
<template #operation="{ row }">
<span class="view-btn" @click="handleView(row)">详情</span>
<span class="edit-btn" @click="handleEdit(row)">编辑</span>
<span class="view-btn" @click="handleView(row)" data-testid="dust-view-button">详情</span>
<span class="edit-btn" @click="handleEdit(row)" data-testid="dust-edit-button">编辑</span>
</template>
</common-table>
</div>
......
<template>
<div class="equipment-management black-theme all-select-btn">
<div class="search">
<div class="equipment-management black-theme all-select-btn" data-testid="equipment-management-container">
<div class="search" data-testid="equipment-management-search-form">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="工序">
<el-select
v-model="formInline.productionLineId"
placeholder="请选择工序"
style="width: 120px"
data-testid="equipment-production-line-select"
>
<el-option
v-for="item in processOptions"
......@@ -22,6 +23,7 @@
placeholder="请输入除尘器名称"
style="width: 240px"
clearable
data-testid="equipment-duster-name-input"
/>
</el-form-item>
<el-form-item label="设备名称">
......@@ -30,6 +32,7 @@
placeholder="请输入设备名称"
style="width: 240px"
clearable
data-testid="equipment-device-name-input"
/>
</el-form-item>
<el-form-item label="设备类型">
......@@ -37,6 +40,7 @@
v-model="formInline.deviceTypeId"
placeholder="请选择设备类型"
style="width: 120px"
data-testid="equipment-device-type-select"
>
<el-option
v-for="item in deviceTypeEnum"
......@@ -48,13 +52,14 @@
</el-form-item>
<el-form-item>
<el-button type="default" class="reset-btn-balck-theme" @click="onReset"
<el-button type="default" class="reset-btn-balck-theme" @click="onReset" data-testid="equipment-reset-button"
>重置</el-button
>
<el-button
type="default"
class="search-btn-balck-theme margin-right-10"
@click="onQuery"
data-testid="equipment-search-button"
>查询</el-button
>
<el-upload
......@@ -70,20 +75,20 @@
token: getToken('TOKEN'),
}"
>
<el-button type="default" class="export-btn-balck-theme margin-right-10"
<el-button type="default" class="export-btn-balck-theme margin-right-10" data-testid="equipment-import-button"
>导入</el-button
>
</el-upload>
<el-button type="default" class="export-btn-balck-theme" @click="onExportFile"
<el-button type="default" class="export-btn-balck-theme" @click="onExportFile" data-testid="equipment-export-button"
>导出</el-button
>
<el-button type="default" class="export-btn-balck-theme" @click="onDownloadTemplate"
<el-button type="default" class="export-btn-balck-theme" @click="onDownloadTemplate" data-testid="equipment-template-button"
>模板下载</el-button
>
</el-form-item>
</el-form>
</div>
<div class="table-box">
<div class="table-box" data-testid="equipment-table-container">
<common-table
:data="tableData"
:columns="tableColumns"
......@@ -98,13 +103,14 @@
next: '后一页',
jumper: '跳至',
}"
data-testid="equipment-common-table"
>
<template #index="{ $index }">
{{ getIndex($index) }}
</template>
<template #operation="{ row }">
<el-button class="green-color" text @click="getParamsConfig(row)" v-if="[10001, 10002].includes(row.deviceTypeId)"> 参数设置 </el-button>
<el-button class="green-color" text @click="getParamsConfig(row)" v-if="[10001, 10002].includes(row.deviceTypeId)" data-testid="equipment-params-button"> 参数设置 </el-button>
</template>
</common-table>
</div>
......
......@@ -32,6 +32,7 @@
name="username"
type="text"
tabindex="1"
data-testid="login-username-input"
@blur="handleAccountBlur($event)"
@input="debounceAction"
autocomplete="off"
......@@ -52,6 +53,7 @@
show-password
placeholder="密码 / Password"
tabindex="1"
data-testid="login-password-input"
autocomplete="off"
/>
</el-form-item>
......@@ -70,12 +72,14 @@
name="captcha"
type="text"
tabindex="1"
data-testid="login-captcha-input"
autocomplete="on"
/>
</div>
<img
class="captcha-img"
:src="verifyCode"
data-testid="login-captcha-image"
@click="fetchVerifyCli"
/>
</div>
......@@ -86,12 +90,14 @@
v-model="loginOldForm.rememberMe"
label="记住密码"
size="large"
data-testid="login-remember-checkbox"
/>
<el-button
:loading="oldloading"
type="primary"
class="login-btn"
data-testid="login-submit-button"
@click.prevent="handleLoginNew"
@keyup.enter="handleLoginNew"
>登录</el-button
......
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