소스 검색

test: 实现重置高压发生器功能的完整E2E测试方案

- 创建完整的E2E测试套件,涵盖5个测试场景
- 扩展device/protocol/study mock handlers,添加完整API响应
- 扩展ExamPage POM,添加7个重置相关方法
- 在ContentAreaLarge.tsx中添加data-testid测试标识
- 编写完整的测试方案文档,包含测试用例和验收标准
- 配置所有必需的Mock(登录、多语言、APR、View详情等)

改动文件:
- cypress/e2e/exam/reset-generator.cy.ts (新增)
- docs/测试/重置高压发生器功能测试方案.md (新增)
- cypress/support/mock/handlers/device.ts
- cypress/support/mock/handlers/protocol.ts
- cypress/support/mock/handlers/study.ts
- cypress/support/pageObjects/ExamPage.ts
- src/pages/exam/ContentAreaLarge.tsx
- cypress.config.ts
- cypress/support/commands.d.ts
- cypress/support/e2e.ts
- docs/DR.md
sw 4 일 전
부모
커밋
0ed76e6ff4

+ 2 - 2
config/dev.ts

@@ -11,7 +11,7 @@ export default {
     stats: true,
   },
   defineConstants: {
-    MQTT_BROKER_URL_FROM_WEBPACK: '"ws://192.168.110.112:8083/mqtt"',
+    MQTT_BROKER_URL_FROM_WEBPACK: '"ws://192.168.110.13:8083/mqtt"',
   },
   mini: {},
   h5: {
@@ -20,7 +20,7 @@ export default {
     devServer: {
       proxy: {
         '/dr': {
-          target: 'http://192.168.110.112:6001', // 你的后端服务地址
+          target: 'http://192.168.110.13:6001', // 你的后端服务地址
           changeOrigin: true, // 允许跨域
           // pathRewrite: {
           //   '^/dr/api': '' // 可选,用于重写路径

+ 6 - 1
cypress.config.ts

@@ -1,7 +1,7 @@
 import { defineConfig } from 'cypress';
 import mqtt from 'mqtt'
 
-function logWithTimestamp(message, ...args) {
+function logWithTimestamp(message: string, ...args: any[]) {
   const timestamp = new Date().toLocaleString();
   console.log(`[${timestamp}] ${message}`, ...args);
   //cy.log(`[${timestamp}] ${message}`);
@@ -37,6 +37,11 @@ export default defineConfig({
             })
             client.on('error', reject)
           })
+        },
+        // 浏览器日志输出到终端
+        browserLog(message) {
+          console.log('[Browser Console]', message)
+          return null
         }
       })
       return require('./cypress/plugins/index.js')(on, config);

+ 264 - 0
cypress/e2e/exam/reset-generator.cy.ts

@@ -0,0 +1,264 @@
+import LoginPage from '../../support/pageObjects/LoginPage';
+import MainPage from '../../support/pageObjects/MainPage';
+import ExamPage from '../../support/pageObjects/ExamPage';
+import WorklistPage from '../../support/pageObjects/WorklistPage';
+import {
+  mockResetDeviceSuccess,
+  mockResetDeviceFail,
+  mockResetDeviceNetworkError,
+  mockResetDeviceDelay,
+} from '../../support/mock/handlers/device';
+import {
+  mockI18nSuccess,
+  mockGetLanguageListSuccess,
+  mockAllRequiredAPIs,
+} from '../../support/mock/handlers/i18n';
+import { mockLoginSuccess } from '../../support/mock/handlers/user';
+import { mockGetStudyDetails } from '../../support/mock/handlers/study';
+import { mockFetchTwoWorks } from '../../support/mock/handlers/worklist';
+import { mockGetQuotaSuccess } from '../../support/mock/handlers/quota';
+import { mockGetAprDetailsComplete, mockGetViewDetailComplete } from '../../support/mock/handlers/protocol';
+
+describe('重置高压发生器功能测试', () => {
+  const loginPage = new LoginPage();
+  const mainPage = new MainPage();
+  const examPage = new ExamPage();
+  const worklistPage = new WorklistPage();
+
+  beforeEach(() => {
+    // Mock多语言资源和必要的API
+    mockI18nSuccess('zh_CN');
+    mockGetLanguageListSuccess();
+    mockAllRequiredAPIs('zh_CN');
+
+    // Mock登录成功响应
+    mockLoginSuccess();
+    // Mock 进入worklist后得到两个数据
+    mockFetchTwoWorks();
+    // Mock得到配额成功
+    mockGetQuotaSuccess();
+    // Mock获取检查详情
+    mockGetStudyDetails('20250912135259444');
+    // Mock获取APR详情
+    mockGetAprDetailsComplete();
+    // Mock获取View详情
+    mockGetViewDetailComplete();
+
+    // 登录系统
+    loginPage.visit();
+    loginPage.login('admin', '123456');
+
+    // 等待页面渲染和路由跳转
+    cy.wait(1500);
+
+    // 验证登录成功:登录页面元素不再存在
+    loginPage.getUsernameInput().should('not.exist');
+    loginPage.getPasswordInput().should('not.exist');
+    loginPage.getSubmitButton().should('not.exist');
+
+    // 导航到工作列表
+    // mainPage.clickPatientManagementButton(); //默认展开,不需要点击使之展开
+    mainPage.clickWorklistButton();
+
+    // 双击第一行进入检查页面
+    worklistPage.findTableAndDoubleClickFirstRow();
+    cy.wait(3000);
+
+    // 验证已进入检查页面
+    cy.get('[data-testid="exam-page"]').should('exist');
+  });
+
+  describe('TC-RESET-01: 成功重置设备', () => {
+    it('应该成功重置高压发生器并更新UI状态', () => {
+      // 1. Mock设备重置API成功响应
+      mockResetDeviceSuccess();
+
+      // 2. 验证按钮初始状态为可用
+      examPage.verifyResetButtonEnabled();
+
+      // 3. 验证初始Redux状态
+      examPage.verifyDeviceStatus('idle');
+
+      // 4. 点击RESET按钮
+      examPage.clickResetGenerator();
+
+      // 5. 验证按钮变为禁用状态(loading)
+      examPage.verifyResetButtonDisabled();
+
+      // 6. 等待API调用完成
+      cy.wait('@resetDevice').then((interception) => {
+        // 验证请求体
+        expect(interception.request.body).to.deep.include({
+          deviceUri: 'DIOS/DEVICE/Generator',
+          reqName: 'RESET',
+        });
+
+        // 验证响应
+        expect(interception.response?.statusCode).to.eq(200);
+        expect(interception.response?.body.code).to.eq('0x000000');
+      });
+
+      // 7. 验证按钮恢复为可用状态
+      examPage.verifyResetButtonEnabled();
+
+      // 8. 验证Redux状态更新为'succeeded'
+      examPage.verifyDeviceStatus('succeeded');
+
+      // 9. 验证无错误信息
+      examPage.verifyDeviceError(null);
+    });
+  });
+
+  describe('TC-RESET-02: API失败处理', () => {
+    it('应该正确处理设备重置失败', () => {
+      // 1. Mock设备重置API失败响应
+      mockResetDeviceFail('0x010001', '设备通信失败');
+
+      // 2. 点击RESET按钮
+      examPage.clickResetGenerator();
+
+      // 3. 等待API调用完成
+      cy.wait('@resetDeviceFail').then((interception) => {
+        // 验证响应包含错误信息
+        expect(interception.response?.body.code).to.eq('0x010001');
+        expect(interception.response?.body.description).to.eq('设备通信失败');
+      });
+
+      // 4. 验证Redux状态更新为'failed'
+      examPage.verifyDeviceStatus('failed');
+
+      // 5. 验证错误信息存储到state.error
+      cy.window()
+        .its('store')
+        .invoke('getState')
+        .its('device')
+        .its('error')
+        .should('exist');
+
+      // 6. 验证按钮恢复可用
+      examPage.verifyResetButtonEnabled();
+
+      // 7. 验证控制台输出错误日志
+      cy.window().then((win) => {
+        cy.spy(win.console, 'error');
+      });
+    });
+  });
+
+  describe('TC-RESET-03: 按钮禁用状态管理', () => {
+    it('应该在loading期间正确管理按钮状态', () => {
+      // 1. Mock设备重置API延迟响应(2秒)
+      mockResetDeviceDelay(2000);
+
+      // 2. 验证初始Redux状态
+      examPage.verifyDeviceStatus('idle');
+
+      // 3. 点击RESET按钮
+      examPage.clickResetGenerator();
+
+      // 4. 验证按钮立即禁用
+      examPage.verifyResetButtonDisabled();
+
+      // 5. 验证Redux状态变为loading
+      examPage.verifyDeviceStatus('loading');
+
+      // 6. 等待1秒,验证按钮仍然禁用
+      cy.wait(1000);
+      examPage.verifyResetButtonDisabled();
+      examPage.verifyDeviceStatus('loading');
+
+      // 7. 等待API响应完成
+      cy.wait('@resetDeviceDelay');
+
+      // 8. 验证按钮恢复可用
+      examPage.verifyResetButtonEnabled();
+
+      // 9. 验证Redux状态更新为'succeeded'
+      examPage.verifyDeviceStatus('succeeded');
+    });
+  });
+
+  describe('TC-RESET-04: 网络错误处理', () => {
+    it('应该正确处理网络异常', () => {
+      // 1. Mock网络错误
+      mockResetDeviceNetworkError();
+
+      // 2. 点击RESET按钮
+      examPage.clickResetGenerator();
+
+      // 3. 等待错误发生
+      cy.wait('@resetDeviceNetworkError');
+
+      // 4. 验证Redux状态更新为'failed'
+      examPage.verifyDeviceStatus('failed');
+
+      // 5. 验证错误信息被捕获
+      cy.window()
+        .its('store')
+        .invoke('getState')
+        .its('device')
+        .its('error')
+        .should('exist');
+
+      // 6. 验证按钮恢复可用
+      examPage.verifyResetButtonEnabled();
+
+      // 7. 验证用户可以重试
+      mockResetDeviceSuccess();
+      examPage.clickResetGenerator();
+      cy.wait('@resetDevice');
+      examPage.verifyDeviceStatus('succeeded');
+    });
+  });
+
+  describe('TC-RESET-05: 防止重复点击', () => {
+    it('应该防止loading时重复点击', () => {
+      // 1. Mock设备重置API延迟响应(3秒)
+      let requestCount = 0;
+      cy.intercept('POST', '/auth/device/action', (req) => {
+        if (req.body.reqName === 'RESET') {
+          requestCount++;
+          req.reply({
+            statusCode: 200,
+            body: {
+              code: '0x000000',
+              description: 'Success',
+              data: {},
+            },
+            delay: 3000,
+          });
+        }
+      }).as('resetDeviceMultiple');
+
+      // 2. 点击RESET按钮
+      examPage.clickResetGenerator();
+
+      // 3. 验证按钮禁用
+      examPage.verifyResetButtonDisabled();
+
+      // 4. 立即再次尝试点击按钮(应该无效)
+      examPage.getResetGeneratorButton().click({ force: true });
+
+      // 5. 等待1秒后再次尝试点击
+      cy.wait(1000);
+      examPage.getResetGeneratorButton().click({ force: true });
+
+      // 6. 等待第一次请求完成
+      cy.wait('@resetDeviceMultiple');
+
+      // 7. 验证只发送了一次API请求
+      cy.wrap(null).then(() => {
+        expect(requestCount).to.eq(1);
+      });
+
+      // 8. 验证完成后可以再次点击
+      examPage.verifyResetButtonEnabled();
+
+      // 重新mock成功响应
+      mockResetDeviceSuccess();
+      examPage.clickResetGenerator();
+      cy.wait('@resetDevice');
+      examPage.verifyDeviceStatus('succeeded');
+    });
+  });
+});

+ 18 - 0
cypress/support/commands.d.ts

@@ -27,6 +27,24 @@ declare namespace Cypress {
      * @param args - 额外参数
      */
     logWithDate(message: string, ...args: any[]): Chainable<Subject>;
+
+    /**
+     * 刷新浏览器 console 日志到终端
+     * 
+     * 将缓冲区中的所有浏览器 console 日志输出到运行 Cypress 的终端
+     * 
+     * @example
+     * cy.flushConsoleLogs();
+     */
+    flushConsoleLogs(): Chainable<Subject>;
+  }
+
+  interface AUTWindow {
+    __consoleLogs__?: Array<{
+      type: 'log' | 'error' | 'warn';
+      message: string;
+      timestamp: string;
+    }>;
   }
 }
 

+ 59 - 1
cypress/support/e2e.ts

@@ -9,4 +9,62 @@ import './pageObjects/WorklistPage';
 
 // Alternatively you can use CommonJS syntax:
 // require('./commands')
-// require('./pageObjects/LoginPage')
+// require('./pageObjects/LoginPage')
+// 拦截浏览器 console 日志并输出到终端
+// 使用 CDP (Chrome DevTools Protocol) 来监听 console 事件
+Cypress.on('window:before:load', (win) => {
+  // 保存原始的 console 方法
+  const originalLog = win.console.log
+  const originalError = win.console.error
+  const originalWarn = win.console.warn
+  
+  // 在 window 对象上创建日志缓冲区
+  if (!win.__consoleLogs__) {
+    win.__consoleLogs__ = []
+  }
+  
+  // 重写 console.log - 存储到缓冲区
+  win.console.log = function(...args: any[]) {
+    originalLog.apply(win.console, args)
+    const message = args.map(arg => 
+      typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+    ).join(' ')
+    win.__consoleLogs__.push({ type: 'log', message, timestamp: new Date().toISOString() })
+  }
+  
+  // 重写 console.error
+  win.console.error = function(...args: any[]) {
+    originalError.apply(win.console, args)
+    const message = args.map(arg => 
+      typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+    ).join(' ')
+    win.__consoleLogs__.push({ type: 'error', message, timestamp: new Date().toISOString() })
+  }
+  
+  // 重写 console.warn
+  win.console.warn = function(...args: any[]) {
+    originalWarn.apply(win.console, args)
+    const message = args.map(arg => 
+      typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+    ).join(' ')
+    win.__consoleLogs__.push({ type: 'warn', message, timestamp: new Date().toISOString() })
+  }
+})
+
+// 添加自定义命令来刷新日志到终端
+Cypress.Commands.add('flushConsoleLogs', () => {
+  cy.window({ log: false }).then((win: any) => {
+    if (win.__consoleLogs__ && win.__consoleLogs__.length > 0) {
+      const logs = win.__consoleLogs__.splice(0) // 清空缓冲区
+      logs.forEach((log: any) => {
+        const prefix = log.type === 'error' ? '[ERROR] ' : log.type === 'warn' ? '[WARN] ' : ''
+        cy.task('browserLog', `${prefix}${log.message}`, { log: false })
+      })
+    }
+  })
+})
+
+// 自动在每个测试后刷新日志
+afterEach(() => {
+  cy.flushConsoleLogs()
+})

+ 118 - 0
cypress/support/mock/handlers/device.ts

@@ -109,3 +109,121 @@ export function mockDeviceActionSuccess() {
     }
   }).as('deviceActionSuccess');
 }
