Browse Source

test: 添加Worklist和History删除功能的E2E测试

- 新增worklist-delete.cy.ts测试文件,覆盖删除功能的5个测试场景
- 新增history-delete.cy.ts测试文件,覆盖删除功能的5个测试场景
- 新增HistoryPage.ts页面对象,封装History页面的操作方法
- 在WorklistPage.ts中添加删除相关方法(点击删除、确认、取消等)
- 在worklist.ts中添加删除Study的mock函数和History数据mock

测试覆盖场景:
1. 成功删除未锁定的记录
2. 尝试删除锁定的记录时显示警告
3. 删除API失败时的错误处理
4. 取消删除操作
5. 未选中记录时的警告提示

改动文件:
- cypress/e2e/patient/worklist-delete.cy.ts (新增)
- cypress/e2e/patient/history-delete.cy.ts (新增)
- cypress/support/pageObjects/HistoryPage.ts (新增)
- cypress/support/pageObjects/WorklistPage.ts (修改)
- cypress/support/mock/handlers/worklist.ts (修改)
dengdx 3 days ago
parent
commit
320caac94d

+ 231 - 0
cypress/e2e/patient/history-delete.cy.ts

@@ -0,0 +1,231 @@
+/**
+ * 测试文件: History - 删除功能
+ * 
+ * 测试场景:
+ * 1. 成功删除未锁定的记录
+ * 2. 尝试删除锁定的记录(应显示警告)
+ * 3. 删除API失败的情况(网络错误/服务器错误)
+ * 4. 取消删除操作
+ */
+
+import { mockLoginSuccess } from '../../support/mock/handlers/user';
+import {
+  mockFetchHistoryDataWithUnlocked,
+  mockFetchHistoryDataWithLocked,
+  mockDeleteStudySuccess,
+  mockDeleteStudyFailure,
+  DeleteStudy,
+  FetchHistoryDataWithUnlocked,
+  FetchHistoryDataWithLocked,
+} from '../../support/mock/handlers/worklist';
+import {
+  mockI18nSuccess,
+  mockGetLanguageListSuccess,
+  mockAllRequiredAPIs,
+} from '../../support/mock/handlers/i18n';
+import { mockGetQuotaSuccess } from '../../support/mock/handlers/quota';
+import LoginPage from '../../support/pageObjects/LoginPage';
+import MainPage from '../../support/pageObjects/MainPage';
+import HistoryPage from '../../support/pageObjects/HistoryPage';
+
+describe('History - 删除功能测试', () => {
+  const loginPage = new LoginPage();
+  const mainPage = new MainPage();
+  const historyPage = new HistoryPage();
+
+  beforeEach(() => {
+    // Mock多语言资源和必要的API
+    mockI18nSuccess('zh_CN');
+    mockGetLanguageListSuccess();
+    mockAllRequiredAPIs('zh_CN');
+
+    // Mock登录成功响应
+    mockLoginSuccess();
+
+    // Mock配额成功
+    mockGetQuotaSuccess();
+
+    // 登录系统
+    loginPage.visit();
+    loginPage.login('admin', '123456');
+
+    // 等待页面渲染和路由跳转
+    cy.wait(1500);
+
+    // 验证登录成功:登录页面元素不再存在
+    loginPage.getUsernameInput().should('not.exist');
+    loginPage.getPasswordInput().should('not.exist');
+    loginPage.getSubmitButton().should('not.exist');
+
+    // ⚠️ 不在这里导航,让每个测试用例自己设置mock并导航
+  });
+
+  it('应该成功删除未锁定的记录', () => {
+    // 设置History数据mock(未锁定)
+    mockFetchHistoryDataWithUnlocked();
+    mockDeleteStudySuccess();
+
+    // 导航到History列表
+    mainPage.clickHistorylistButton();
+    cy.wait(FetchHistoryDataWithUnlocked);
+
+    // 验证初始状态:2条记录
+    historyPage.verifyRowCount(2);
+
+    // 调试:点击前查看Redux state
+    cy.window().its('store').invoke('getState').then(state => {
+      cy.log('Before click - selectedIds:', state.historySelection?.selectedIds || []);
+    });
+
+    // 点击第一行(未锁定)选中
+    historyPage.clickRowByIndex(0);
+
+    // 等待React更新state
+    cy.wait(500);
+
+    // 调试:点击后查看Redux state
+    cy.window().its('store').invoke('getState').then(state => {
+      cy.log('After click - selectedIds:', state.historySelection?.selectedIds || []);
+    });
+
+    historyPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    historyPage.clickDeleteButton();
+    // 等待 提示框出现
+    cy.wait(1500);
+    // 验证确认对话框出现
+    historyPage.getDeleteConfirmModal();
+    cy.contains('确认删除').should('be.visible');
+
+    // 确认删除
+    historyPage.confirmDeleteInModal();
+
+    // 等待删除API调用
+    cy.wait(DeleteStudy).then((interception) => {
+      expect(interception.response?.statusCode).to.eq(200);
+      expect(interception.response?.body.code).to.eq('0x000000');
+    });
+
+    // 验证删除后只剩1条记录(本地删除,无需等待列表刷新)
+    historyPage.verifyRowCount(1);
+  });
+
+  it('尝试删除锁定的记录时应显示警告', () => {
+    // 设置History数据mock(已锁定)
+    mockFetchHistoryDataWithLocked();
+
+    // 导航到History列表
+    mainPage.clickHistorylistButton();
+    cy.wait(FetchHistoryDataWithLocked);
+
+    // 验证有2条记录
+    historyPage.verifyRowCount(2);
+
+    // 点击第一行(已锁定)选中
+    historyPage.clickRowByIndex(0);
+    historyPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    historyPage.clickDeleteButton();
+
+    // 验证警告消息:锁定状态不可删除
+    historyPage.verifyDeleteWarningMessage('锁定状态不可删除');
+
+    // 验证没有弹出确认对话框(因为被提前拦截了)
+    historyPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    historyPage.verifyRowCount(2);
+  });
+
+  it('删除API失败时应显示错误提示', () => {
+    // 设置History数据mock和失败的删除API
+    mockFetchHistoryDataWithUnlocked();
+    mockDeleteStudyFailure();
+
+    // 导航到History列表
+    mainPage.clickHistorylistButton();
+    cy.wait(FetchHistoryDataWithUnlocked);
+
+    // 验证有2条记录
+    historyPage.verifyRowCount(2);
+
+    // 点击第一行选中
+    historyPage.clickRowByIndex(0);
+    historyPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    historyPage.clickDeleteButton();
+
+    // 验证确认对话框出现
+    historyPage.getDeleteConfirmModal();
+
+    // 确认删除
+    historyPage.confirmDeleteInModal();
+
+    // 等待删除API调用(失败)
+    cy.wait(DeleteStudy).then((interception) => {
+      expect(interception.response?.statusCode).to.eq(200);
+       expect(interception.response?.body.code).to.not.eq('0x000000');
+    });
+
+    // 验证记录仍然存在(因为删除失败)
+    historyPage.verifyRowCount(2);
+  });
+
+  it('取消删除操作时记录应保持不变', () => {
+    // 设置History数据mock
+    mockFetchHistoryDataWithUnlocked();
+
+    // 导航到History列表
+    mainPage.clickHistorylistButton();
+    cy.wait(FetchHistoryDataWithUnlocked);
+
+    // 验证有2条记录
+    historyPage.verifyRowCount(2);
+
+    // 点击第一行选中
+    historyPage.clickRowByIndex(0);
+    historyPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    historyPage.clickDeleteButton();
+
+    // 验证确认对话框出现
+    historyPage.getDeleteConfirmModal();
+
+    // 取消删除
+    historyPage.cancelDeleteInModal();
+
+    // 验证对话框关闭
+    historyPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    historyPage.verifyRowCount(2);
+  });
+
+  it('未选中任何记录时点击删除应显示警告', () => {
+    // 设置History数据mock
+    mockFetchHistoryDataWithUnlocked();
+
+    // 导航到History列表
+    mainPage.clickHistorylistButton();
+    cy.wait(FetchHistoryDataWithUnlocked);
+
+    // 验证有2条记录
+    historyPage.verifyRowCount(2);
+
+    // 不选中任何记录,直接点击删除按钮
+    historyPage.clickDeleteButton();
+
+    // 验证警告消息:请先选择要删除的项目
+    historyPage.verifyDeleteWarningMessage('请先选择要删除的项目');
+
+    // 验证没有弹出确认对话框
+    historyPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    historyPage.verifyRowCount(2);
+  });
+});

