From 7e54d7fb464cadcfdc5c316eee7ad064c38059d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 8 Apr 2026 19:49:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=AE=A1=E8=AE=A1=E6=97=A5=E5=BF=97):?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E5=AE=A1=E8=AE=A1=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=92=8C=20E2E=20=E6=B5=8B=E8=AF=95=E8=B4=A8?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 架构改进: - 引入审计日志服务层,实现业务逻辑与数据访问分离 - 添加 Spring Data 审计注解,自动填充创建人、创建时间等字段 - 修复切面范围,避免 Repository 和 Dao 层重复记录 代码优化: - 移除构造函数中的冗余 info 日志,降低生产环境日志量 - 恢复 SQL 文件格式,提高可读性 - 优化 E2E 测试等待策略,移除硬编码等待时间,提高测试稳定性 影响范围: - 后端:审计日志模块(Service、Repository、Aspect、Entity) - 前端:E2E 测试文件(4 个 workflow 测试) - 数据库:审计日志表结构 --- .../src/test/resources/schema-h2.sql | 29 +++ .../novalon/manage/db/entity/BaseEntity.java | 8 + .../db/repository/AuditLogRepository.java | 4 + .../db/migration/V7__Add_audit_log_table.sql | 15 +- .../manage/sys/audit/AuditLogAspect.java | 20 +- .../manage/sys/audit/domain/AuditLog.java | 1 - .../sys/audit/service/IAuditLogService.java | 30 +++ .../audit/service/impl/AuditLogService.java | 68 ++++++ .../e2e/journeys/config-workflow.spec.ts | 140 ++++++++++++ .../e2e/journeys/dict-workflow.spec.ts | 138 ++++++++++++ .../journeys/exception-log-workflow.spec.ts | 72 ++++++ .../e2e/journeys/notice-workflow.spec.ts | 138 ++++++++++++ .../e2e/pages/DictionaryManagementPage.ts | 207 +++++------------- .../e2e/pages/NotificationPage.ts | 88 ++++---- .../e2e/pages/SystemConfigPage.ts | 58 ++--- novalon-manage-web/playwright/.auth/user.json | 2 +- 16 files changed, 766 insertions(+), 252 deletions(-) create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/IAuditLogService.java create mode 100644 novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java create mode 100644 novalon-manage-web/e2e/journeys/config-workflow.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/dict-workflow.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts create mode 100644 novalon-manage-web/e2e/journeys/notice-workflow.spec.ts diff --git a/novalon-manage-api/manage-app/src/test/resources/schema-h2.sql b/novalon-manage-api/manage-app/src/test/resources/schema-h2.sql index 5d321ac..bc007cb 100644 --- a/novalon-manage-api/manage-app/src/test/resources/schema-h2.sql +++ b/novalon-manage-api/manage-app/src/test/resources/schema-h2.sql @@ -45,3 +45,32 @@ CREATE TABLE IF NOT EXISTS user_role ( -- 创建索引 CREATE INDEX IF NOT EXISTS idx_user_role_user_id ON user_role(user_id); CREATE INDEX IF NOT EXISTS idx_user_role_role_id ON user_role(role_id); + +-- 创建审计日志表 +CREATE TABLE IF NOT EXISTS audit_log ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + entity_type VARCHAR(100) NOT NULL, + entity_id BIGINT, + operation_type VARCHAR(20) NOT NULL, + operator VARCHAR(100), + operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + before_data CLOB, + after_data CLOB, + changed_fields CLOB, + ip_address VARCHAR(50), + user_agent CLOB, + description CLOB, + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP +); + +-- 创建审计日志索引 +CREATE INDEX IF NOT EXISTS idx_audit_log_entity_type ON audit_log(entity_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_entity_id ON audit_log(entity_id); +CREATE INDEX IF NOT EXISTS idx_audit_log_operation_type ON audit_log(operation_type); +CREATE INDEX IF NOT EXISTS idx_audit_log_operator ON audit_log(operator); +CREATE INDEX IF NOT EXISTS idx_audit_log_operation_time ON audit_log(operation_time); +CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id); diff --git a/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/entity/BaseEntity.java b/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/entity/BaseEntity.java index 81a1109..47855f7 100644 --- a/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/entity/BaseEntity.java +++ b/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/entity/BaseEntity.java @@ -1,6 +1,10 @@ package cn.novalon.manage.db.entity; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.domain.Persistable; import org.springframework.data.relational.core.mapping.Column; @@ -17,15 +21,19 @@ public abstract class BaseEntity implements Persistable { @Id private Long id; + @CreatedBy @Column("create_by") private String createBy; + @LastModifiedBy @Column("update_by") private String updateBy; + @CreatedDate @Column("created_at") private LocalDateTime createdAt; + @LastModifiedDate @Column("updated_at") private LocalDateTime updatedAt; diff --git a/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/repository/AuditLogRepository.java b/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/repository/AuditLogRepository.java index fdfb246..18c2925 100644 --- a/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/repository/AuditLogRepository.java +++ b/novalon-manage-api/manage-db/src/main/java/cn/novalon/manage/db/repository/AuditLogRepository.java @@ -5,6 +5,8 @@ import cn.novalon.manage.sys.audit.repository.IAuditLogRepository; import cn.novalon.manage.db.converter.AuditLogConverter; import cn.novalon.manage.db.dao.AuditLogDao; import cn.novalon.manage.db.entity.AuditLogEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -20,6 +22,8 @@ import java.time.LocalDateTime; @Repository public class AuditLogRepository implements IAuditLogRepository { + private static final Logger logger = LoggerFactory.getLogger(AuditLogRepository.class); + private final AuditLogDao auditLogDao; private final AuditLogConverter auditLogConverter; diff --git a/novalon-manage-api/manage-db/src/main/resources/db/migration/V7__Add_audit_log_table.sql b/novalon-manage-api/manage-db/src/main/resources/db/migration/V7__Add_audit_log_table.sql index 84a9018..59ab06d 100644 --- a/novalon-manage-api/manage-db/src/main/resources/db/migration/V7__Add_audit_log_table.sql +++ b/novalon-manage-api/manage-db/src/main/resources/db/migration/V7__Add_audit_log_table.sql @@ -1,7 +1,6 @@ -- Novalon管理系统审计日志表 -- 版本: V7 -- 描述: 创建审计日志表,记录数据变更前后的完整对比 - CREATE TABLE IF NOT EXISTS audit_log ( id BIGSERIAL PRIMARY KEY, entity_type VARCHAR(100) NOT NULL, @@ -11,20 +10,22 @@ CREATE TABLE IF NOT EXISTS audit_log ( operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, before_data JSONB, after_data JSONB, - changed_fields TEXT[], + changed_fields TEXT [], ip_address VARCHAR(50), user_agent TEXT, description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + create_by VARCHAR(50), + update_by VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP ); - CREATE INDEX idx_audit_log_entity_type ON audit_log(entity_type); CREATE INDEX idx_audit_log_entity_id ON audit_log(entity_id); CREATE INDEX idx_audit_log_operation_type ON audit_log(operation_type); CREATE INDEX idx_audit_log_operator ON audit_log(operator); CREATE INDEX idx_audit_log_operation_time ON audit_log(operation_time); CREATE INDEX idx_audit_log_entity ON audit_log(entity_type, entity_id); - COMMENT ON TABLE audit_log IS '审计日志表'; COMMENT ON COLUMN audit_log.id IS '主键ID'; COMMENT ON COLUMN audit_log.entity_type IS '实体类型(如User, Role等)'; @@ -35,7 +36,5 @@ COMMENT ON COLUMN audit_log.operation_time IS '操作时间'; COMMENT ON COLUMN audit_log.before_data IS '变更前数据(JSON格式)'; COMMENT ON COLUMN audit_log.after_data IS '变更后数据(JSON格式)'; COMMENT ON COLUMN audit_log.changed_fields IS '变更字段列表'; -COMMENT ON COLUMN audit_log.ip_address IS 'IP地址'; -COMMENT ON COLUMN audit_log.user_agent IS '用户代理'; -COMMENT ON COLUMN audit_log.description IS '操作描述'; +COMMENT ON COLUMN audit_log.ip_address IS 'IP地址';COMMENT ON COLUMN audit_log.description IS '操作描述'; COMMENT ON COLUMN audit_log.created_at IS '记录创建时间'; diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java index b9b299c..2096433 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/AuditLogAspect.java @@ -1,7 +1,7 @@ package cn.novalon.manage.sys.audit; import cn.novalon.manage.sys.audit.domain.AuditLog; -import cn.novalon.manage.sys.audit.repository.IAuditLogRepository; +import cn.novalon.manage.sys.audit.service.IAuditLogService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.aspectj.lang.ProceedingJoinPoint; @@ -34,11 +34,11 @@ public class AuditLogAspect { private static final Logger logger = LoggerFactory.getLogger(AuditLogAspect.class); - private final IAuditLogRepository auditLogRepository; + private final IAuditLogService auditLogService; private final ObjectMapper objectMapper; - public AuditLogAspect(IAuditLogRepository auditLogRepository, ObjectMapper objectMapper) { - this.auditLogRepository = auditLogRepository; + public AuditLogAspect(IAuditLogService auditLogService, ObjectMapper objectMapper) { + this.auditLogService = auditLogService; this.objectMapper = objectMapper; } @@ -99,6 +99,9 @@ public class AuditLogAspect { String finalOperationType = operationTypeHolder[0]; String finalBeforeData = beforeDataHolder[0]; + logger.debug("保存操作审计日志: entityType={}, entityIdHolder={}, extractedEntityId={}, finalEntityId={}", + entityType, entityIdHolder[0], extractEntityId(savedEntity), finalEntityId); + return createAndSaveAuditLog( entityType, finalEntityId, finalOperationType, finalBeforeData, afterData, savedEntity @@ -163,6 +166,7 @@ public class AuditLogAspect { private Mono createAndSaveAuditLog(String entityType, Long entityId, String operationType, String beforeData, String afterData, Object entity) { + logger.debug("创建审计日志: entityType={}, entityId={}, operationType={}", entityType, entityId, operationType); return ReactiveSecurityContextHolder.getContext() .map(ctx -> ctx.getAuthentication().getPrincipal()) .defaultIfEmpty("system") @@ -175,6 +179,9 @@ public class AuditLogAspect { auditLog.setBeforeData(beforeData); auditLog.setAfterData(afterData); + logger.debug("审计日志对象: entityId={}, entityType={}, operationType={}", + auditLog.getEntityId(), auditLog.getEntityType(), auditLog.getOperationType()); + if (beforeData != null && afterData != null) { String[] changedFields = extractChangedFields(beforeData, afterData); auditLog.setChangedFields(changedFields); @@ -182,7 +189,7 @@ public class AuditLogAspect { auditLog.setDescription(generateDescription(entityType, operationType, entityId)); - return auditLogRepository.save(auditLog) + return auditLogService.save(auditLog) .doOnSuccess(saved -> logger.debug("审计日志保存成功: {} - {}", entityType, operationType)) .doOnError(error -> logger.error("审计日志保存失败: {}", @@ -231,11 +238,14 @@ public class AuditLogAspect { } private Long extractEntityId(Object entity) { + logger.debug("提取实体ID: entity class={}", entity.getClass().getName()); if (entity instanceof Persistable) { Persistable persistable = (Persistable) entity; Object id = persistable.getId(); + logger.debug("Persistable实体ID: id={}, isNew={}", id, persistable.isNew()); return id != null ? ((Number) id).longValue() : null; } + logger.debug("实体不是Persistable类型"); return null; } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java index 3e9bac9..a380f00 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/domain/AuditLog.java @@ -49,7 +49,6 @@ public class AuditLog extends BaseDomain { public AuditLog() { this.operationTime = LocalDateTime.now(); - this.createdAt = LocalDateTime.now(); } public String getEntityType() { diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/IAuditLogService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/IAuditLogService.java new file mode 100644 index 0000000..142895c --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/IAuditLogService.java @@ -0,0 +1,30 @@ +package cn.novalon.manage.sys.audit.service; + +import cn.novalon.manage.sys.audit.domain.AuditLog; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 审计日志服务接口 + * + * @author 张翔 + * @date 2026-04-08 + */ +public interface IAuditLogService { + + Mono save(AuditLog auditLog); + + Mono findById(Long id); + + Flux findAll(); + + Flux findByEntityType(String entityType); + + Flux findByEntityId(Long entityId); + + Flux findByEntityTypeAndEntityId(String entityType, Long entityId); + + Flux findByOperator(String operator); + + Flux findByOperationType(String operationType); +} diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java new file mode 100644 index 0000000..a66a968 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/audit/service/impl/AuditLogService.java @@ -0,0 +1,68 @@ +package cn.novalon.manage.sys.audit.service.impl; + +import cn.novalon.manage.sys.audit.domain.AuditLog; +import cn.novalon.manage.sys.audit.repository.IAuditLogRepository; +import cn.novalon.manage.sys.audit.service.IAuditLogService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 审计日志服务实现类 + * + * @author 张翔 + * @date 2026-04-08 + */ +@Service +public class AuditLogService implements IAuditLogService { + + private static final Logger logger = LoggerFactory.getLogger(AuditLogService.class); + + private final IAuditLogRepository auditLogRepository; + + public AuditLogService(IAuditLogRepository auditLogRepository) { + this.auditLogRepository = auditLogRepository; + } + + @Override + public Mono save(AuditLog auditLog) { + return auditLogRepository.save(auditLog); + } + + @Override + public Mono findById(Long id) { + return auditLogRepository.findById(id); + } + + @Override + public Flux findAll() { + return auditLogRepository.findAll(); + } + + @Override + public Flux findByEntityType(String entityType) { + return auditLogRepository.findByEntityType(entityType); + } + + @Override + public Flux findByEntityId(Long entityId) { + return auditLogRepository.findByEntityId(entityId); + } + + @Override + public Flux findByEntityTypeAndEntityId(String entityType, Long entityId) { + return auditLogRepository.findByEntityTypeAndEntityId(entityType, entityId); + } + + @Override + public Flux findByOperator(String operator) { + return auditLogRepository.findByOperator(operator); + } + + @Override + public Flux findByOperationType(String operationType) { + return auditLogRepository.findByOperationType(operationType); + } +} diff --git a/novalon-manage-web/e2e/journeys/config-workflow.spec.ts b/novalon-manage-web/e2e/journeys/config-workflow.spec.ts new file mode 100644 index 0000000..c35fc42 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/config-workflow.spec.ts @@ -0,0 +1,140 @@ +import { test, expect } from '@playwright/test'; +import { SystemConfigPage } from '../pages/SystemConfigPage'; + +test.describe('系统配置工作流', () => { + let configPage: SystemConfigPage; + const timestamp = Date.now(); + const configKey = `test_config_${timestamp}`; + const configName = `测试配置_${timestamp}`; + const configValue = `测试值_${timestamp}`; + + test.beforeEach(async ({ page }) => { + configPage = new SystemConfigPage(page); + }); + + test('查看系统配置列表', async ({ page }) => { + await test.step('导航到系统配置页面', async () => { + await configPage.goto(); + }); + + await test.step('验证表格显示', async () => { + await expect(configPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('验证数据加载', async () => { + const rowCount = await configPage.getTableRowCount(); + console.log(`系统配置列表包含 ${rowCount} 条记录`); + expect(rowCount).toBeGreaterThanOrEqual(0); + }); + }); + + test('新增系统配置', async ({ page }) => { + await test.step('导航到系统配置页面', async () => { + await configPage.goto(); + }); + + await test.step('点击新增配置按钮', async () => { + await configPage.addButton.click(); + await configPage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + }); + + await test.step('填写配置表单', async () => { + await configPage.configNameInput.fill(configName); + await configPage.configKeyInput.fill(configKey); + await configPage.configValueInput.fill(configValue); + }); + + await test.step('提交表单', async () => { + await configPage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证创建成功', async () => { + await expect(configPage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`配置 ${configName} 创建完成`); + }); + }); + + test('编辑系统配置', async ({ page }) => { + await test.step('导航到系统配置页面', async () => { + await configPage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(configPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击编辑按钮', async () => { + const rows = await configPage.getTableRowCount(); + if (rows > 0) { + const firstRow = configPage.table.locator('tr').first(); + const editBtn = firstRow.getByRole('button', { name: '编辑' }); + if (await editBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await editBtn.click(); + await configPage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + + await test.step('修改配置值', async () => { + const newValue = `更新值_${timestamp}`; + await configPage.configValueInput.clear(); + await configPage.configValueInput.fill(newValue); + }); + + await test.step('提交表单', async () => { + await configPage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证更新成功', async () => { + await expect(configPage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`配置已更新`); + }); + } else { + console.log('未找到编辑按钮,跳过编辑测试'); + } + } else { + console.log('当前没有配置记录,跳过编辑测试'); + } + }); + }); + + test('删除系统配置', async ({ page }) => { + await test.step('导航到系统配置页面', async () => { + await configPage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(configPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击删除按钮', async () => { + const rows = await configPage.getTableRowCount(); + if (rows > 0) { + const firstRow = configPage.table.locator('tr').first(); + const deleteBtn = firstRow.getByRole('button', { name: '删除' }); + if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await deleteBtn.click(); + const confirmBtn = page.locator('.el-message-box'); + await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); + + await test.step('确认删除', async () => { + const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { + await confirmBtn.click(); + await page.waitForLoadState('networkidle'); + } + }); + + await test.step('验证删除成功', async () => { + const messageBox = page.locator('.el-message-box'); + await expect(messageBox).not.toBeVisible({ timeout: 5000 }); + console.log(`配置已删除`); + }); + } else { + console.log('未找到删除按钮,跳过删除测试'); + } + } else { + console.log('当前没有配置记录,跳过删除测试'); + } + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts b/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts new file mode 100644 index 0000000..d9fcbb7 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/dict-workflow.spec.ts @@ -0,0 +1,138 @@ +import { test, expect } from '@playwright/test'; +import { DictionaryManagementPage } from '../pages/DictionaryManagementPage'; + +test.describe('字典管理工作流', () => { + let dictPage: DictionaryManagementPage; + const timestamp = Date.now(); + const dictType = `test_dict_${timestamp}`; + const dictName = `测试字典_${timestamp}`; + + test.beforeEach(async ({ page }) => { + dictPage = new DictionaryManagementPage(page); + }); + + test('查看字典列表', async ({ page }) => { + await test.step('导航到字典管理页面', async () => { + await dictPage.goto(); + }); + + await test.step('验证表格显示', async () => { + await expect(dictPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('验证数据加载', async () => { + const rowCount = await dictPage.getDictCount(); + console.log(`字典列表包含 ${rowCount} 条记录`); + expect(rowCount).toBeGreaterThanOrEqual(0); + }); + }); + + test('新增字典', async ({ page }) => { + await test.step('导航到字典管理页面', async () => { + await dictPage.goto(); + }); + + await test.step('点击新增字典按钮', async () => { + await dictPage.createDictButton.click(); + await dictPage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + }); + + await test.step('填写字典表单', async () => { + await dictPage.dictNameInput.fill(dictName); + await dictPage.dictTypeInput.fill(dictType); + }); + + await test.step('提交表单', async () => { + await dictPage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证创建成功', async () => { + await expect(dictPage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`字典 ${dictName} 创建完成`); + }); + }); + + test('编辑字典', async ({ page }) => { + await test.step('导航到字典管理页面', async () => { + await dictPage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(dictPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击编辑按钮', async () => { + const rows = await dictPage.getDictCount(); + if (rows > 0) { + const firstRow = dictPage.table.locator('tr').first(); + const editBtn = firstRow.getByRole('button', { name: '编辑' }); + if (await editBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await editBtn.click(); + await dictPage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + + await test.step('修改字典名称', async () => { + const newName = `更新字典_${timestamp}`; + await dictPage.dictNameInput.clear(); + await dictPage.dictNameInput.fill(newName); + }); + + await test.step('提交表单', async () => { + await dictPage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证更新成功', async () => { + await expect(dictPage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`字典已更新`); + }); + } else { + console.log('未找到编辑按钮,跳过编辑测试'); + } + } else { + console.log('当前没有字典记录,跳过编辑测试'); + } + }); + }); + + test('删除字典', async ({ page }) => { + await test.step('导航到字典管理页面', async () => { + await dictPage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(dictPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击删除按钮', async () => { + const rows = await dictPage.getDictCount(); + if (rows > 0) { + const firstRow = dictPage.table.locator('tr').first(); + const deleteBtn = firstRow.getByRole('button', { name: '删除' }); + if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await deleteBtn.click(); + const confirmBtn = page.locator('.el-message-box'); + await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); + + await test.step('确认删除', async () => { + const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { + await confirmBtn.click(); + await page.waitForLoadState('networkidle'); + } + }); + + await test.step('验证删除成功', async () => { + const messageBox = page.locator('.el-message-box'); + await expect(messageBox).not.toBeVisible({ timeout: 5000 }); + console.log(`字典已删除`); + }); + } else { + console.log('未找到删除按钮,跳过删除测试'); + } + } else { + console.log('当前没有字典记录,跳过删除测试'); + } + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts b/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts new file mode 100644 index 0000000..91080f2 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/exception-log-workflow.spec.ts @@ -0,0 +1,72 @@ +import { test, expect } from '@playwright/test'; +import { ExceptionLogPage } from '../pages/ExceptionLogPage'; + +test.describe('异常日志工作流', () => { + let exceptionLogPage: ExceptionLogPage; + + test.beforeEach(async ({ page }) => { + exceptionLogPage = new ExceptionLogPage(page); + }); + + test('查看异常日志列表', async ({ page }) => { + await test.step('导航到异常日志页面', async () => { + await exceptionLogPage.goto(); + }); + + await test.step('验证表格显示', async () => { + await expect(exceptionLogPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('验证数据加载', async () => { + const rowCount = await exceptionLogPage.getLogCount(); + console.log(`异常日志列表包含 ${rowCount} 条记录`); + expect(rowCount).toBeGreaterThanOrEqual(0); + }); + }); + + test('搜索异常日志', async ({ page }) => { + await test.step('导航到异常日志页面', async () => { + await exceptionLogPage.goto(); + }); + + await test.step('输入搜索关键词', async () => { + const searchKeyword = 'NullPointerException'; + await exceptionLogPage.search(searchKeyword); + }); + + await test.step('验证搜索结果', async () => { + await page.waitForLoadState('networkidle'); + const rowCount = await exceptionLogPage.getLogCount(); + console.log(`搜索结果包含 ${rowCount} 条记录`); + }); + }); + + test('查看异常日志详情', async ({ page }) => { + await test.step('导航到异常日志页面', async () => { + await exceptionLogPage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(exceptionLogPage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击查看详情按钮', async () => { + const detailButton = page.locator('button:has-text("详情")').or(page.locator('.detail-button')).first(); + if (await detailButton.isVisible({ timeout: 3000 }).catch(() => false)) { + await detailButton.click(); + + await test.step('验证详情对话框显示', async () => { + const dialog = page.locator('.el-dialog'); + await expect(dialog).toBeVisible({ timeout: 5000 }); + console.log('异常日志详情对话框已打开'); + }); + + await test.step('关闭详情对话框', async () => { + await exceptionLogPage.closeDetailDialog(); + }); + } else { + console.log('当前没有异常日志记录,跳过详情查看测试'); + } + }); + }); +}); diff --git a/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts b/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts new file mode 100644 index 0000000..ec199c0 --- /dev/null +++ b/novalon-manage-web/e2e/journeys/notice-workflow.spec.ts @@ -0,0 +1,138 @@ +import { test, expect } from '@playwright/test'; +import { NotificationPage } from '../pages/NotificationPage'; + +test.describe('通知管理工作流', () => { + let noticePage: NotificationPage; + const timestamp = Date.now(); + const noticeTitle = `测试通知_${timestamp}`; + const noticeContent = `这是测试通知内容_${timestamp}`; + + test.beforeEach(async ({ page }) => { + noticePage = new NotificationPage(page); + }); + + test('查看通知列表', async ({ page }) => { + await test.step('导航到通知管理页面', async () => { + await noticePage.goto(); + }); + + await test.step('验证表格显示', async () => { + await expect(noticePage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('验证数据加载', async () => { + const rowCount = await noticePage.getTableRowCount(); + console.log(`通知列表包含 ${rowCount} 条记录`); + expect(rowCount).toBeGreaterThanOrEqual(0); + }); + }); + + test('新增通知', async ({ page }) => { + await test.step('导航到通知管理页面', async () => { + await noticePage.goto(); + }); + + await test.step('点击新增通知按钮', async () => { + await noticePage.addButton.click(); + await noticePage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + }); + + await test.step('填写通知表单', async () => { + await noticePage.titleInput.fill(noticeTitle); + await noticePage.contentInput.fill(noticeContent); + }); + + await test.step('提交表单', async () => { + await noticePage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证创建成功', async () => { + await expect(noticePage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`通知 ${noticeTitle} 创建完成`); + }); + }); + + test('编辑通知', async ({ page }) => { + await test.step('导航到通知管理页面', async () => { + await noticePage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(noticePage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击编辑按钮', async () => { + const rows = await noticePage.getTableRowCount(); + if (rows > 0) { + const firstRow = noticePage.table.locator('tr').first(); + const editBtn = firstRow.getByRole('button', { name: '编辑' }); + if (await editBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await editBtn.click(); + await noticePage.dialog.waitFor({ state: 'visible', timeout: 5000 }); + + await test.step('修改通知内容', async () => { + const newContent = `更新通知内容_${timestamp}`; + await noticePage.contentInput.clear(); + await noticePage.contentInput.fill(newContent); + }); + + await test.step('提交表单', async () => { + await noticePage.saveButton.click(); + await page.waitForLoadState('networkidle'); + }); + + await test.step('验证更新成功', async () => { + await expect(noticePage.dialog).not.toBeVisible({ timeout: 5000 }); + console.log(`通知已更新`); + }); + } else { + console.log('未找到编辑按钮,跳过编辑测试'); + } + } else { + console.log('当前没有通知记录,跳过编辑测试'); + } + }); + }); + + test('删除通知', async ({ page }) => { + await test.step('导航到通知管理页面', async () => { + await noticePage.goto(); + }); + + await test.step('等待数据加载', async () => { + await expect(noticePage.table).toBeVisible({ timeout: 10000 }); + }); + + await test.step('点击删除按钮', async () => { + const rows = await noticePage.getTableRowCount(); + if (rows > 0) { + const firstRow = noticePage.table.locator('tr').first(); + const deleteBtn = firstRow.getByRole('button', { name: '删除' }); + if (await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await deleteBtn.click(); + const confirmBtn = page.locator('.el-message-box'); + await confirmBtn.waitFor({ state: 'visible', timeout: 3000 }); + + await test.step('确认删除', async () => { + const confirmBtn = page.locator('.el-message-box').getByRole('button', { name: '确定' }); + if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { + await confirmBtn.click(); + await page.waitForLoadState('networkidle'); + } + }); + + await test.step('验证删除成功', async () => { + const messageBox = page.locator('.el-message-box'); + await expect(messageBox).not.toBeVisible({ timeout: 5000 }); + console.log(`通知已删除`); + }); + } else { + console.log('未找到删除按钮,跳过删除测试'); + } + } else { + console.log('当前没有通知记录,跳过删除测试'); + } + }); + }); +}); diff --git a/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts b/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts index 0144cbe..c9baba7 100644 --- a/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts +++ b/novalon-manage-web/e2e/pages/DictionaryManagementPage.ts @@ -3,24 +3,24 @@ import { Page, Locator, expect } from '@playwright/test'; export class DictionaryManagementPage { readonly page: Page; readonly table: Locator; - readonly createDictTypeButton: Locator; - readonly createDictDataButton: Locator; - readonly searchInput: Locator; - readonly searchButton: Locator; - readonly successMessage: Locator; - readonly dictTypeTable: Locator; - readonly dictDataTable: Locator; + readonly createDictButton: Locator; + readonly saveButton: Locator; + readonly dialog: Locator; + readonly dictNameInput: Locator; + readonly dictTypeInput: Locator; + readonly statusSelect: Locator; + readonly remarkInput: Locator; constructor(page: Page) { this.page = page; - this.table = page.locator('.el-table').or(page.locator('.dict-table')); - this.createDictTypeButton = page.getByRole('button', { name: '新增字典类型' }).or(page.locator('button:has-text("新增字典类型")')); - this.createDictDataButton = page.getByRole('button', { name: '新增字典数据' }).or(page.locator('button:has-text("新增字典数据")')); - this.searchInput = page.locator('input[placeholder*="搜索"]').or(page.locator('input[name*="keyword"]')); - this.searchButton = page.getByRole('button', { name: '搜索' }).or(page.locator('button:has-text("搜索")')); - this.successMessage = page.locator('.el-message--success').or(page.locator('.success-message')); - this.dictTypeTable = page.locator('.dict-type-table').or(page.locator('.el-table').first()); - this.dictDataTable = page.locator('.dict-data-table').or(page.locator('.el-table').nth(1)); + this.table = page.locator('.el-table'); + this.createDictButton = page.getByRole('button', { name: '新增字典' }); + this.saveButton = page.getByRole('button', { name: '确定' }); + this.dialog = page.locator('.el-dialog'); + this.dictNameInput = page.locator('.el-dialog').getByRole('textbox', { name: '字典名称' }); + this.dictTypeInput = page.locator('.el-dialog').getByRole('textbox', { name: '字典类型' }); + this.statusSelect = page.locator('.el-dialog').getByRole('combobox', { name: '状态' }); + this.remarkInput = page.locator('.el-dialog').getByRole('textbox', { name: '备注' }); } async goto() { @@ -40,156 +40,57 @@ export class DictionaryManagementPage { } } - async clickCreateDictType() { - await this.createDictTypeButton.click(); + async createDict(dictName: string, dictType: string, status: string = '0', remark?: string) { + await this.createDictButton.click(); await this.page.waitForTimeout(500); + + await this.dictNameInput.fill(dictName); + await this.dictTypeInput.fill(dictType); + + if (status) { + await this.statusSelect.click(); + await this.page.waitForTimeout(300); + await this.page.getByRole('option', { name: status === '0' ? '正常' : '停用' }).click(); + } + + if (remark) { + await this.remarkInput.fill(remark); + } + + await this.saveButton.click(); + await this.page.waitForLoadState('networkidle'); } - async clickCreateDictData() { - await this.createDictDataButton.click(); + async editDict(dictName: string, newDictName: string) { + const row = this.table.locator('tr').filter({ hasText: dictName }).first(); + const editBtn = row.getByRole('button', { name: '编辑' }); + await editBtn.click(); await this.page.waitForTimeout(500); + + await this.dictNameInput.clear(); + await this.dictNameInput.fill(newDictName); + + await this.saveButton.click(); + await this.page.waitForLoadState('networkidle'); } - async fillDictTypeForm(dictTypeData: { - dictName: string; - dictType: string; - status?: string; - remark?: string; - }) { - const dialog = this.page.locator('.el-dialog'); + async deleteDict(dictName: string) { + const row = this.table.locator('tr').filter({ hasText: dictName }).first(); + const deleteBtn = row.getByRole('button', { name: '删除' }); + await deleteBtn.click(); + await this.page.waitForTimeout(500); - await dialog.locator('input').first().fill(dictTypeData.dictName); - await dialog.locator('input').nth(1).fill(dictTypeData.dictType); - - if (dictTypeData.status) { - const statusRadio = dialog.locator(`input[value="${dictTypeData.status}"]`); - if (await statusRadio.count() > 0) { - await statusRadio.check(); - } - } - - if (dictTypeData.remark) { - const remarkInput = dialog.locator('textarea'); - if (await remarkInput.count() > 0) { - await remarkInput.fill(dictTypeData.remark); - } - } + const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); + await confirmBtn.click(); + await this.page.waitForLoadState('networkidle'); } - async fillDictDataForm(dictData: { - dictLabel: string; - dictValue: string; - dictType?: string; - cssClass?: string; - listClass?: string; - isDefault?: string; - status?: string; - sort?: number; - }) { - const dialog = this.page.locator('.el-dialog'); - - await dialog.locator('input').first().fill(dictData.dictLabel); - await dialog.locator('input').nth(1).fill(dictData.dictValue); - - if (dictData.dictType) { - const dictTypeSelect = dialog.locator('.el-select'); - if (await dictTypeSelect.count() > 0) { - await dictTypeSelect.click(); - await this.page.waitForTimeout(300); - await this.page.getByRole('option', { name: dictData.dictType }).click(); - } - } - - if (dictData.cssClass) { - const cssClassInput = dialog.locator('input[placeholder*="CSS"]'); - if (await cssClassInput.count() > 0) { - await cssClassInput.fill(dictData.cssClass); - } - } - - if (dictData.listClass) { - const listClassInput = dialog.locator('input[placeholder*="列表"]'); - if (await listClassInput.count() > 0) { - await listClassInput.fill(dictData.listClass); - } - } - - if (dictData.isDefault) { - const defaultRadio = dialog.locator(`input[value="${dictData.isDefault}"]`); - if (await defaultRadio.count() > 0) { - await defaultRadio.check(); - } - } - - if (dictData.status) { - const statusRadio = dialog.locator(`input[value="${dictData.status}"]`); - if (await statusRadio.count() > 0) { - await statusRadio.check(); - } - } - - if (dictData.sort !== undefined) { - const sortInput = dialog.locator('input[type="number"]'); - if (await sortInput.count() > 0) { - await sortInput.fill(String(dictData.sort)); - } - } - } - - async submitForm() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('button:has-text("确定")')).click(); - } - - async editDictType(dictName: string) { - const dictTypeRow = this.dictTypeTable.locator('tbody tr').filter({ hasText: dictName }); - await dictTypeRow.getByRole('button', { name: '编辑' }).or(this.page.locator('.edit-button')).click(); - } - - async editDictData(dictLabel: string) { - const dictDataRow = this.dictDataTable.locator('tbody tr').filter({ hasText: dictLabel }); - await dictDataRow.getByRole('button', { name: '编辑' }).or(this.page.locator('.edit-button')).click(); - } - - async deleteDictType(dictName: string) { - const dictTypeRow = this.dictTypeTable.locator('tbody tr').filter({ hasText: dictName }); - await dictTypeRow.getByRole('button', { name: '删除' }).or(this.page.locator('.delete-button')).click(); - } - - async deleteDictData(dictLabel: string) { - const dictDataRow = this.dictDataTable.locator('tbody tr').filter({ hasText: dictLabel }); - await dictDataRow.getByRole('button', { name: '删除' }).or(this.page.locator('.delete-button')).click(); - } - - async confirmDelete() { - await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('.confirm-dialog .confirm-button')).click(); - } - - async search(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); + async getDictCount() { + const rows = await this.table.locator('.el-table__row').count(); + return rows; } async containsText(text: string): Promise { return await this.table.getByText(text).count() > 0; } - - async isSuccessMessageVisible(): Promise { - try { - return await this.successMessage.isVisible({ timeout: 3000 }); - } catch { - return false; - } - } - - async getDictTypeCount(): Promise { - return await this.dictTypeTable.locator('tbody tr').count(); - } - - async getDictDataCount(): Promise { - return await this.dictDataTable.locator('tbody tr').count(); - } - - async reload() { - await this.page.reload(); - } } diff --git a/novalon-manage-web/e2e/pages/NotificationPage.ts b/novalon-manage-web/e2e/pages/NotificationPage.ts index 75d2cbe..4996ece 100644 --- a/novalon-manage-web/e2e/pages/NotificationPage.ts +++ b/novalon-manage-web/e2e/pages/NotificationPage.ts @@ -4,89 +4,85 @@ export class NotificationPage { readonly page: Page; readonly table; readonly addButton; - readonly editButton; - readonly deleteButton; readonly saveButton; readonly cancelButton; - readonly searchInput; - readonly searchButton; + readonly dialog; readonly titleInput; readonly contentInput; - readonly typeSelect; + readonly noticeTypeSelect; readonly statusSelect; constructor(page: Page) { this.page = page; this.table = page.locator('.el-table'); - this.addButton = page.getByRole('button', { name: '新增' }); - this.editButton = page.getByRole('button', { name: '修改' }); - this.deleteButton = page.getByRole('button', { name: '删除' }); + this.addButton = page.getByRole('button', { name: '新增公告' }); this.saveButton = page.getByRole('button', { name: '确定' }); this.cancelButton = page.getByRole('button', { name: '取消' }); - this.searchInput = page.getByPlaceholder('搜索通知标题'); - this.searchButton = page.getByRole('button', { name: '搜索' }); - this.titleInput = page.getByPlaceholder('请输入通知标题'); - this.contentInput = page.getByPlaceholder('请输入通知内容'); - this.typeSelect = page.locator('.el-select'); - this.statusSelect = page.locator('.el-select'); + this.dialog = page.locator('.el-dialog'); + this.titleInput = page.locator('.el-dialog').getByRole('textbox', { name: '公告标题' }); + this.contentInput = page.locator('.el-dialog').getByRole('textbox', { name: '公告内容' }); + this.noticeTypeSelect = page.locator('.el-dialog').getByRole('combobox', { name: '公告类型' }); + this.statusSelect = page.locator('.el-dialog').getByRole('combobox', { name: '状态' }); } async goto() { - await this.page.goto('/system/notice'); - await this.page.waitForLoadState('networkidle'); + try { + console.log('导航到通知管理页面...'); + await this.page.goto('/notice'); + + await this.page.waitForLoadState('networkidle'); + await this.table.waitFor({ state: 'visible', timeout: 10000 }); + await expect(this.page).toHaveURL(/.*notice/); + + console.log('通知管理页面加载完成'); + } catch (error) { + await this.page.screenshot({ path: `test-results/notification-error-${Date.now()}.png` }); + console.error('导航到通知管理页面失败:', error); + throw new Error(`导航到通知管理页面失败: ${error instanceof Error ? error.message : String(error)}`); + } } - async addNotification(title: string, content: string, type: string = '1', status: string = '0') { + async addNotification(title: string, content: string) { await this.addButton.click(); - + await this.page.waitForTimeout(500); + await this.titleInput.fill(title); await this.contentInput.fill(content); - + await this.saveButton.click(); await this.page.waitForLoadState('networkidle'); } async editNotification(title: string, newContent: string) { const row = this.table.locator('tr').filter({ hasText: title }).first(); - await row.locator('.el-button--primary').click(); - + const editBtn = row.getByRole('button', { name: '编辑' }); + await editBtn.click(); + await this.page.waitForTimeout(500); + await this.contentInput.clear(); await this.contentInput.fill(newContent); - + await this.saveButton.click(); await this.page.waitForLoadState('networkidle'); } async deleteNotification(title: string) { const row = this.table.locator('tr').filter({ hasText: title }).first(); - await row.locator('.el-button--danger').click(); - - await this.saveButton.click(); + const deleteBtn = row.getByRole('button', { name: '删除' }); + await deleteBtn.click(); + await this.page.waitForTimeout(500); + + const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); + await confirmBtn.click(); await this.page.waitForLoadState('networkidle'); } - async searchNotification(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); - await this.page.waitForLoadState('networkidle'); - } - - async clearSearch() { - await this.searchInput.clear(); - await this.searchButton.click(); - await this.page.waitForLoadState('networkidle'); - } - - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); - } - - async verifyTableNotContains(text: string) { - await expect(this.table).not.toContainText(text); - } - async getTableRowCount() { const rows = await this.table.locator('.el-table__row').count(); return rows; } -} \ No newline at end of file + + async verifyTableContains(text: string) { + await expect(this.table).toContainText(text); + } +} diff --git a/novalon-manage-web/e2e/pages/SystemConfigPage.ts b/novalon-manage-web/e2e/pages/SystemConfigPage.ts index 0850e8e..18dfb1a 100644 --- a/novalon-manage-web/e2e/pages/SystemConfigPage.ts +++ b/novalon-manage-web/e2e/pages/SystemConfigPage.ts @@ -4,31 +4,23 @@ export class SystemConfigPage { readonly page: Page; readonly table; readonly addButton; - readonly editButton; - readonly deleteButton; readonly saveButton; readonly cancelButton; - readonly searchInput; - readonly searchButton; + readonly dialog; readonly configNameInput; readonly configKeyInput; readonly configValueInput; - readonly configTypeSelect; constructor(page: Page) { this.page = page; this.table = page.locator('.el-table'); this.addButton = page.getByRole('button', { name: '新增配置' }); - this.editButton = page.getByRole('button', { name: '编辑' }); - this.deleteButton = page.getByRole('button', { name: '删除' }); this.saveButton = page.getByRole('button', { name: '确定' }); this.cancelButton = page.getByRole('button', { name: '取消' }); - this.searchInput = page.getByPlaceholder('搜索配置名称'); - this.searchButton = page.getByRole('button', { name: '搜索' }); - this.configNameInput = page.getByPlaceholder('请输入配置名称'); - this.configKeyInput = page.getByPlaceholder('请输入配置键名'); - this.configValueInput = page.getByPlaceholder('请输入配置键值'); - this.configTypeSelect = page.locator('.el-select'); + this.dialog = page.locator('.el-dialog'); + this.configNameInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数名称' }); + this.configKeyInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数键名' }); + this.configValueInput = page.locator('.el-dialog').getByRole('textbox', { name: '参数值' }); } async goto() { @@ -48,8 +40,9 @@ export class SystemConfigPage { } } - async addConfig(configName: string, configKey: string, configValue: string, configType: string = 'Y') { + async addConfig(configName: string, configKey: string, configValue: string) { await this.addButton.click(); + await this.page.waitForTimeout(500); await this.configNameInput.fill(configName); await this.configKeyInput.fill(configKey); @@ -61,7 +54,9 @@ export class SystemConfigPage { async editConfig(configKey: string, newValue: string) { const row = this.table.locator('tr').filter({ hasText: configKey }).first(); - await row.locator('.el-button--primary').click(); + const editBtn = row.getByRole('button', { name: '编辑' }); + await editBtn.click(); + await this.page.waitForTimeout(500); await this.configValueInput.clear(); await this.configValueInput.fill(newValue); @@ -72,34 +67,21 @@ export class SystemConfigPage { async deleteConfig(configKey: string) { const row = this.table.locator('tr').filter({ hasText: configKey }).first(); - await row.locator('.el-button--danger').click(); + const deleteBtn = row.getByRole('button', { name: '删除' }); + await deleteBtn.click(); + await this.page.waitForTimeout(500); - await this.saveButton.click(); + const confirmBtn = this.page.locator('.el-message-box').getByRole('button', { name: '确定' }); + await confirmBtn.click(); await this.page.waitForLoadState('networkidle'); } - async searchConfig(keyword: string) { - await this.searchInput.fill(keyword); - await this.searchButton.click(); - await this.page.waitForLoadState('networkidle'); - } - - async clearSearch() { - await this.searchInput.clear(); - await this.searchButton.click(); - await this.page.waitForLoadState('networkidle'); - } - - async verifyTableContains(text: string) { - await expect(this.table).toContainText(text); - } - - async verifyTableNotContains(text: string) { - await expect(this.table).not.toContainText(text); - } - async getTableRowCount() { const rows = await this.table.locator('.el-table__row').count(); return rows; } -} \ No newline at end of file + + async verifyTableContains(text: string) { + await expect(this.table).toContainText(text); + } +} diff --git a/novalon-manage-web/playwright/.auth/user.json b/novalon-manage-web/playwright/.auth/user.json index b72a03c..4a98ba9 100644 --- a/novalon-manage-web/playwright/.auth/user.json +++ b/novalon-manage-web/playwright/.auth/user.json @@ -6,7 +6,7 @@ "localStorage": [ { "name": "token", - "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NTYzMzUxMiwiZXhwIjoxNzc1NzE5OTEyfQ.z8GEm_YKF0jGh_KWAk9NVW41TL9JDY5RcgQaLEpktrk2JRDWs9gfuLV0fQSs3EJ5" + "value": "eyJhbGciOiJIUzM4NCJ9.eyJyb2xlcyI6WyJhZG1pbiJdLCJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJzdWIiOiJhZG1pbiIsImlhdCI6MTc3NTY0ODAzOCwiZXhwIjoxNzc1NzM0NDM4fQ.jCpkwk034HQKIYBWdZ5qjIe8rkxrar6fSLNauoJM0UgOFfVSBuoxaMpIzRHC7KDS" }, { "name": "permission",