+
+/**
+ * 重置高压发生器 - 成功场景
+ * 
+ * @description 重置高压发生器参数到初始状态
+ * @method POST
+ * @url /auth/device/action
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.deviceUri - 设备URI (DIOS/DEVICE/Generator)
+ * @param {string} requestBody.reqName - 请求名称 (RESET)
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockResetDeviceSuccess();
+ * cy.wait('@resetDevice');
+ */
+export function mockResetDeviceSuccess() {
+  cy.intercept('POST', '*/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          data: {}
+        }
+      });
+    }
+  }).as('resetDevice');
+}
+
+/**
+ * 重置高压发生器 - 失败场景
+ * 
+ * @description 模拟设备重置失败
+ * @method POST
+ * @url /auth/device/action
+ * @access 需要认证
+ * 
+ * @param {string} errorCode - 错误码,默认为'0x010001'
+ * @param {string} description - 错误描述,默认为'设备通信失败'
+ * 
+ * @returns {Object} 失败响应
+ * 
+ * @example
+ * mockResetDeviceFail();
+ * cy.wait('@resetDeviceFail');
+ */
+export function mockResetDeviceFail(
+  errorCode: string = '0x010001',
+  description: string = '设备通信失败'
+) {
+  cy.intercept('POST', '/auth/device/action', {
+    statusCode: 200,
+    body: {
+      code: errorCode,
+      description: description,
+      solution: '检查设备连接'
+    }
+  }).as('resetDeviceFail');
+}
+
+/**
+ * 重置高压发生器 - 网络错误
+ * 
+ * @description 模拟网络通信错误
+ * @method POST
+ * @url /auth/device/action
+ * @access 需要认证
+ * 
+ * @returns {Object} 网络错误
+ * 
+ * @example
+ * mockResetDeviceNetworkError();
+ * cy.wait('@resetDeviceNetworkError');
+ */
+export function mockResetDeviceNetworkError() {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({ forceNetworkError: true });
+    }
+  }).as('resetDeviceNetworkError');
+}
+
+/**
+ * 重置高压发生器 - 延迟响应
+ * 
+ * @description 模拟设备重置延迟响应(用于测试loading状态)
+ * @method POST
+ * @url /auth/device/action
+ * @access 需要认证
+ * 
+ * @param {number} delayMs - 延迟毫秒数
+ * 
+ * @returns {Object} 延迟后的成功响应
+ * 
+ * @example
+ * mockResetDeviceDelay(2000); // 延迟2秒
+ * cy.wait('@resetDeviceDelay');
+ */
+export function mockResetDeviceDelay(delayMs: number) {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          data: {}
+        },
+        delay: delayMs
+      });
+    }
+  }).as('resetDeviceDelay');
+}