+ 231 - 0
cypress/e2e/patient/worklist-delete.cy.ts

@@ -0,0 +1,231 @@
+/**
+ * 测试文件: Worklist - 删除功能
+ * 
+ * 测试场景:
+ * 1. 成功删除未锁定的记录
+ * 2. 尝试删除锁定的记录(应显示警告)
+ * 3. 删除API失败的情况(网络错误/服务器错误)
+ * 4. 取消删除操作
+ */
+
+import { mockLoginSuccess } from '../../support/mock/handlers/user';
+import {
+  mockFetchTwoWorksWithUnlocked,
+  mockFetchTwoWorksWithLocked,
+  mockDeleteStudySuccess,
+  mockDeleteStudyFailure,
+  DeleteStudy,
+  FetchTwoWorksWithUnlocked,
+  FetchTwoWorksWithLocked,
+} from '../../support/mock/handlers/worklist';
+import {
+  mockI18nSuccess,
+  mockGetLanguageListSuccess,
+  mockAllRequiredAPIs,
+} from '../../support/mock/handlers/i18n';
+import { mockGetQuotaSuccess } from '../../support/mock/handlers/quota';
+import LoginPage from '../../support/pageObjects/LoginPage';
+import MainPage from '../../support/pageObjects/MainPage';
+import WorklistPage from '../../support/pageObjects/WorklistPage';
+
+describe('Worklist - 删除功能测试', () => {
+  const loginPage = new LoginPage();
+  const mainPage = new MainPage();
+  const worklistPage = new WorklistPage();
+
+  beforeEach(() => {
+    // Mock多语言资源和必要的API
+    mockI18nSuccess('zh_CN');
+    mockGetLanguageListSuccess();
+    mockAllRequiredAPIs('zh_CN');
+
+    // Mock登录成功响应
+    mockLoginSuccess();
+
+    // Mock配额成功
+    mockGetQuotaSuccess();
+
+    // 登录系统
+    loginPage.visit();
+    loginPage.login('admin', '123456');
+
+    // 等待页面渲染和路由跳转
+    cy.wait(1500);
+
+    // 验证登录成功:登录页面元素不再存在
+    loginPage.getUsernameInput().should('not.exist');
+    loginPage.getPasswordInput().should('not.exist');
+    loginPage.getSubmitButton().should('not.exist');
+
+    // ⚠️ 不在这里导航,让每个测试用例自己设置mock并导航
+  });
+
+  it('应该成功删除未锁定的记录', () => {
+    // 设置Worklist数据mock(未锁定)
+    mockFetchTwoWorksWithUnlocked();
+    mockDeleteStudySuccess();
+
+    // 导航到工作列表
+    mainPage.clickWorklistButton();
+    cy.wait(FetchTwoWorksWithUnlocked);
+
+    // 验证初始状态:2条记录
+    worklistPage.verifyRowCount(2);
+
+    // 调试:点击前查看Redux state
+    cy.window().its('store').invoke('getState').then(state => {
+      cy.log('Before click - selectedIds:', state.workSelection?.selectedIds || []);
+    });
+
+    // 点击第一行(未锁定)选中
+    worklistPage.clickRowByIndex(0);
+
+    // 等待React更新state
+    cy.wait(500);
+
+    // 调试:点击后查看Redux state
+    cy.window().its('store').invoke('getState').then(state => {
+      cy.log('After click - selectedIds:', state.workSelection?.selectedIds || []);
+    });
+
+    worklistPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    worklistPage.clickDeleteButton();
+
+    // 验证确认对话框出现
+    worklistPage.getDeleteConfirmModal();
+    cy.contains('确认删除').should('be.visible');
+    cy.contains('确定要删除选中的 1 个项目吗').should('be.visible');
+
+    // 确认删除
+    worklistPage.confirmDeleteInModal();
+
+    // 等待删除API调用
+    cy.wait(DeleteStudy).then((interception) => {
+      expect(interception.response?.statusCode).to.eq(200);
+      expect(interception.response?.body.code).to.eq('0x000000');
+    });
+
+    // 验证删除后只剩1条记录(本地删除,无需等待列表刷新)
+    worklistPage.verifyRowCount(1);
+  });
+
+  it('尝试删除锁定的记录时应显示警告', () => {
+    // 设置Worklist数据mock(已锁定)
+    mockFetchTwoWorksWithLocked();
+
+    // 导航到工作列表
+    mainPage.clickWorklistButton();
+    cy.wait(FetchTwoWorksWithLocked);
+
+    // 验证有2条记录
+    worklistPage.verifyRowCount(2);
+
+    // 点击第一行(已锁定)选中
+    worklistPage.clickRowByIndex(0);
+    worklistPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    worklistPage.clickDeleteButton();
+
+    // 验证警告消息:锁定状态不可删除
+    worklistPage.verifyDeleteWarningMessage('锁定状态不可删除');
+
+    // 验证没有弹出确认对话框(因为被提前拦截了)
+    worklistPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    worklistPage.verifyRowCount(2);
+  });
+
+  it('删除API失败时应显示错误提示', () => {
+    // 设置Worklist数据mock和失败的删除API
+    mockFetchTwoWorksWithUnlocked();
+    mockDeleteStudyFailure();
+
+    // 导航到工作列表
+    mainPage.clickWorklistButton();
+    cy.wait(FetchTwoWorksWithUnlocked);
+
+    // 验证有2条记录
+    worklistPage.verifyRowCount(2);
+
+    // 点击第一行选中
+    worklistPage.clickRowByIndex(0);
+    worklistPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    worklistPage.clickDeleteButton();
+
+    // 验证确认对话框出现
+    worklistPage.getDeleteConfirmModal();
+
+    // 确认删除
+    worklistPage.confirmDeleteInModal();
+
+    // 等待删除API调用(失败)
+    cy.wait(DeleteStudy).then((interception) => {
+      expect(interception.response?.statusCode).to.eq(200);
+      expect(interception.response?.body.code).to.not.eq('0x000000');
+    });
+
+    // 验证记录仍然存在(因为删除失败)
+    worklistPage.verifyRowCount(2);
+  });
+
+  it('取消删除操作时记录应保持不变', () => {
+    // 设置Worklist数据mock
+    mockFetchTwoWorksWithUnlocked();
+
+    // 导航到工作列表
+    mainPage.clickWorklistButton();
+    cy.wait(FetchTwoWorksWithUnlocked);
+
+    // 验证有2条记录
+    worklistPage.verifyRowCount(2);
+
+    // 点击第一行选中
+    worklistPage.clickRowByIndex(0);
+    worklistPage.verifyRowSelected(0);
+
+    // 点击删除按钮
+    worklistPage.clickDeleteButton();
+
+    // 验证确认对话框出现
+    worklistPage.getDeleteConfirmModal();
+
+    // 取消删除
+    worklistPage.cancelDeleteInModal();
+
+    // 验证对话框关闭
+    worklistPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    worklistPage.verifyRowCount(2);
+  });
+
+  it('未选中任何记录时点击删除应显示警告', () => {
+    // 设置Worklist数据mock
+    mockFetchTwoWorksWithUnlocked();
+
+    // 导航到工作列表
+    mainPage.clickWorklistButton();
+    cy.wait(FetchTwoWorksWithUnlocked);
+
+    // 验证有2条记录
+    worklistPage.verifyRowCount(2);
+
+    // 不选中任何记录,直接点击删除按钮
+    worklistPage.clickDeleteButton();
+
+    // 验证警告消息:请先选择要删除的项目
+    worklistPage.verifyDeleteWarningMessage('请先选择要删除的项目');
+
+    // 验证没有弹出确认对话框
+    worklistPage.verifyModalNotExist();
+
+    // 验证记录仍然存在
+    worklistPage.verifyRowCount(2);
+  });
+});

