原先的"注销用户"功能执行了系统级的注销操作(shutdown /l
),导致注销了整个操作系统用户,而不是应用程序内的用户。
将系统级注销改为应用级注销,即清除应用内的用户状态并返回登录页面。
src/components/ExitModal.tsx
useDispatch
和 clearUserInfo
logout
case 中调用 dispatch(clearUserInfo())
清除 Redux 用户状态window.electronAPI.logoutUser()
调用logoutUser
APIpreload.js
logoutUser
API 暴露main.js
exit-logout
IPC handlergetSystemCommands()
中的 logout
命令配置src/API/security/userActions.ts
logout()
函数占位cypress/support/mock/handlers/user.ts
mockLogoutSuccess()
mock handlercypress/e2e/security/logout.cy.ts
(新增)login.cy.ts
移动到 cypress/e2e/security/
目录ExitModal
组件调用 dispatch(clearUserInfo())
src/pages/index/index.tsx
检测到 loggedIn = false
<Login />
组件,返回登录页面为确保测试能够正常运行,logout 测试依赖以下 mock handlers:
i18n Mocks (cypress/support/mock/handlers/i18n.ts
)
mockAllRequiredAPIs()
- 模拟软件信息和日志 APImockI18nSuccess('zh')
- 模拟多语言资源加载成功Quota Mocks (cypress/support/mock/handlers/quota.ts
)
mockGetQuotaSuccess()
- 模拟配额检查成功Protocol Mocks (cypress/support/mock/handlers/protocol.ts
) - 重要!
mockGetPatientTypeHuman()
- 模拟患者类型 APImockGetBodyPartHuman()
- 模拟身体部位 APIUser Mocks (cypress/support/mock/handlers/user.ts
)
mockLoginSuccess()
- 模拟登录成功mockLogoutSuccess()
- 模拟登出成功(可选,当前未使用)beforeEach(() => {
cy.clearAllSessionStorage();
cy.clearAllLocalStorage();
mockAllRequiredAPIs(); // 软件信息、日志
mockI18nSuccess('zh'); // 多语言资源
mockGetQuotaSuccess(); // 配额检查
mockGetPatientTypeHuman(); // 患者类型 ← 必需!
mockGetBodyPartHuman(); // 身体部位 ← 必需!
mockLoginSuccess(); // 登录
loginPage.visit();
loginPage.login('admin', '123456');
});
登录成功后,AppInitializer
会执行以下初始化操作:
getPatientTypes()
- 获取患者类型列表getBodyParts()
- 获取身体部位列表initializeProductState()
- 初始化产品状态重要:只有这三个操作全部完成后,才会调用 onInitialized()
进入主界面。如果缺少相应的 mock,测试会卡在"正在初始化,请稍候..."
# 运行所有安全相关测试
npm run cypress:run -- --spec "cypress/e2e/security/**/*.cy.ts"
# 只运行 logout 测试
npm run cypress:run -- --spec "cypress/e2e/security/logout.cy.ts"
# 以交互模式运行
npm run cypress:open
src/API/security/userActions.ts
中的 logout()
函数: export function logout(): Promise<{ data: { code: string } }> {
return axiosInstance.post('/pub/logout');
}
ExitModal.tsx
中调用后端 API(可选): case 'logout':
actionName = '注销用户';
try {
// 调用后端 logout API
await logout();
} catch (error) {
console.error('后端登出失败:', error);
// 即使后端失败也继续清除前端状态
}
dispatch(clearUserInfo());
message.success('已退出登录');
onClose();
return;
src/pages/index/index.tsx
中,无需修改src/components/ExitModal.tsx
preload.js
main.js
src/API/security/userActions.ts
src/layouts/SystemZone.tsx
- 添加 data-testid="exit-button"
cypress/support/mock/handlers/user.ts
cypress/support/pageObjects/MainPage.ts
- 添加退出按钮方法cypress/support/pageObjects/ExitModalPage.ts
(新增)cypress/e2e/security/logout.cy.ts
(新增)cypress/e2e/login.cy.ts
→ cypress/e2e/security/login.cy.ts
(移动)src/states/user_info/index.ts
(已有 clearUserInfo
action)src/pages/index/index.tsx
(已有登录状态检查逻辑)为提高测试可维护性和可读性,使用了 Page Object 模式:
cypress/support/pageObjects/MainPage.ts
)clickExitButton()
- 点击退出按钮(带 10 秒超时等待)getExitButton()
- 获取退出按钮元素cypress/support/pageObjects/ExitModalPage.ts
)新增专门的退出 Modal Page Object:
shouldBeVisible()
- 验证 modal 可见shouldNotBeVisible()
- 验证 modal 已关闭clickLogout()
- 点击"注销用户"clickClose()
- 点击"关闭程序"clickShutdown()
- 点击"关机"clickCancel()
- 点击"取消"为所有测试相关的按钮添加了 data-testid
属性,便于测试定位:
SystemZone.tsx - 退出按钮:
data-testid="exit-button"
ExitModal.tsx - Modal 内的四个按钮:
data-testid="exit-modal-close-button"
- 关闭程序data-testid="exit-modal-logout-button"
- 注销用户data-testid="exit-modal-shutdown-button"
- 关机data-testid="exit-modal-cancel-button"
- 取消为什么使用 data-testid?
it('should successfully logout and return to login page', () => {
// 使用 Page Object 点击退出按钮(带超时等待)
mainPage.clickExitButton();
// 验证 modal 可见
exitModalPage.shouldBeVisible();
// 点击"注销用户"按钮
exitModalPage.clickLogout();
// 验证成功消息和返回登录页
cy.contains('已退出登录').should('be.visible');
loginPage.getUsernameInput().should('be.visible');
});
clickExitButton
方法中)shouldBeVisible
方法中)✅ 所有计划的修改已完成 ✅ 所有测试代码已编写 ⏳ 等待手动测试验证 ⏳ 等待后端 API 开发