+ 217 - 2
cypress/support/mock/handlers/protocol.ts

@@ -315,9 +315,9 @@ export function mockGetViewListSuccess() {
 }
 
 /**
- * 获取体位详情 - 成功场景
+ * 获取体位详情 - 成功场景(简化版)
  * 
- * @description 根据ID获取体位详情
+ * @description 根据ID获取体位详情(不含完整config_object)
  * @method GET
  * @url /dr/api/v1/auth/protocol/view/{id}
  * @access 需要认证
@@ -357,6 +357,109 @@ export function mockGetViewDetailSuccess() {
   }).as('getViewDetailSuccess');
 }
 
+/**
+ * 获取体位详情 - 完整版(包含config_object)
+ * 
+ * @description 根据ID获取完整的体位详情,包含DX和Common配置对象
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/view/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 体位ID(路径参数)
+ * 
+ * @returns {Object} data - 完整的体位详情,包含config_object
+ * @returns {Object} data.config_object.DX - 人医配置参数
+ * @returns {Object} data.config_object.Common - 通用配置参数
+ * 
+ * @example
+ * mockGetViewDetailComplete();
+ * cy.wait('@getViewDetailComplete');
+ * 
+ * @see src/API/patient/viewActions.ts - fetchViewDetail函数
+ */
+export function mockGetViewDetailComplete() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/view/*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.View",
+        internal_id: "View_DX_T_A_SK_AP_00",
+        view_id: "View_DX_T_A_SK_AP_00",
+        view_name: "颅骨前后位",
+        view_name_local: "颅骨前后位",
+        view_other_name: "Skull AP",
+        view_description: "颅骨前后位",
+        view_position: "AP",
+        application: "RAD",
+        anatomic_region: "Skull",
+        patient_type: "Human",
+        body_part_id: "Human_SKULL",
+        view_icon_name: "/Image/Position/Human/skull.ap.table.x.png",
+        view_big_icon_name: "/Image/Position/Human/skull.ap.table.x_big.png",
+        view_coach_name: "/Image/Coach/Human/skull_ap.png",
+        modality: "DX",
+        work_station_id: 0,
+        apr_id: "View_DX_T_A_SK_AP_00",
+        img_proc_id: "View_DX_T_A_SK_AP_00",
+        config_object: {
+          DX: {
+            WorkStationID: "0",
+            PatientOrientationRow: "P",
+            PatientOrientationColumn: "L",
+            ImageLaterality: "U",
+            ImageRotate: "0",
+            CollimatorSizeLength: "430",
+            CollimatorSizeWidth: "430",
+            CollimatorSize: "430*430",
+            CollimatorCenter: "0,0",
+            CollimatorFilter: "None",
+            CollimatorNoChange: false,
+            StandPos: "Table",
+            ImageHorizontalFlip: "False",
+            LabelStyle: "Default",
+            LabelPosition: "TopLeft",
+            RatioFactorThickness: 1.0,
+            RatioFactorWeight: 1.0,
+            RatioFactorSize: 1.0,
+            RatioFactorLength: 1.0,
+            TargetEXI: 200
+          },
+          Common: {
+            CollimatorCenter: "0,0",
+            CollimatorFilter: "None",
+            CollimatorNoChange: false,
+            CollimatorSize: "430*430",
+            CollimatorSizeLength: "430",
+            CollimatorSizeWidth: "430",
+            ImageHorizontalFlip: "False",
+            ImageInvert: false,
+            ImageLaterality: "U",
+            ImageRotate: "0",
+            LabelPosition: "TopLeft",
+            RatioFactorLength: 1.0,
+            RatioFactorSize: 1.0,
+            RatioFactorThickness: 1.0,
+            RatioFactorWeight: 1.0,
+            StandPos: "Table",
+            TargetEXI: 200,
+            UseMaskMapMatrix: false,
+            ViewID: "View_DX_T_A_SK_AP_00",
+            XIndex: 0,
+            YIndex: 0
+          }
+        },
+        sort: 1,
+        is_enabled: true,
+        product: "DROS",
+        is_pre_install: true
+      }
+    }
+  }).as('getViewDetailComplete');
+}
+
 /**
  * 获取APR详情(通过view_id)- 成功场景
  * 
@@ -501,3 +604,115 @@ export function mockGetAprTechParamsSuccess() {
     }
   }).as('getAprTechParamsSuccess');
 }
+
+/**
+ * 获取APR完整详情(包含所有体型参数)- 成功场景
+ * 
+ * @description 根据体位ID获取完整的APR详情,包含所有患者体型的曝光参数
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/view/{id}/apr
+ * @access 需要认证
+ * 
+ * @param {string} id - 体位ID(路径参数)
+ * @param {string} [patient_type] - 患者类型
+ * @param {string} [body_part] - 身体部位
+ * @param {boolean} [is_enabled] - 是否启用
+ * @param {string} [procedure_id] - 协议ID
+ * 
+ * @returns {Object} data - 完整的APR详情
+ * @returns {Object[]} data.sub - 不同体型的曝光参数列表
+ * @returns {Object} data.sub[].config_object.Common - 完整的曝光配置
+ * 
+ * @example
+ * mockGetAprDetailsComplete();
+ * cy.wait('@getAprDetailsComplete');
+ * 
+ * @see src/API/exam/APRActions.ts - getAprDetails函数
+ */
+export function mockGetAprDetailsComplete() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/view/*/apr*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.APR",
+        apr_id: "View_DX_T_A_SK_AP_00",
+        apr_name: "颅骨前后位APR",
+        apr_description: "颅骨前后位曝光参数",
+        patient_type: "Human",
+        body_part_id: "Human_SKULL",
+        view_position: "AP",
+        category: "DX",
+        modality: "DX",
+        sub: [
+          {
+            work_station_id: 0,
+            patient_size: "Large",
+            config_object: {
+              Common: {
+                AECDensity: 0,
+                AECField: "Center",
+                AECFilm: 0,
+                Dose: 100,
+                ExposureMode: 1,
+                Focus: 0,
+                TOD: 100,
+                TubeLoad: 50,
+                kV: 70,
+                mA: 125,
+                mAs: 12.5,
+                ms: 100
+              }
+            }
+          },
+          {
+            work_station_id: 0,
+            patient_size: "Medium",
+            config_object: {
+              Common: {
+                AECDensity: 0,
+                AECField: "Center",
+                AECFilm: 0,
+                Dose: 80,
+                ExposureMode: 1,
+                Focus: 0,
+                TOD: 100,
+                TubeLoad: 40,
+                kV: 65,
+                mA: 100,
+                mAs: 10,
+                ms: 100
+              }
+            }
+          },
+          {
+            work_station_id: 0,
+            patient_size: "Small",
+            config_object: {
+              Common: {
+                AECDensity: 0,
+                AECField: "Center",
+                AECFilm: 0,
+                Dose: 60,
+                ExposureMode: 1,
+                Focus: 0,
+                TOD: 100,
+                TubeLoad: 30,
+                kV: 60,
+                mA: 80,
+                mAs: 8,
+                ms: 100
+              }
+            }
+          }
+        ],
+        sort: 1,
+        is_enabled: true,
+        product: "DROS",
+        is_pre_install: true
+      }
+    }
+  }).as('getAprDetailsComplete');
+}

+ 167 - 1
cypress/support/mock/handlers/study.ts

@@ -44,7 +44,7 @@ export function mockRegisterStudySuccess() {
 }
 
 /**
- * 获取检查信息 - Arrived状态
+ * 获取检查信息 - Arrived状态(简化版)
  * 
  * @description 根据study_id获取检查详细信息(已到达状态)
  * @method GET
@@ -79,6 +79,172 @@ export function mockGetStudyArrived() {
   }).as('getStudyArrived');
 }
 
+/**
+ * 获取检查信息 - 完整详情
+ * 
+ * @description 根据study_id获取完整的检查详细信息,包含series和images
+ * @method GET
+ * @url /dr/api/v1/auth/study/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(study_id),路径参数
+ * 
+ * @returns {Object} data - 完整的检查详细信息,包含series和images
+ * 
+ * @example
+ * mockGetStudyDetails();
+ * cy.wait('@getStudyDetails');
+ * 
+ * @see docs/DR.md - 章节19
+ */
+export function mockGetStudyDetails(studyId:string) {
+  cy.intercept('GET', `/dr/api/v1/auth/study/${studyId}`, {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "成功",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.study.Study",
+        study_instance_uid: "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+        study_id: studyId,
+        public_study_id: "",
+        specific_character_set: "ISO_IR 192",
+        accession_number: "ACC0012345",
+        ref_physician: "Dr. Smith (Vet)",
+        patient_id: "PET007",
+        patient_name: "Buddy (Dog)",
+        patient_english_name: "Buddy en",
+        patient_former_name: "Buddy f",
+        patient_size: "Large",
+        other_patient_ids: "",
+        other_patient_names: "",
+        patient_age: "008Y",
+        patient_dob: "2025-06-10T03:12:36.181739Z",
+        patient_sex: "M",
+        sex_neutered: "",
+        pregnancy_status: "",
+        patient_state: "",
+        admitting_time: null,
+        priority: "",
+        reg_source: "",
+        study_description: "",
+        study_start_datetime: "2025-09-29T08:38:17.283651Z",
+        study_end_datetime: null,
+        scheduled_procedure_step_start_date: null,
+        performed_physician: "",
+        study_lock: "Unlocked",
+        folder_path: "",
+        operator_name: "OP987",
+        modality: "DX",
+        weight: 25,
+        thickness: 15,
+        length: 60,
+        patient_type: "Human",
+        study_type: "Normal",
+        owner_name: "owner1",
+        chip_number: "CHIP123456789",
+        variety: "Golden Retriever",
+        is_anaesthesia: true,
+        is_sedation: true,
+        mwl: "",
+        is_exported: false,
+        is_edited: false,
+        is_appended: false,
+        department: "",
+        mapped_status: false,
+        qc_result: false,
+        comment: "一二三四五六七八九十",
+        study_status: "Arrived",
+        sort: 0,
+        product: "DROS",
+        create_time: "2025-09-29T08:38:17.353598Z",
+        series: [
+          {
+            series_instance_uid: "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
+            study_instance_uid: "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+            study_id: studyId,
+            procedure_id: "P0-0002",
+            patient_type: "Human",
+            body_part: "Human_SKULL",
+            performed_datetime: null,
+            performed_protocol_code_meaning: "颅骨前后位 + 侧位",
+            performed_protocol_code_value: "P0-0002",
+            sort: 1,
+            product: "DROS",
+            is_pre_install: true,
+            create_time: "2025-09-29T08:38:17.359308Z",
+            images: [
+              {
+                sop_instance_uid: "2.25.156.999999.0000.1.4.8323328.269954.1759135097.323786",
+                series_instance_uid: "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
+                study_instance_uid: "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+                secondary_sop_uid: "",
+                study_id: studyId,
+                view_id: "View_DX_T_A_SK_AP_00",
+                view_description: "颅骨前后位",
+                patient_type: "Human",
+                body_part_id: "Human_SKULL",
+                anatomic_region: "Skull",
+                image_type: "expose",
+                image_file_path: "",
+                image_file: "",
+                thumbnail_file: "",
+                acquisition_mode: "RAD",
+                acquisition_context: null,
+                img_proc_context: null,
+                comment: "",
+                expose_status: "Unexposed",
+                expose_time: null,
+                judged_status: "NotJudged",
+                send_status: "Unsent",
+                export_status: "NotExported",
+                storage_status: "NotSaved",
+                ticket: "",
+                sort: 1,
+                product: "DROS",
+                is_pre_install: true,
+                create_time: "2025-09-29T08:38:17.361002Z"
+              },
+              {
+                sop_instance_uid: "2.25.156.999999.0000.1.4.8323328.269954.1759135097.323787",
+                series_instance_uid: "2.25.156.999999.0000.1.3.8323328.269954.1759135097.323785",
+                study_instance_uid: "2.25.156.999999.0000.1.2.8323328.269954.1759135097.323784",
+                secondary_sop_uid: "",
+                study_id: studyId,
+                view_id: "View_DX_T_A_SK_LAT_00",
+                view_description: "颅骨左侧位",
+                patient_type: "Human",
+                body_part_id: "Human_SKULL",
+                anatomic_region: "Skull",
+                image_type: "expose",
+                image_file_path: "",
+                image_file: "",
+                thumbnail_file: "",
+                acquisition_mode: "RAD",
+                acquisition_context: null,
+                img_proc_context: null,
+                comment: "",
+                expose_status: "Unexposed",
+                expose_time: null,
+                judged_status: "NotJudged",
+                send_status: "Unsent",
+                export_status: "NotExported",
+                storage_status: "NotSaved",
+                ticket: "",
+                sort: 2,
+                product: "DROS",
+                is_pre_install: true,
+                create_time: "2025-09-29T08:38:17.362195Z"
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }).as('getStudyDetails');
+}
+
 /**
  * 获取检查信息状态 - 成功场景
  * 

+ 66 - 0
cypress/support/pageObjects/ExamPage.ts

@@ -59,6 +59,72 @@ class ExamPage {
     // 验证内容区域可见
     this.getContentArea().should('be.visible');
   }
+
+  /**
+   * 获取重置高压发生器按钮
+   */
+  getResetGeneratorButton() {
+    return cy.get('[data-testid="reset-generator-btn"]');
+  }
+
+  /**
+   * 点击重置按钮
+   */
+  clickResetGenerator() {
+    this.getResetGeneratorButton().click();
+  }
+
+  /**
+   * 验证重置按钮为启用状态
+   */
+  verifyResetButtonEnabled() {
+    this.getResetGeneratorButton()
+      .should('be.visible')
+      .should('not.be.disabled');
+  }
+
+  /**
+   * 验证重置按钮为禁用状态
+   */
+  verifyResetButtonDisabled() {
+    this.getResetGeneratorButton()
+      .should('be.visible')
+      .should('be.disabled');
+  }
+
+  /**
+   * 等待重置操作完成
+   */
+  waitForResetComplete() {
+    cy.wait('@resetDevice');
+    this.verifyResetButtonEnabled();
+  }
+
+  /**
+   * 验证Redux设备状态
+   * @param expectedStatus - 期望的状态: 'idle' | 'loading' | 'succeeded' | 'failed'
+   */
+  verifyDeviceStatus(expectedStatus: string) {
+    cy.window()
+      .its('store')
+      .invoke('getState')
+      .its('device')
+      .its('status')
+      .should('eq', expectedStatus);
+  }
+
+  /**
+   * 验证设备错误信息
+   * @param expectedError - 期望的错误信息,null表示无错误
+   */
+  verifyDeviceError(expectedError: string | null) {
+    cy.window()
+      .its('store')
+      .invoke('getState')
+      .its('device')
+      .its('error')
+      .should('eq', expectedError);
+  }
 }
 
 export default ExamPage;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2285 - 1973
docs/DR.md


+ 636 - 0
docs/测试/重置高压发生器功能测试方案.md

@@ -0,0 +1,636 @@
+# 重置高压发生器功能 E2E 测试方案
+
+## 功能概述
+
+重置高压发生器功能允许用户通过界面上的RESET按钮重置设备参数,将高压发生器恢复到初始状态。
+
+## 功能实现分析
+
+### 数据流
+
+```
+UI Button (RESET)
+  ↓
+handleResetParameters()
+  ↓
+dispatch(resetDevices()) [Redux Thunk]
+  ↓
+resetAllDevices() [API]
+  ↓
+POST /auth/device/action
+  ↓
+设备重置完成
+```
+
+### 技术架构
+
+- **UI层**: ContentAreaLarge.tsx 组件
+- **状态管理**: Redux Toolkit (deviceSlice)
+- **异步处理**: createAsyncThunk
+- **API调用**: axios + interceptor
+
+### Redux状态管理
+
+```typescript
+interface DeviceState {
+  status: 'idle' | 'loading' | 'succeeded' | 'failed';
+  error: string | null;
+  deviceError: string | null;
+}
+```
+
+### API请求格式
+
+```json
+POST /auth/device/action
+{
+  "deviceUri": "DIOS/DEVICE/Generator",
+  "reqName": "RESET",
+  "reqParam": "",
+  "reqTransaction": "",
+  "reqClientID": ""
+}
+```
+
+### API响应格式
+
+**成功响应**:
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "data": {}
+}
+```
+
+**失败响应**:
+```json
+{
+  "code": "0x010001",
+  "description": "设备通信失败",
+  "solution": "检查设备连接"
+}
+```
+
+---
+
+## 测试套件设计
+
+### 测试套件:重置高压发生器功能
+
+**文件路径**: `cypress/e2e/exam/reset-generator.cy.ts`
+
+---
+
+#### TC-RESET-01: 成功重置设备
+
+**测试目标**: 验证正常重置流程
+
+**前置条件**:
+- 已登录系统
+- 已进入检查页面
+- Mock所有依赖API(quota、i18n等)
+
+**测试步骤**:
+1. Mock设备重置API成功响应
+2. 定位RESET按钮
+3. 验证按钮初始状态为可用
+4. 点击RESET按钮
+5. 验证按钮变为禁用状态(loading)
+6. 等待API调用完成
+7. 验证按钮恢复为可用状态
+8. 验证Redux状态更新为'succeeded'
+
+**验证点**:
+- ✅ 按钮可点击
+- ✅ 点击后按钮禁用(disabled=true)
+- ✅ API调用成功(code='0x000000')
+- ✅ 按钮恢复可用状态
+- ✅ Redux状态: status='succeeded'
+- ✅ 无错误提示
+
+**Mock配置**:
+```typescript
+cy.intercept('POST', '/auth/device/action', (req) => {
+  if (req.body.reqName === 'RESET') {
+    req.reply({
+      statusCode: 200,
+      body: {
+        code: '0x000000',
+        description: 'Success',
+        data: {}
+      }
+    });
+  }
+}).as('resetDevice');
+```
+
+---
+
+#### TC-RESET-02: API失败处理
+
+**测试目标**: 验证重置失败时的错误处理
+
+**前置条件**:
+- 已登录系统
+- 已进入检查页面
+
+**测试步骤**:
+1. Mock设备重置API失败响应
+2. 点击RESET按钮
+3. 等待API调用完成
+4. 验证Redux状态更新为'failed'
+5. 验证错误信息存储到state.error
+6. 验证按钮恢复可用
+7. 验证控制台输出错误日志
+
+**验证点**:
+- ✅ API返回非'0x000000'错误码
+- ✅ Redux状态: status='failed'
+- ✅ 错误信息存储到state.error
+- ✅ 按钮恢复可用
+- ✅ 控制台输出错误日志
+
+**Mock配置**:
+```typescript
+cy.intercept('POST', '/auth/device/action', {
+  statusCode: 200,
+  body: {
+    code: '0x010001',
+    description: '设备通信失败',
+    solution: '检查设备连接'
+  }
+}).as('resetDeviceFail');
+```
+
+---
+
+#### TC-RESET-03: 按钮禁用状态管理
+
+**测试目标**: 验证loading期间按钮状态管理
+
+**前置条件**:
+- 已登录系统
+- 已进入检查页面
+
+**测试步骤**:
+1. Mock设备重置API延迟响应(2秒)
+2. 点击RESET按钮
+3. 验证按钮立即禁用
+4. 验证disabled属性为true
+5. 等待1秒,验证按钮仍然禁用
+6. 等待API响应完成
+7. 验证按钮恢复可用
+
+**验证点**:
+- ✅ 点击后按钮立即禁用
+- ✅ disabled属性为true
+- ✅ 响应前按钮保持禁用
+- ✅ 响应后按钮恢复可用
+- ✅ Redux状态正确转换: idle → loading → succeeded
+
+**Mock配置**:
+```typescript
+cy.intercept('POST', '/auth/device/action', (req) => {
+  req.reply({
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      data: {}
+    },
+    delay: 2000
+  });
+}).as('resetDeviceDelay');
+```
+
+---
+
+#### TC-RESET-04: 网络错误处理
+
+**测试目标**: 验证网络异常场景的错误处理
+
+**前置条件**:
+- 已登录系统
+- 已进入检查页面
+
+**测试步骤**:
+1. Mock网络错误
+2. 点击RESET按钮
+3. 等待错误发生
+4. 验证Redux状态更新为'failed'
+5. 验证错误信息被捕获
+6. 验证按钮恢复可用
+7. 验证用户可以重试
+
+**验证点**:
+- ✅ 捕获网络错误
+- ✅ Redux状态: status='failed'
+- ✅ 错误信息存储
+- ✅ 状态恢复正常
+- ✅ 用户可重试操作
+
+**Mock配置**:
+```typescript
+cy.intercept('POST', '/auth/device/action', {
+  forceNetworkError: true
+}).as('resetDeviceNetworkError');
+```
+
+---
+
+#### TC-RESET-05: 防止重复点击
+
+**测试目标**: 验证loading状态下防止重复操作
+
+**前置条件**:
+- 已登录系统
+- 已进入检查页面
+
+**测试步骤**:
+1. Mock设备重置API延迟响应(3秒)
+2. 点击RESET按钮
+3. 立即再次尝试点击按钮
+4. 验证第二次点击无效
+5. 验证只发送了一次API请求
+6. 等待第一次请求完成
+7. 验证可以再次点击
+
+**验证点**:
+- ✅ loading时按钮禁用
+- ✅ 再次点击无效
+- ✅ 只发送一次API请求
+- ✅ 完成后才能再次点击
+- ✅ 防抖机制有效
+
+**Mock配置**:
+```typescript
+let requestCount = 0;
+cy.intercept('POST', '/auth/device/action', (req) => {
+  requestCount++;
+  req.reply({
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      data: {}
+    },
+    delay: 3000
+  });
+}).as('resetDeviceMultiple');
+
+// 验证requestCount === 1
+```
+
+---
+
+## Page Object Model 设计
+
+### ExamPage 扩展
+
+**文件**: `cypress/support/pageObjects/ExamPage.ts`
+
+```typescript
+class ExamPage {
+  // ... 现有方法
+
+  /**
+   * 获取重置高压发生器按钮
+   */
+  getResetGeneratorButton() {
+    return cy.get('[data-testid="reset-generator-btn"]');
+  }
+
+  /**
+   * 点击重置按钮
+   */
+  clickResetGenerator() {
+    this.getResetGeneratorButton().click();
+  }
+
+  /**
+   * 验证重置按钮为启用状态
+   */
+  verifyResetButtonEnabled() {
+    this.getResetGeneratorButton()
+      .should('be.visible')
+      .should('not.be.disabled');
+  }
+
+  /**
+   * 验证重置按钮为禁用状态
+   */
+  verifyResetButtonDisabled() {
+    this.getResetGeneratorButton()
+      .should('be.visible')
+      .should('be.disabled');
+  }
+
+  /**
+   * 等待重置操作完成
+   */
+  waitForResetComplete() {
+    cy.wait('@resetDevice');
+    this.verifyResetButtonEnabled();
+  }
+
+  /**
+   * 验证Redux设备状态
+   * @param expectedStatus - 期望的状态: 'idle' | 'loading' | 'succeeded' | 'failed'
+   */
+  verifyDeviceStatus(expectedStatus: string) {
+    cy.window().its('store').invoke('getState')
+      .its('device').its('status')
+      .should('eq', expectedStatus);
+  }
+
+  /**
+   * 验证设备错误信息
+   * @param expectedError - 期望的错误信息,null表示无错误
+   */
+  verifyDeviceError(expectedError: string | null) {
+    cy.window().its('store').invoke('getState')
+      .its('device').its('error')
+      .should('eq', expectedError);
+  }
+}
+```
+
+---
+
+## Mock Handlers 设计
+
+### 文件结构
+
+**文件**: `cypress/support/mock/handlers/deviceActions.ts`
+
+```typescript
+/**
+ * Mock设备重置成功响应
+ */
+export const mockResetDeviceSuccess = () => {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          data: {}
+        }
+      });
+    }
+  }).as('resetDevice');
+};
+
+/**
+ * Mock设备重置失败响应
+ * @param errorCode - 错误码,默认为'0x010001'
+ * @param description - 错误描述
+ */
+export const mockResetDeviceFail = (
+  errorCode: string = '0x010001',
+  description: string = '设备通信失败'
+) => {
+  cy.intercept('POST', '/auth/device/action', {
+    statusCode: 200,
+    body: {
+      code: errorCode,
+      description: description,
+      solution: '检查设备连接'
+    }
+  }).as('resetDeviceFail');
+};
+
+/**
+ * Mock网络错误
+ */
+export const mockResetDeviceNetworkError = () => {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({ forceNetworkError: true });
+    }
+  }).as('resetDeviceNetworkError');
+};
+
+/**
+ * Mock设备重置延迟响应
+ * @param delayMs - 延迟毫秒数
+ */
+export const mockResetDeviceDelay = (delayMs: number) => {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    if (req.body.reqName === 'RESET') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          data: {}
+        },
+        delay: delayMs
+      });
+    }
+  }).as('resetDeviceDelay');
+};
+
+/**
+ * Mock所有设备操作(用于其他测试场景)
+ */
+export const mockAllDeviceActions = () => {
+  cy.intercept('POST', '/auth/device/action', (req) => {
+    req.reply({
+      statusCode: 200,
+      body: {
+        code: '0x000000',
+        description: 'Success',
+        data: {}
+      }
+    });
+  }).as('deviceAction');
+};
+```
+
+---
+
+## UI组件修改
+
+### ContentAreaLarge.tsx 修改点
+
+在RESET按钮添加测试标识:
+
+```tsx
+<Button
+  data-testid="reset-generator-btn"  // 新增测试标识
+  style={{ width: '1.5rem', height: '1.5rem' }}
+  icon={
+    <Icon
+      module="module-exam"
+      name="btn_ResetGenerator"
+      userId="base"
+      theme="default"
+      size="2x"
+      state="normal"
+    />
+  }
+  title="重置参数"
+  onClick={handleResetParameters}
+  disabled={isResetting}
+/>
+```
+
+---
+
+## 测试数据准备
+
+### 前置条件Mock
+
+每个测试用例都需要以下Mock:
+
+1. **多语言资源(必需)**
+   ```typescript
+   mockI18nSuccess('zh_CN');           // 获取中文翻译资源
+   mockGetLanguageListSuccess();        // 获取语言列表
+   mockAllRequiredAPIs('zh_CN');       // Mock软件信息和日志API
+   ```
+
+2. **登录相关**
+   - 用户认证 - LoginPage已处理
+   - 权限检查 - LoginPage已处理
+   
+3. **配额检查**
+   - Quota API Mock - LoginPage已处理
+
+4. **检查页面数据**
+   - 患者信息 - WorklistPage已处理
+   - 体位列表 - 进入检查页面后自动加载
+   - APR配置 - 进入检查页面后自动加载
+
+### Redux State初始化
+
+```typescript
+// 测试前重置Redux状态
+beforeEach(() => {
+  cy.window().its('store').invoke('dispatch', {
+    type: 'device/resetState'
+  });
+});
+```
+
+---
+
+## 测试执行计划
+
+### 第一阶段:核心功能测试(优先级高)
+
+- ✅ TC-RESET-01: 成功重置设备
+- ✅ TC-RESET-02: API失败处理
+- ✅ TC-RESET-03: 按钮禁用状态管理
+
+### 第二阶段:异常处理测试(优先级中)
+
+- ✅ TC-RESET-04: 网络错误处理
+- ✅ TC-RESET-05: 防止重复点击
+
+### 第三阶段:集成测试(优先级低)
+
+- 与其他功能的集成测试
+- 性能测试
+
+---
+
+## 测试覆盖率目标
+
+- **功能覆盖**: 100% - 所有重置相关功能
+- **代码覆盖**: 90%+ - 核心业务逻辑代码
+- **边界测试**: 包含成功、失败、网络异常等场景
+- **状态测试**: 验证所有Redux状态转换
+
+---
+
+## 验收标准
+
+### 功能验收
+
+- ✅ 所有5个测试用例通过
+- ✅ 成功和失败场景都能正确处理
+- ✅ UI状态与Redux状态同步
+- ✅ 错误处理机制完善
+- ✅ 按钮状态管理正确
+
+### 性能验收
+
+- ✅ 按钮响应及时(<100ms)
+- ✅ API调用正确(请求体符合规范)
+- ✅ loading状态管理准确
+- ✅ 无内存泄漏
+
+### 用户体验验收
+
+- ✅ 按钮禁用时有视觉反馈
+- ✅ 操作结果有明确提示
+- ✅ 错误时可重试
+- ✅ 防止误操作
+
+---
+
+## 常见错误场景
+
+### 错误码对照表
+
+| 错误码 | 描述 | 解决方案 |
+|--------|------|----------|
+| 0x000000 | 成功 | - |
+| 0x010001 | 设备通信失败 | 检查设备连接 |
+| 0x010002 | 设备忙碌 | 等待设备空闲后重试 |
+| 0x010003 | 参数错误 | 检查请求参数 |
+| 0x010004 | 超时 | 检查网络连接 |
+
+---
+
+## 附录
+
+### 相关文件清单
+
+**新建文件**:
+1. `docs/测试/重置高压发生器功能测试方案.md` - 本文档
+2. `cypress/e2e/exam/reset-generator.cy.ts` - 测试用例
+3. `cypress/support/mock/handlers/deviceActions.ts` - Mock处理器
+
+**修改文件**:
+1. `cypress/support/pageObjects/ExamPage.ts` - 扩展POM
+2. `src/pages/exam/ContentAreaLarge.tsx` - 添加data-testid
+
+### API接口文档
+
+**接口**: POST /auth/device/action
+
+**请求参数**:
+```typescript
+interface DeviceActionMessage {
+  deviceUri: string;      // 设备URI
+  reqName: string;        // 请求名称
+  reqParam: string;       // 请求参数
+  reqTransaction: string; // 事务ID
+  reqClientID: string;    // 客户端ID
+}
+```
+
+**响应格式**:
+```typescript
+interface DeviceActionResponse {
+  code: string;          // 状态码
+  description: string;   // 描述信息
+  solution?: string;     // 解决方案(可选)
+  data?: any;           // 响应数据(可选)
+}
+```
+
+---
+
+## 文档版本
+
+- **版本**: 1.0.0
+- **创建日期**: 2025-10-10
+- **最后更新**: 2025-10-10
+- **作者**: Cline AI Assistant
+- **审核**: 待审核

+ 1 - 0
src/pages/exam/ContentAreaLarge.tsx

@@ -354,6 +354,7 @@ const ContentAreaLarge = () => {
         </div>
         <Flex align="center" justify="start" gap="middle" wrap>
           <Button
+          data-testid="reset-generator-btn"
             style={{ width: '1.5rem', height: '1.5rem' }}
             icon={
               <Icon

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.