+ 741 - 7
cypress/support/mock/handlers/worklist.ts

@@ -1,9 +1,15 @@
-const aliasOfFetchTwoWorks='getTwoStudy'
-export const FetchTwoWorks=`@${aliasOfFetchTwoWorks}`;
-export function mockFetchTwoWorks(){
-    cy.intercept('GET', '/dr/api/v1/auth/study*', {
-      statusCode: 200,
-      body: {
+const aliasOfFetchTwoWorks = 'getTwoStudy';
+export const FetchTwoWorks = `@${aliasOfFetchTwoWorks}`;
+
+// 删除Study的alias
+const aliasOfDeleteStudy = 'deleteStudy';
+export const DeleteStudy = `@${aliasOfDeleteStudy}`;
+
+// 原有的mock函数
+export function mockFetchTwoWorks() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
         "code": "0x000000",
         "description": "Success",
         "solution": "",
@@ -124,5 +130,733 @@ export function mockFetchTwoWorks(){
           ]
         }
       }
-    }).as(aliasOfFetchTwoWorks);
+  }).as(aliasOfFetchTwoWorks);
+}
+
+/**
+ * Mock: 返回2条记录,第一条未锁定
+ */
+const aliasOfFetchTwoWorksWithUnlocked = 'getTwoWorksUnlocked';
+export const FetchTwoWorksWithUnlocked = `@${aliasOfFetchTwoWorksWithUnlocked}`;
+
+export function mockFetchTwoWorksWithUnlocked() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 2,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.100635',
+            study_id: '20250912135259444',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'ACC0012345',
+            ref_physician: 'Dr. Smith (Vet)',
+            patient_id: 'PET007',
+            patient_name: '测试患者1',
+            patient_english_name: 'Test Patient 1',
+            patient_former_name: '',
+            patient_size: 'Large',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '5Y',
+            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-12T05:52:59.699143Z',
+            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: '测试记录1',
+            study_status: 'Arrived',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.100627',
+            study_id: '20250912135259265',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'ACC0012346',
+            ref_physician: 'Dr. Smith (Vet)',
+            patient_id: 'PET008',
+            patient_name: '测试患者2',
+            patient_english_name: 'Test Patient 2',
+            patient_former_name: '',
+            patient_size: 'Large',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '3Y',
+            patient_dob: '2025-06-10T03:12:36.181739Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-12T05:52:59.573182Z',
+            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: '测试记录2',
+            study_status: 'Arrived',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchTwoWorksWithUnlocked);
+}
+
+/**
+ * Mock: 返回2条记录,第一条已锁定
+ */
+const aliasOfFetchTwoWorksWithLocked = 'getTwoWorksLocked';
+export const FetchTwoWorksWithLocked = `@${aliasOfFetchTwoWorksWithLocked}`;
+
+export function mockFetchTwoWorksWithLocked() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 2,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.100635',
+            study_id: '20250912135259444',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'ACC0012345',
+            ref_physician: 'Dr. Smith (Vet)',
+            patient_id: 'PET007',
+            patient_name: '测试患者1',
+            patient_english_name: 'Test Patient 1',
+            patient_former_name: '',
+            patient_size: 'Large',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '5Y',
+            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-12T05:52:59.699143Z',
+            study_end_datetime: null,
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Locked', // 已锁定
+            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: '测试记录1-已锁定',
+            study_status: 'Arrived',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.100627',
+            study_id: '20250912135259265',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'ACC0012346',
+            ref_physician: 'Dr. Smith (Vet)',
+            patient_id: 'PET008',
+            patient_name: '测试患者2',
+            patient_english_name: 'Test Patient 2',
+            patient_former_name: '',
+            patient_size: 'Large',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '3Y',
+            patient_dob: '2025-06-10T03:12:36.181739Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-12T05:52:59.573182Z',
+            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: '测试记录2',
+            study_status: 'Arrived',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchTwoWorksWithLocked);
+}
+
+/**
+ * Mock: 删除后返回1条记录
+ */
+const aliasOfFetchWorklistAfterDelete = 'getWorklistAfterDelete';
+export const FetchWorklistAfterDelete = `@${aliasOfFetchWorklistAfterDelete}`;
+
+export function mockFetchWorklistAfterDelete() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 1,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.100627',
+            study_id: '20250912135259265',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'ACC0012346',
+            ref_physician: 'Dr. Smith (Vet)',
+            patient_id: 'PET008',
+            patient_name: '测试患者2',
+            patient_english_name: 'Test Patient 2',
+            patient_former_name: '',
+            patient_size: 'Large',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '3Y',
+            patient_dob: '2025-06-10T03:12:36.181739Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-12T05:52:59.573182Z',
+            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: '测试记录2',
+            study_status: 'Arrived',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchWorklistAfterDelete);
+}
+
+/**
+ * Mock: 删除Study成功
+ */
+export function mockDeleteStudySuccess() {
+  cy.intercept('DELETE', '/dr/api/v1/auth/study', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: '删除成功',
+      solution: '',
+      data: {},
+    },
+  }).as(aliasOfDeleteStudy);
+}
+
+/**
+ * Mock: 删除Study失败
+ */
+export function mockDeleteStudyFailure() {
+  cy.intercept('DELETE', '/dr/api/v1/auth/study', {
+    statusCode: 200,
+    body: {
+      code: '0x000001',
+      description: '删除失败',
+      solution: '请稍后重试',
+      data: {},
+    },
+  }).as(aliasOfDeleteStudy);
+}
+
+/**
+ * Mock: History数据 - 2条记录,第一条未锁定
+ */
+const aliasOfFetchHistoryDataWithUnlocked = 'getHistoryUnlocked';
+export const FetchHistoryDataWithUnlocked = `@${aliasOfFetchHistoryDataWithUnlocked}`;
+
+export function mockFetchHistoryDataWithUnlocked() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 2,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.200001',
+            study_id: 'HIST20250912001',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'HIST001',
+            ref_physician: 'Dr. Johnson',
+            patient_id: 'HIST001',
+            patient_name: 'History患者1',
+            patient_english_name: 'History Patient 1',
+            patient_former_name: '',
+            patient_size: 'Medium',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '40Y',
+            patient_dob: '1985-01-01T00:00:00Z',
+            patient_sex: 'M',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-01T08:00:00Z',
+            study_end_datetime: '2025-09-01T09:00:00Z',
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Unlocked', // 未锁定
+            folder_path: '',
+            operator_name: 'OP001',
+            modality: 'DX',
+            weight: 70,
+            thickness: 20,
+            length: 175,
+            patient_type: 'Human',
+            study_type: 'Normal',
+            owner_name: '',
+            chip_number: '',
+            variety: '',
+            is_anaesthesia: false,
+            is_sedation: false,
+            mwl: '',
+            is_exported: true,
+            is_edited: false,
+            is_appended: false,
+            department: '',
+            mapped_status: true,
+            qc_result: true,
+            comment: 'History测试记录1',
+            study_status: 'Completed',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.200002',
+            study_id: 'HIST20250912002',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'HIST002',
+            ref_physician: 'Dr. Johnson',
+            patient_id: 'HIST002',
+            patient_name: 'History患者2',
+            patient_english_name: 'History Patient 2',
+            patient_former_name: '',
+            patient_size: 'Medium',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '35Y',
+            patient_dob: '1990-01-01T00:00:00Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-02T08:00:00Z',
+            study_end_datetime: '2025-09-02T09:00:00Z',
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Unlocked', // 未锁定
+            folder_path: '',
+            operator_name: 'OP001',
+            modality: 'DX',
+            weight: 65,
+            thickness: 18,
+            length: 165,
+            patient_type: 'Human',
+            study_type: 'Normal',
+            owner_name: '',
+            chip_number: '',
+            variety: '',
+            is_anaesthesia: false,
+            is_sedation: false,
+            mwl: '',
+            is_exported: true,
+            is_edited: false,
+            is_appended: false,
+            department: '',
+            mapped_status: true,
+            qc_result: true,
+            comment: 'History测试记录2',
+            study_status: 'Completed',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchHistoryDataWithUnlocked);
+}
+
+/**
+ * Mock: History数据 - 2条记录,第一条已锁定
+ */
+const aliasOfFetchHistoryDataWithLocked = 'getHistoryLocked';
+export const FetchHistoryDataWithLocked = `@${aliasOfFetchHistoryDataWithLocked}`;
+
+export function mockFetchHistoryDataWithLocked() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 2,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.200001',
+            study_id: 'HIST20250912001',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'HIST001',
+            ref_physician: 'Dr. Johnson',
+            patient_id: 'HIST001',
+            patient_name: 'History患者1',
+            patient_english_name: 'History Patient 1',
+            patient_former_name: '',
+            patient_size: 'Medium',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '40Y',
+            patient_dob: '1985-01-01T00:00:00Z',
+            patient_sex: 'M',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-01T08:00:00Z',
+            study_end_datetime: '2025-09-01T09:00:00Z',
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Locked', // 已锁定
+            folder_path: '',
+            operator_name: 'OP001',
+            modality: 'DX',
+            weight: 70,
+            thickness: 20,
+            length: 175,
+            patient_type: 'Human',
+            study_type: 'Normal',
+            owner_name: '',
+            chip_number: '',
+            variety: '',
+            is_anaesthesia: false,
+            is_sedation: false,
+            mwl: '',
+            is_exported: true,
+            is_edited: false,
+            is_appended: false,
+            department: '',
+            mapped_status: true,
+            qc_result: true,
+            comment: 'History测试记录1-已锁定',
+            study_status: 'Completed',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.200002',
+            study_id: 'HIST20250912002',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'HIST002',
+            ref_physician: 'Dr. Johnson',
+            patient_id: 'HIST002',
+            patient_name: 'History患者2',
+            patient_english_name: 'History Patient 2',
+            patient_former_name: '',
+            patient_size: 'Medium',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '35Y',
+            patient_dob: '1990-01-01T00:00:00Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-02T08:00:00Z',
+            study_end_datetime: '2025-09-02T09:00:00Z',
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Unlocked', // 未锁定
+            folder_path: '',
+            operator_name: 'OP001',
+            modality: 'DX',
+            weight: 65,
+            thickness: 18,
+            length: 165,
+            patient_type: 'Human',
+            study_type: 'Normal',
+            owner_name: '',
+            chip_number: '',
+            variety: '',
+            is_anaesthesia: false,
+            is_sedation: false,
+            mwl: '',
+            is_exported: true,
+            is_edited: false,
+            is_appended: false,
+            department: '',
+            mapped_status: true,
+            qc_result: true,
+            comment: 'History测试记录2',
+            study_status: 'Completed',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchHistoryDataWithLocked);
+}
+
+/**
+ * Mock: History删除后返回1条记录
+ */
+const aliasOfFetchHistoryAfterDelete = 'getHistoryAfterDelete';
+export const FetchHistoryAfterDelete = `@${aliasOfFetchHistoryAfterDelete}`;
+
+export function mockFetchHistoryAfterDelete() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 1,
+        studies: [
+          {
+            study_instance_uid:
+              '2.25.156.999999.0000.1.2.2831189249.819691.1757656379.200002',
+            study_id: 'HIST20250912002',
+            public_study_id: '',
+            specific_character_set: 'ISO_IR 192',
+            accession_number: 'HIST002',
+            ref_physician: 'Dr. Johnson',
+            patient_id: 'HIST002',
+            patient_name: 'History患者2',
+            patient_english_name: 'History Patient 2',
+            patient_former_name: '',
+            patient_size: 'Medium',
+            other_patient_ids: '',
+            other_patient_names: '',
+            patient_age: '35Y',
+            patient_dob: '1990-01-01T00:00:00Z',
+            patient_sex: 'F',
+            sex_neutered: '',
+            pregnancy_status: '',
+            patient_state: '',
+            admitting_time: null,
+            priority: '',
+            reg_source: '',
+            study_description: '',
+            study_start_datetime: '2025-09-02T08:00:00Z',
+            study_end_datetime: '2025-09-02T09:00:00Z',
+            scheduled_procedure_step_start_date: null,
+            performed_physician: '',
+            study_lock: 'Unlocked',
+            folder_path: '',
+            operator_name: 'OP001',
+            modality: 'DX',
+            weight: 65,
+            thickness: 18,
+            length: 165,
+            patient_type: 'Human',
+            study_type: 'Normal',
+            owner_name: '',
+            chip_number: '',
+            variety: '',
+            is_anaesthesia: false,
+            is_sedation: false,
+            mwl: '',
+            is_exported: true,
+            is_edited: false,
+            is_appended: false,
+            department: '',
+            mapped_status: true,
+            qc_result: true,
+            comment: 'History测试记录2',
+            study_status: 'Completed',
+            sort: 0,
+            product: 'DROS',
+            series: [],
+          },
+        ],
+      },
+    },
+  }).as(aliasOfFetchHistoryAfterDelete);
 }

