123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697 |
- /**
- * 测试文件: 检查页面 - 退出检查逻辑
- * 功能: 验证从检查页面退出时的各种场景和逻辑处理
- *
- * 测试场景:
- * 1. 未曝光状态直接退出
- * 2. 半曝光状态 - 继续检查
- * 3. 半曝光状态 - 保存并完成
- * 4. 半曝光状态 - 直接中止
- * 5. 全曝光状态退出
- * 6. 发生器采集中阻止退出到process
- * 7. 从检查正常退出到process(非采集)
- * 8. 边界场景:快速连续点击退出
- *
- * 相关需求文档: docs/实现/退出检查的逻辑.md
- */
- import LoginPage from '../../support/pageObjects/LoginPage';
- import MainPage from '../../support/pageObjects/MainPage';
- import WorklistPage from '../../support/pageObjects/WorklistPage';
- describe('检查页面:退出检查逻辑', () => {
- const loginPage = new LoginPage();
- const mainPage = new MainPage();
- const worklistPage = new WorklistPage();
- /**
- * 通用的登录和进入检查页面的前置步骤
- */
- beforeEach(() => {
- // 登录系统
- loginPage.visit();
- loginPage.login('admin', '123456');
- cy.contains('登录成功').should('be.visible', { timeout: 10000 });
- // 导航到工作列表
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- // 双击第一行进入检查页面
- worklistPage.findTableAndDoubleClickFirstRow();
- cy.wait(1000);
- // 验证已进入检查页面
- cy.get('[data-testid="exam-page"]').should('exist');
- });
- /**
- * 测试场景 1:未曝光状态直接退出
- *
- * Given: 用户已登录系统并从工作列表进入检查页面
- * 未进行任何曝光操作
- * When: 用户点击导航按钮切换到工作列表
- * Then: 应该直接退出检查页面,不显示退出反馈弹窗
- */
- it('场景1:未曝光状态应该直接退出检查', () => {
- /**
- * Given: 验证当前在检查页面
- */
- cy.get('[data-testid="exam-page"]').should('exist');
- /**
- * Given: 验证曝光状态为 "Not Exposed"
- */
- cy.window().its('store').invoke('getState')
- .its('bodyPositionList')
- .its('exposureStatus')
- .should('equal', 'Not Exposed');
- /**
- * When: 点击导航按钮切换到工作列表
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证成功退出到工作列表页面
- */
- cy.url().should('include', '/worklist');
- /**
- * Then: 验证未显示退出反馈弹窗
- */
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证Redux store中业务流程已切换
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'worklist');
- });
- /**
- * 测试场景 2:半曝光状态 - 继续检查
- *
- * Given: 用户已登录系统并进入检查页面
- * 已完成部分体位的曝光
- * When: 用户点击退出,显示反馈弹窗
- * 用户点击"继续检查"按钮
- * Then: 弹窗关闭,停留在检查页面
- */
- it('场景2:半曝光状态点击"继续检查"应该停留在检查页面', () => {
- /**
- * Given: 模拟半曝光状态
- * 通过Redux store直接设置状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- });
- /**
- * Given: 验证曝光状态为 "Half Exposed"
- */
- cy.window().its('store').invoke('getState')
- .its('bodyPositionList')
- .its('exposureStatus')
- .should('equal', 'Half Exposed');
- /**
- * When: 点击导航按钮尝试退出
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证显示退出反馈弹窗
- */
- cy.get('[role="dialog"]').should('be.visible');
- /**
- * Then: 验证弹窗标题
- */
- cy.get('[role="dialog"]').should('contain', '检查未完成');
- /**
- * Then: 验证弹窗提示信息
- */
- cy.get('[role="dialog"]').should('contain', '当前检查的曝光步骤尚未完成');
- /**
- * Then: 验证Redux store中弹窗状态
- */
- cy.window().its('store').invoke('getState')
- .its('largeScreen')
- .its('isFeedbackOpen')
- .should('be.true');
- /**
- * When: 点击"继续检查"按钮
- */
- cy.get('[role="dialog"]').contains('button', '继续检查').click();
- /**
- * Then: 验证弹窗关闭
- */
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证仍停留在检查页面
- */
- cy.get('[data-testid="exam-page"]').should('exist');
- cy.url().should('include', '/exam');
- /**
- * Then: 验证Redux store中弹窗已关闭
- */
- cy.window().its('store').invoke('getState')
- .its('largeScreen')
- .its('isFeedbackOpen')
- .should('be.false');
- /**
- * Then: 验证业务流程仍为exam
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'exam');
- });
- /**
- * 测试场景 3:半曝光状态 - 保存并完成
- *
- * Given: 用户已登录系统并进入检查页面
- * 已完成部分体位的曝光
- * When: 用户点击退出,显示反馈弹窗
- * 用户点击"保存并完成"按钮
- * Then: 调用API通知后端(study_status='Completed')
- * 成功跳转到工作列表页面
- */
- it('场景3:半曝光状态点击"保存并完成"应该调用API并退出', () => {
- /**
- * Given: Mock API接口
- */
- cy.intercept('POST', '/auth/task/inspection/leave', {
- statusCode: 200,
- body: {
- code: '0x000000',
- description: 'Success',
- solution: '',
- data: {}
- }
- }).as('suspendOrCompleteStudy');
- /**
- * Given: 模拟半曝光状态并设置StudyID
- */
- cy.window().its('store').then((store) => {
- const state = store.getState();
-
- // 设置曝光状态
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- // 确保有StudyID
- if (state.bodyPositionList.selectedBodyPosition?.work?.StudyID) {
- cy.wrap(state.bodyPositionList.selectedBodyPosition.work.StudyID).as('currentStudyId');
- }
- });
- /**
- * When: 点击导航按钮尝试退出
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证显示退出反馈弹窗
- */
- cy.get('[role="dialog"]').should('be.visible');
- /**
- * When: 点击"保存并完成"按钮
- */
- cy.get('[role="dialog"]').contains('button', '保存并完成').click();
- /**
- * Then: 验证调用了API
- */
- cy.wait('@suspendOrCompleteStudy').then((interception) => {
- // 验证请求参数
- expect(interception.request.body).to.have.property('study_status', 'Completed');
- expect(interception.request.body).to.have.property('study_id');
- });
- /**
- * Then: 验证弹窗关闭
- */
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证成功跳转到工作列表页面
- */
- cy.url().should('include', '/worklist');
- /**
- * Then: 验证业务流程已切换
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'worklist');
- });
- /**
- * 测试场景 4:半曝光状态 - 直接中止
- *
- * Given: 用户已登录系统并进入检查页面
- * 已完成部分体位的曝光
- * When: 用户点击退出,显示反馈弹窗
- * 用户点击"直接中止"按钮(危险按钮)
- * Then: 调用API通知后端(study_status='InProgress')
- * 成功跳转到工作列表页面
- */
- it('场景4:半曝光状态点击"直接中止"应该调用API并退出', () => {
- /**
- * Given: Mock API接口
- */
- cy.intercept('POST', '/auth/task/inspection/leave', {
- statusCode: 200,
- body: {
- code: '0x000000',
- description: 'Success',
- solution: '',
- data: {}
- }
- }).as('suspendOrCompleteStudy');
- /**
- * Given: 模拟半曝光状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- });
- /**
- * When: 点击导航按钮尝试退出
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证显示退出反馈弹窗
- */
- cy.get('[role="dialog"]').should('be.visible');
- /**
- * Then: 验证"直接中止"按钮为危险样式
- */
- cy.get('[role="dialog"]')
- .contains('button', '直接中止')
- .should('have.class', 'ant-btn-dangerous');
- /**
- * When: 点击"直接中止"按钮
- */
- cy.get('[role="dialog"]').contains('button', '直接中止').click();
- /**
- * Then: 验证调用了API
- */
- cy.wait('@suspendOrCompleteStudy').then((interception) => {
- // 验证请求参数
- expect(interception.request.body).to.have.property('study_status', 'InProgress');
- expect(interception.request.body).to.have.property('study_id');
- });
- /**
- * Then: 验证弹窗关闭
- */
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证成功跳转到工作列表页面
- */
- cy.url().should('include', '/worklist');
- });
- /**
- * 测试场景 5:全曝光状态退出
- *
- * Given: 用户已登录系统并进入检查页面
- * 已完成所有体位的曝光
- * When: 用户点击导航按钮切换到工作列表
- * Then: 应该直接退出,不显示退出反馈弹窗
- */
- it('场景5:全曝光状态应该直接退出检查', () => {
- /**
- * Given: 模拟全曝光状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Fully Exposed'
- });
- });
- /**
- * Given: 验证曝光状态为 "Fully Exposed"
- */
- cy.window().its('store').invoke('getState')
- .its('bodyPositionList')
- .its('exposureStatus')
- .should('equal', 'Fully Exposed');
- /**
- * When: 点击导航按钮切换到工作列表
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证未显示退出反馈弹窗
- */
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证成功退出到工作列表页面
- */
- cy.url().should('include', '/worklist');
- /**
- * Then: 验证业务流程已切换
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'worklist');
- });
- /**
- * 测试场景 6:发生器采集中阻止退出到process
- *
- * Given: 用户在检查页面
- * 发生器正在采集(acquisitionState = 1)
- * When: 用户尝试跳转到图像处理页面(process)
- * Then: 跳转被阻止,停留在检查页面
- */
- it('场景6:发生器采集中应该阻止退出到图像处理', () => {
- /**
- * Given: 模拟发生器正在采集
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'generatorMonitor/setAcquisitionState',
- payload: 1
- });
- });
- /**
- * Given: 验证发生器状态
- */
- cy.window().its('store').invoke('getState')
- .its('generatorMonitor')
- .its('acquisitionState')
- .should('equal', 1);
- /**
- * When: 尝试跳转到图像处理页面
- * 注意:这里需要根据实际的导航方式调整
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'BusinessFlow/setBusinessFlow',
- payload: 'process'
- });
- });
- /**
- * Then: 验证仍停留在检查页面
- */
- cy.wait(500); // 等待可能的状态变化
- cy.get('[data-testid="exam-page"]').should('exist');
- /**
- * Then: 验证业务流程仍为exam
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'exam');
- });
- /**
- * 测试场景 7:从检查正常退出到process(非采集)
- *
- * Given: 用户在检查页面
- * 发生器空闲(acquisitionState != 1)
- * 有已曝光的图像
- * When: 用户点击进入图像处理页面
- * Then: 成功跳转到process页面
- * 执行了清理操作(unprepare)
- */
- it('场景7:发生器空闲时应该能正常退出到图像处理', () => {
- /**
- * Given: 确保发生器不在采集状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'generatorMonitor/setAcquisitionState',
- payload: 0
- });
- });
- /**
- * Given: 验证发生器状态
- */
- cy.window().its('store').invoke('getState')
- .its('generatorMonitor')
- .its('acquisitionState')
- .should('not.equal', 1);
- /**
- * Given: 监听控制台日志(验证unprepare调用)
- */
- cy.window().then((win) => {
- cy.spy(win.console, 'log').as('consoleLog');
- });
- /**
- * When: 点击进入图像处理页面
- * 注意:这里需要根据实际的导航方式调整
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'BusinessFlow/setBusinessFlow',
- payload: 'process'
- });
- });
- /**
- * Then: 验证成功跳转到process页面
- */
- cy.wait(1000);
- cy.url().should('include', '/process');
- /**
- * Then: 验证业务流程已切换
- */
- cy.window().its('store').invoke('getState')
- .its('BusinessFlow')
- .its('currentKey')
- .should('equal', 'process');
- /**
- * Then: 验证控制台输出了相应日志
- */
- cy.get('@consoleLog').should('be.calledWith', '从检查退出到处理');
- });
- /**
- * 测试场景 8:边界场景 - 快速连续点击退出
- *
- * Given: 用户在检查页面(半曝光状态)
- * When: 快速连续点击退出按钮(3次)
- * Then: 只显示一个退出反馈弹窗
- * 弹窗功能正常
- */
- it('场景8:快速连续点击退出应该只显示一个弹窗', () => {
- /**
- * Given: 模拟半曝光状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- });
- /**
- * When: 快速连续点击退出按钮3次
- */
- for (let i = 0; i < 3; i++) {
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- cy.wait(50); // 很短的等待时间,模拟快速点击
- }
- /**
- * Then: 验证只显示一个弹窗
- */
- cy.get('[role="dialog"]').should('have.length', 1);
- /**
- * Then: 验证弹窗可见
- */
- cy.get('[role="dialog"]').should('be.visible');
- /**
- * Then: 验证弹窗功能正常(点击"继续检查")
- */
- cy.get('[role="dialog"]').contains('button', '继续检查').click();
- cy.get('[role="dialog"]').should('not.exist');
- /**
- * Then: 验证仍在检查页面
- */
- cy.get('[data-testid="exam-page"]').should('exist');
- });
- /**
- * 测试场景 9:边界场景 - StudyID为空的错误处理
- *
- * Given: 用户在检查页面但StudyID为空
- * When: 用户尝试退出并选择保存
- * Then: 应该有适当的错误处理
- */
- it('场景9:StudyID为空时应该有错误处理', () => {
- /**
- * Given: 清空StudyID
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- // 模拟StudyID为空的情况
- store.dispatch({
- type: 'bodyPositionList/clearSelectedBodyPosition'
- });
- });
- /**
- * Given: 监听控制台错误日志
- */
- cy.window().then((win) => {
- cy.spy(win.console, 'error').as('consoleError');
- });
- /**
- * When: 尝试退出
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 可能显示弹窗或直接显示错误
- * 根据实际实现调整验证逻辑
- */
- cy.get('[role="dialog"]').then(($dialog) => {
- if ($dialog.length > 0) {
- // 如果显示了弹窗,尝试点击保存
- cy.get('[role="dialog"]').contains('button', '保存并完成').click();
-
- // 验证控制台输出了错误日志
- cy.get('@consoleError').should('be.called');
- }
- });
- });
- /**
- * 测试场景 10:验证退出反馈弹窗UI元素
- *
- * Given: 用户在检查页面(半曝光状态)
- * When: 触发退出反馈弹窗
- * Then: 验证弹窗包含所有必要的UI元素
- */
- it('场景10:退出反馈弹窗应该包含所有必要的UI元素', () => {
- /**
- * Given: 模拟半曝光状态
- */
- cy.window().its('store').then((store) => {
- store.dispatch({
- type: 'bodyPositionList/setExposureStatus',
- payload: 'Half Exposed'
- });
- });
- /**
- * When: 触发退出
- */
- mainPage.clickPatientManagementButton();
- mainPage.clickWorklistButton();
- /**
- * Then: 验证弹窗存在
- */
- cy.get('[role="dialog"]').should('be.visible');
- /**
- * Then: 验证弹窗标题
- */
- cy.get('[role="dialog"]').should('contain', '检查未完成');
- /**
- * Then: 验证警告图标
- */
- cy.get('[role="dialog"]').find('.anticon-exclamation-circle').should('exist');
- /**
- * Then: 验证提示消息
- */
- cy.get('[role="dialog"]').should('contain', '当前检查的曝光步骤尚未完成');
- cy.get('[role="dialog"]').should('contain', '中止后,本次检查的进度将不会保存');
- /**
- * Then: 验证包含三个按钮
- */
- cy.get('[role="dialog"]').find('button').should('have.length', 3);
- /**
- * Then: 验证"继续检查"按钮
- */
- cy.get('[role="dialog"]').contains('button', '继续检查').should('exist');
- /**
- * Then: 验证"保存并完成"按钮(主按钮)
- */
- cy.get('[role="dialog"]')
- .contains('button', '保存并完成')
- .should('have.class', 'ant-btn-primary');
- /**
- * Then: 验证"直接中止"按钮(危险按钮)
- */
- cy.get('[role="dialog"]')
- .contains('button', '直接中止')
- .should('have.class', 'ant-btn-primary')
- .should('have.class', 'ant-btn-dangerous');
- /**
- * 清理:关闭弹窗
- */
- cy.get('[role="dialog"]').contains('button', '继续检查').click();
- });
- });
|