+ 104 - 0
cypress/support/pageObjects/HistoryPage.ts

@@ -0,0 +1,104 @@
+/**
+ * HistoryPage - History列表页面的Page Object Model
+ * 用于History界面的元素选择器和操作方法
+ */
+class HistoryPage {
+  /**
+   * 获取表格
+   */
+  getTable() {
+    return cy.get('table');
+  }
+
+  /**
+   * 点击指定索引的行
+   * @param index 行索引(从0开始)
+   */
+  clickRowByIndex(index: number) {
+    cy.get('table').within(() => {
+      cy.get(`tbody tr[data-testid="row-${index}"]`)
+        .scrollIntoView()
+        .should('be.visible')
+        .click({ force: true });
+    });
+  }
+
+  /**
+   * 验证指定行被选中(黄色高亮)
+   * @param index 行索引(从0开始)
+   */
+  verifyRowSelected(index: number) {
+    cy.get('table').within(() => {
+      cy.get(`tbody tr[data-testid="row-${index}"]`)
+        .should('be.visible')
+        .should('have.class', 'bg-yellow-500');
+    });
+  }
+
+  /**
+   * 点击删除按钮
+   */
+  clickDeleteButton() {
+    cy.get('[data-testid="delete-button"]').click();
+  }
+
+  /**
+   * 获取删除确认对话框
+   */
+  getDeleteConfirmModal() {
+    return cy.get('.ant-modal-confirm').should('be.visible');
+  }
+
+  /**
+   * 在对话框中确认删除
+   */
+  confirmDeleteInModal() {
+    cy.get('[data-testid="modal-confirm-delete"]').click();
+  }
+
+  /**
+   * 在对话框中取消删除
+   */
+  cancelDeleteInModal() {
+    cy.get('[data-testid="modal-cancel-delete"]').click();
+  }
+
+  /**
+   * 验证删除成功提示消息
+   */
+  verifyDeleteSuccessMessage() {
+    cy.contains('删除成功').should('be.visible');
+  }
+
+  /**
+   * 验证删除警告消息
+   * @param expectedMessage 期望的警告消息文本
+   */
+  verifyDeleteWarningMessage(expectedMessage: string) {
+    cy.contains(expectedMessage).should('be.visible');
+  }
+
+  /**
+   * 验证删除错误消息
+   */
+  verifyDeleteErrorMessage() {
+    cy.contains('删除失败').should('be.visible');
+  }
+
+  /**
+   * 验证表格行数
+   * @param expectedCount 期望的行数
+   */
+  verifyRowCount(expectedCount: number) {
+    cy.get('table tbody tr').should('have.length.at.least', expectedCount);
+  }
+
+  /**
+   * 验证对话框不存在
+   */
+  verifyModalNotExist() {
+    cy.get('.ant-modal-confirm').should('not.exist');
+  }
+}
+
+export default HistoryPage;

+ 90 - 0
cypress/support/pageObjects/WorklistPage.ts

@@ -79,6 +79,96 @@ class WorklistPage {
   getHeaderCount() {
     return this.getTableHeaders().its('length');
   }
+
+  /**
+   * 点击指定索引的行
+   * @param index 行索引(从0开始)
+   */
+  clickRowByIndex(index: number) {
+    cy.get('table').within(() => {
+      cy.get(`tbody tr[data-testid="row-${index}"]`)
+        .scrollIntoView()
+        .should('be.visible')
+        .click({ force: true });
+    });
+  }
+
+  /**
+   * 验证指定行被选中(黄色高亮)
+   * @param index 行索引(从0开始)
+   */
+  verifyRowSelected(index: number) {
+    cy.get('table').within(() => {
+      cy.get(`tbody tr[data-testid="row-${index}"]`)
+        .should('be.visible')
+        .should('have.class', 'bg-yellow-500');
+    });
+  }
+
+  /**
+   * 点击删除按钮
+   */
+  clickDeleteButton() {
+    cy.get('[data-testid="delete-button"]').click();
+  }
+
+  /**
+   * 获取删除确认对话框
+   */
+  getDeleteConfirmModal() {
+    return cy.get('.ant-modal-confirm').should('be.visible');
+  }
+
+  /**
+   * 在对话框中确认删除
+   */
+  confirmDeleteInModal() {
+    cy.get('[data-testid="modal-confirm-delete"]').click();
+  }
+
+  /**
+   * 在对话框中取消删除
+   */
+  cancelDeleteInModal() {
+    cy.get('[data-testid="modal-cancel-delete"]').click();
+  }
+
+  /**
+   * 验证删除成功提示消息
+   */
+  verifyDeleteSuccessMessage() {
+    cy.contains('删除成功').should('be.visible');
+  }
+
+  /**
+   * 验证删除警告消息
+   * @param expectedMessage 期望的警告消息文本
+   */
+  verifyDeleteWarningMessage(expectedMessage: string) {
+    cy.contains(expectedMessage).should('be.visible');
+  }
+
+  /**
+   * 验证删除错误消息
+   */
+  verifyDeleteErrorMessage() {
+    cy.contains('删除失败').should('be.visible');
+  }
+
+  /**
+   * 验证表格行数
+   * @param expectedCount 期望的行数
+   */
+  verifyRowCount(expectedCount: number) {
+    cy.get('table tbody tr').should('have.length.at.least', expectedCount);
+  }
+
+  /**
+   * 验证对话框不存在
+   */
+  verifyModalNotExist() {
+    cy.get('.ant-modal-confirm').should('not.exist');
+  }
 }
 
 export default WorklistPage;