| @@ -98,6 +98,55 @@ const twoStepDefinition = (channel: Channel) => ({ | |||
| } : {}, | |||
| }) | |||
| const virtualPreviewDefinition = () => ({ | |||
| schema: 'unreal-tran.training', | |||
| version: '1.0', | |||
| level: '中修', | |||
| skills: ['总成拆检', '部件更换', '功能复验'], | |||
| scenario: { | |||
| schema: 'unreal-tran.scene', | |||
| version: '2.0', | |||
| name: '支腿液压缸中修训练场景', | |||
| sceneType: 'WORKSHOP', | |||
| objects: [{ | |||
| id: 'virtual-equipment', type: 'model', name: '支腿液压缸工位', | |||
| assetUrl: 'content://sha256/contract-excavator', assetCode: 'EXCAVATOR_A', | |||
| targetProjectId: 'asset-project-1', targetVersionId: 'asset-version-1', | |||
| position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], visible: true, | |||
| semantic: { interactionId: 'hydraulic-cylinder', operable: true }, | |||
| }], | |||
| }, | |||
| steps: [ | |||
| ['STEP-01', '卸压确认与故障基线采集', 'RELEASE_PRESSURE_BASELINE', '确认系统卸压并采集故障基线。', '确认压力表回零,记录故障压力和泄漏现象。'], | |||
| ['STEP-02', '油管与接头外泄检查', 'CHECK_EXTERNAL_LEAK', '检查油管、接头与密封面的外泄。', '沿管路逐点检查,确认无高压喷射风险。'], | |||
| ['STEP-03', '测量系统故障压力', 'MEASURE_FAULT_PRESSURE', '连接压力表并测量系统故障压力。', '按量程选择测点,读取并记录稳定压力值。'], | |||
| ['STEP-04', '保压定位液压缸内泄', 'LOCATE_INTERNAL_LEAK', '执行保压试验并定位液压缸内泄。', '隔离负载后观察压力衰减,判断内泄位置。'], | |||
| ['STEP-05', '更换活塞密封组件', 'REPLACE_PISTON_SEAL', '拆检并更换活塞密封组件。', '核对密封方向,避免划伤密封唇口和缸筒。'], | |||
| ['STEP-06', '缸盖复位与力矩紧固', 'TORQUE_CYLINDER_HEAD', '复位缸盖并按规定力矩紧固。', '按对角顺序分级紧固,记录最终力矩。'], | |||
| ['STEP-07', '复测工作压力与响应', 'VERIFY_PRESSURE_RESPONSE', '复测工作压力、动作速度与响应。', '低压试运转后逐级升压,观察动作是否平稳。'], | |||
| ['STEP-08', '密封复验与中修闭环', 'VERIFY_SEAL_AND_CLOSE', '完成密封复验并形成中修闭环。', '确认无泄漏、参数合格,清点工具并恢复防护。'], | |||
| ].map(([stepCode, title, actionCode, publicSummary, narrationText], index) => ({ | |||
| stepCode, | |||
| title, | |||
| actionCode, | |||
| publicSummary, | |||
| instruction: narrationText, | |||
| narrationText, | |||
| safety: index === 0 ? '执行前确认能量隔离与作业区域安全。' : '按维修作业规程佩戴防护用品并保持工位整洁。', | |||
| targetId: 'virtual-equipment', | |||
| targetName: '支腿液压缸工位', | |||
| score: index === 7 ? 16 : 12, | |||
| })), | |||
| runtime: { teacherEnabled: true, teacherVoiceEnabled: true, allowReplay: true }, | |||
| digitalInstructor: { | |||
| enabled: true, | |||
| agentSlug: 'dh-b69424ed0904', | |||
| agentName: '点火系统维修教研员', | |||
| webBaseUrl: '/ute2e-ai-person', | |||
| }, | |||
| modeConfig: { allowedModes: ['DEMO', 'PRACTICE', 'MOCK_EXAM'] }, | |||
| }) | |||
| const assignment = (id: string, channel: Channel, kind: AssignmentKind, audienceType: 'ASSIGNED' | 'COMMON', collaborationMode: 'INDIVIDUAL' | 'TEAM') => ({ | |||
| id, | |||
| code: kind === 'FORMAL_EXAM' ? 'EXAM-2026-001' : `TASK-${channel}-001`, | |||
| @@ -173,11 +222,23 @@ export class TeachingContractMock { | |||
| readonly deniedPermissions = new Set<string>() | |||
| administrativeRunsRejected = 0 | |||
| administrativeAnalyticsRecordsRejected = 0 | |||
| digitalInstructorFrameLoads = 0 | |||
| constructor(identity: TeachingIdentity) { | |||
| this.identity = identity | |||
| const previewTask = { | |||
| ...assignment('assignment-virtual-preview', 'VIRTUAL', 'TRAINING', 'ASSIGNED', 'INDIVIDUAL'), | |||
| code: 'TASK-VIRTUAL-PREVIEW-001', | |||
| name: '支腿液压缸中修训练(虚拟仿真)', | |||
| description: '面向液压密封件检查与更换的八步虚拟训练任务。', | |||
| definitionSnapshot: virtualPreviewDefinition(), | |||
| runCount: 0, | |||
| pendingReviewCount: 0, | |||
| listed: false, | |||
| } | |||
| const tasks = [ | |||
| assignment('assignment-virtual-common', 'VIRTUAL', 'TRAINING', 'COMMON', 'INDIVIDUAL'), | |||
| previewTask, | |||
| assignment('assignment-physical', 'PHYSICAL', 'TRAINING', 'ASSIGNED', 'INDIVIDUAL'), | |||
| assignment('assignment-confrontation', 'CONFRONTATION', 'TRAINING', 'ASSIGNED', 'TEAM'), | |||
| assignment('assignment-exam', 'VIRTUAL', 'FORMAL_EXAM', 'ASSIGNED', 'INDIVIDUAL'), | |||
| @@ -393,6 +454,14 @@ export class TeachingContractMock { | |||
| await page.route('**/api/tran/v1/content/catalog**', (route) => fulfill(route, pageResult([{ | |||
| projectId: 'training-project-1', versionId: 'training-version-1', type: 'TRAINING', code: 'TRAIN-001', name: '挖掘机液压系统训练', categoryCode: 'VIRTUAL', coverUri: '', versionNumber: 2, versionStatus: 'PUBLISHED', publishedAt: '2026-08-11T08:00:00+08:00', content: { steps: [] }, | |||
| }]), 'ute2e-teaching-catalog')) | |||
| await page.route(/\/embed\/dh-b69424ed0904(?:\?.*)?$/, (route) => { | |||
| this.digitalInstructorFrameLoads += 1 | |||
| return route.fulfill({ | |||
| status: 200, | |||
| contentType: 'text/html; charset=utf-8', | |||
| body: '<!doctype html><html lang="zh-CN"><body><main data-testid="mock-ai-person">虚拟教员在线答疑</main></body></html>', | |||
| }) | |||
| }) | |||
| await page.route('**/api/tran/v1/teaching/**', (route) => this.handleTeaching(route)) | |||
| } | |||
| @@ -430,13 +499,13 @@ export class TeachingContractMock { | |||
| if (method === 'GET' && path === '/assignments/summary') { | |||
| const channel = url.searchParams.get('channel') | |||
| const kind = url.searchParams.get('assignmentKind') ?? 'TRAINING' | |||
| const tasks = [...this.assignments.values()].filter((item) => (!channel || item.channel === channel) && item.assignmentKind === kind) | |||
| const tasks = [...this.assignments.values()].filter((item) => item.listed !== false && (!channel || item.channel === channel) && item.assignmentKind === kind) | |||
| return fulfill(route, { assignments: tasks.length, draft: 0, published: tasks.length, withdrawn: 0, archived: 0, pendingRuns: 0, activeRuns: this.visibleRuns().filter((item) => item.status === 'IN_PROGRESS').length, submittedRuns: this.visibleRuns().filter((item) => item.status === 'SUBMITTED').length, reviewedRuns: this.visibleRuns().filter((item) => item.status === 'REVIEWED').length, byChannel: Object.fromEntries(tasks.map((item) => [String(item.channel), 1])) }, 'ute2e-summary') | |||
| } | |||
| if (method === 'GET' && path === '/assignments') { | |||
| const channel = url.searchParams.get('channel') | |||
| const kind = url.searchParams.get('assignmentKind') ?? 'TRAINING' | |||
| const tasks = [...this.assignments.values()].filter((item) => (!channel || item.channel === channel) && item.assignmentKind === kind) | |||
| const tasks = [...this.assignments.values()].filter((item) => item.listed !== false && (!channel || item.channel === channel) && item.assignmentKind === kind) | |||
| return fulfill(route, pageResult(tasks), 'ute2e-assignments') | |||
| } | |||
| if (method === 'POST' && path === '/assignments') { | |||
| @@ -54,6 +54,33 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| expect(keywordBox!.width).toBeGreaterThan(230) | |||
| expect(keywordBox!.width).toBeLessThanOrEqual(270) | |||
| expect(createBox!.x + createBox!.width).toBeGreaterThan(toolbarBox!.x + toolbarBox!.width - 40) | |||
| if (path === '/teaching/virtual-training') { | |||
| const grid = page.locator('.ttc-assignment-grid') | |||
| const card = page.getByTestId('teaching-assignment-TASK-VIRTUAL-001') | |||
| const assignmentRunRequests = () => contract.requests.filter((request) => request.method === 'GET' | |||
| && request.path === '/runs' | |||
| && request.query.get('assignmentId') === 'assignment-virtual-common').length | |||
| const assignmentRunRequestsBefore = assignmentRunRequests() | |||
| await expect(grid).toHaveClass(/ttc-assignment-grid--single/) | |||
| await expect(card).toHaveClass(/virtual-prototype-card/) | |||
| await expect(card).toContainText('固化定义版本 2.0') | |||
| await expect(card.locator('.ttc-mode-summary')).toHaveText('演示 / 训练 / 模拟考试') | |||
| await expect(card).toContainText('分配学员') | |||
| expect(assignmentRunRequestsBefore).toBe(0) | |||
| const detailsButton = card.getByRole('button', { name: /学员明细 1/ }) | |||
| await expect(detailsButton).toHaveAttribute('aria-expanded', 'false') | |||
| await detailsButton.click() | |||
| const details = card.locator('.ttc-assignment-runs') | |||
| await expect(details).toBeVisible() | |||
| await expect(details.locator('tbody tr')).toHaveCount(1) | |||
| await expect(card.locator('.ttc-progress-block')).toContainText('100%') | |||
| await expect.poll(assignmentRunRequests).toBeGreaterThan(assignmentRunRequestsBefore) | |||
| await expect(detailsButton).toHaveAttribute('aria-expanded', 'true') | |||
| await detailsButton.click() | |||
| await expect(details).toHaveCount(0) | |||
| } | |||
| } | |||
| await expect(page.locator('iframe')).toHaveCount(0) | |||
| } | |||
| @@ -61,7 +88,7 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await page.goto('/teaching/virtual-training') | |||
| await page.getByTestId('teaching-tab-guides').click() | |||
| await expect(page).toHaveURL(/\/teaching\/virtual-training\?view=guides$/) | |||
| await expect(page.getByRole('heading', { name: '虚拟仿真作业指导书', exact: true })).toBeVisible() | |||
| await expect(page.getByRole('heading', { name: '虚拟仿真可用指导书', exact: true })).toBeVisible() | |||
| await expect(page.getByTestId('teaching-guide-library').locator('.guide-card-v2')).toHaveCount(1) | |||
| await page.getByRole('button', { name: /打开阅读/ }).first().click() | |||
| const guideReader = page.locator('.guide-reader-v2') | |||
| @@ -262,7 +289,7 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| expect(contract.count('GET', '/runs')).toBe(0) | |||
| expect(contract.administrativeRunsRejected).toBe(0) | |||
| await page.getByRole('button', { name: '查看', exact: true }).click() | |||
| await page.getByRole('button', { name: '任务详情', exact: true }).click() | |||
| const drawer = page.locator('.el-drawer:visible') | |||
| await expect(drawer).toContainText('虚拟维修训练') | |||
| await expect(drawer.getByText('执行与评定记录', { exact: true })).toHaveCount(0) | |||
| @@ -325,10 +352,10 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await switchIdentity(page, contract, 'teacher') | |||
| await page.goto('/teaching/virtual-training') | |||
| await page.getByRole('button', { name: '查看', exact: true }).click() | |||
| const drawer = page.locator('.el-drawer:visible') | |||
| await expect(drawer.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await drawer.getByRole('button', { name: '评定', exact: true }).click() | |||
| await page.getByRole('button', { name: /学员明细/ }).click() | |||
| const details = page.locator('.ttc-assignment-runs') | |||
| await expect(details.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await details.getByRole('button', { name: '评定', exact: true }).click() | |||
| await visibleDialog(page).getByRole('button', { name: '提交评定', exact: true }).click() | |||
| await expect(page.getByText('评定结果已提交', { exact: true })).toBeVisible() | |||
| expect(contract.runs.get('run-virtual-common')?.status).toBe('REVIEWED') | |||
| @@ -342,16 +369,16 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await contract.install(page) | |||
| await page.goto('/teaching/virtual-training') | |||
| await page.getByRole('button', { name: '查看', exact: true }).click() | |||
| await page.getByRole('button', { name: '任务详情', exact: true }).click() | |||
| await expect(page.locator('.el-drawer:visible').getByRole('button', { name: '超时结算', exact: true })).toHaveCount(0) | |||
| expect(contract.count('POST', '/runs/run-timeout/timeout')).toBe(0) | |||
| await switchIdentity(page, contract, 'teacher') | |||
| await page.getByRole('button', { name: '查看', exact: true }).click() | |||
| const drawer = page.locator('.el-drawer:visible') | |||
| await expect(drawer.getByRole('button', { name: '超时结算', exact: true })).toBeVisible() | |||
| await expect(drawer.getByRole('button', { name: '结束运行', exact: true })).toHaveCount(0) | |||
| await drawer.getByRole('button', { name: '超时结算', exact: true }).click() | |||
| await page.getByRole('button', { name: /学员明细/ }).click() | |||
| let details = page.locator('.ttc-assignment-runs') | |||
| await expect(details.getByRole('button', { name: '超时结算', exact: true })).toBeVisible() | |||
| await expect(details.getByRole('button', { name: '结束运行', exact: true })).toHaveCount(0) | |||
| await details.getByRole('button', { name: '超时结算', exact: true }).click() | |||
| const timeoutDialog = visibleDialog(page) | |||
| await expect(timeoutDialog.getByRole('heading', { name: '确认超时结算', exact: true })).toBeVisible() | |||
| await expect(timeoutDialog).toContainText('结算后该运行进入待评定状态') | |||
| @@ -359,11 +386,12 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await expect(page.getByText('运行已超时结算,可继续进行评定', { exact: true })).toBeVisible() | |||
| await expect(timeoutDialog).toBeHidden() | |||
| await expect(drawer.locator('.el-loading-mask:visible')).toHaveCount(0) | |||
| expect(contract.last('POST', '/runs/run-timeout/timeout')?.body).toEqual({ version: 2 }) | |||
| expect(contract.runs.get('run-timeout')?.status).toBe('SUBMITTED') | |||
| await expect(drawer.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await drawer.getByRole('button', { name: '评定', exact: true }).click() | |||
| await page.getByRole('button', { name: /学员明细/ }).click() | |||
| details = page.locator('.ttc-assignment-runs') | |||
| await expect(details.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await details.getByRole('button', { name: '评定', exact: true }).click() | |||
| await visibleDialog(page).getByRole('button', { name: '提交评定', exact: true }).click() | |||
| await expect(page.getByText('评定结果已提交', { exact: true })).toBeVisible() | |||
| expect(contract.runs.get('run-timeout')?.status).toBe('REVIEWED') | |||
| @@ -377,11 +405,11 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await contract.install(page) | |||
| await page.goto('/teaching/virtual-training') | |||
| await page.getByRole('button', { name: '查看', exact: true }).click() | |||
| const drawer = page.locator('.el-drawer:visible') | |||
| await expect(drawer.getByRole('button', { name: '超时结算', exact: true })).toHaveCount(0) | |||
| await expect(drawer.getByRole('button', { name: '结束运行', exact: true })).toBeVisible() | |||
| await drawer.getByRole('button', { name: '结束运行', exact: true }).click() | |||
| await page.getByRole('button', { name: /学员明细/ }).click() | |||
| let details = page.locator('.ttc-assignment-runs') | |||
| await expect(details.getByRole('button', { name: '超时结算', exact: true })).toHaveCount(0) | |||
| await expect(details.getByRole('button', { name: '结束运行', exact: true })).toBeVisible() | |||
| await details.getByRole('button', { name: '结束运行', exact: true }).click() | |||
| const terminateDialog = visibleDialog(page) | |||
| await expect(terminateDialog.getByRole('heading', { name: '结束运行', exact: true })).toBeVisible() | |||
| @@ -396,11 +424,12 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await expect(page.getByText('运行已结束并进入待评定状态', { exact: true })).toBeVisible() | |||
| await expect(terminateDialog).toBeHidden() | |||
| await expect(drawer.locator('.el-loading-mask:visible')).toHaveCount(0) | |||
| expect(contract.last('POST', '/runs/run-terminate/terminate')?.body).toEqual({ version: 2, reason }) | |||
| expect(contract.runs.get('run-terminate')?.status).toBe('SUBMITTED') | |||
| await expect(drawer.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await drawer.getByRole('button', { name: '评定', exact: true }).click() | |||
| await page.getByRole('button', { name: /学员明细/ }).click() | |||
| details = page.locator('.ttc-assignment-runs') | |||
| await expect(details.getByRole('button', { name: '评定', exact: true })).toBeVisible() | |||
| await details.getByRole('button', { name: '评定', exact: true }).click() | |||
| await visibleDialog(page).getByRole('button', { name: '提交评定', exact: true }).click() | |||
| await expect(page.getByText('评定结果已提交', { exact: true })).toBeVisible() | |||
| expect(contract.runs.get('run-terminate')?.status).toBe('REVIEWED') | |||
| @@ -588,7 +617,7 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await expect(preflight).toContainText('当前身份仅可查看,无沉浸训练执行权限') | |||
| await expect(preflight).toContainText('不会创建服务端会话,也不会申请浏览器 WebXR 会话') | |||
| const desktopStarts = page.getByRole('button', { name: /只读查看,无法启动/ }) | |||
| await expect(desktopStarts).toHaveCount(2) | |||
| await expect(desktopStarts).toHaveCount(1) | |||
| for (const button of await desktopStarts.all()) await expect(button).toBeDisabled() | |||
| await expect(page.getByRole('button', { name: 'VR 沉浸模式' })).toBeDisabled() | |||
| expect(contract.count('POST', /\/runs\/[^/]+\/xr-sessions$/)).toBe(0) | |||
| @@ -635,8 +664,13 @@ test.describe('教学实施模块状态化 UI 契约', () => { | |||
| await chooseSelectOption(page, page.locator('.teaching-v2-filter-toolbar'), '全部渠道', '虚拟仿真') | |||
| await chooseSelectOption(page, page.locator('.teaching-v2-filter-toolbar'), '全部状态', '已评定') | |||
| const toolbar = page.locator('.teaching-v2-filter-toolbar') | |||
| await toolbar.getByRole('combobox', { name: '开始时间' }).fill('2026-08-01 00:00:00') | |||
| await toolbar.getByRole('combobox', { name: '结束时间' }).fill('2026-08-12 00:00:00') | |||
| const startAtInput = toolbar.getByRole('combobox', { name: '开始时间' }) | |||
| const endAtInput = toolbar.getByRole('combobox', { name: '结束时间' }) | |||
| await startAtInput.fill('2026-08-01 00:00:00') | |||
| await startAtInput.press('Enter') | |||
| await endAtInput.fill('2026-08-12 00:00:00') | |||
| await endAtInput.press('Enter') | |||
| await page.keyboard.press('Escape') | |||
| await page.getByRole('button', { name: '搜索', exact: true }).click() | |||
| await expect.poll(() => contract.count('GET', '/analytics/records')).toBeGreaterThan(1) | |||
| @@ -7,32 +7,57 @@ test.describe('教员只读预览工作台', () => { | |||
| const contract = new TeachingContractMock('admin') | |||
| await contract.install(page) | |||
| await page.goto('/teaching/virtual-training/preview/assignment-virtual-common') | |||
| await page.goto('/teaching/virtual-training/preview/assignment-virtual-preview') | |||
| const workbench = page.locator('.teaching-preview-workbench') | |||
| await expect(workbench).toBeVisible() | |||
| await expect(workbench).toContainText('教员预览 · 不计成绩') | |||
| await expect(workbench.locator('.preview-step-panel')).toContainText('识别作业对象') | |||
| await expect(workbench.locator('.preview-step-panel > header')).toContainText('1 / 8') | |||
| await expect(workbench.locator('.preview-step-panel li')).toHaveCount(8) | |||
| await expect(workbench.locator('.preview-step-panel')).toContainText('卸压确认与故障基线采集') | |||
| await expect(workbench.locator('.preview-step-panel')).toContainText('密封复验与中修闭环') | |||
| await expect(workbench.locator('.preview-runtime-viewport canvas')).toBeVisible() | |||
| await expect(workbench.locator('.preview-digital-teacher')).toContainText('语音服务未接入') | |||
| await expect(workbench.locator('.preview-digital-teacher')).toContainText('点火系统维修教研员') | |||
| await expect(workbench.locator('.preview-digital-teacher')).toContainText('确认压力表回零,记录故障压力和泄漏现象。') | |||
| await expect(workbench.locator('.preview-digital-teacher img')).toHaveJSProperty('complete', true) | |||
| await expect(workbench.locator('iframe')).toHaveCount(0) | |||
| await captureScreenshot(page, testInfo, 'teaching-teacher-preview-initial') | |||
| const writesBefore = contract.requests.filter((request) => ['POST', 'PUT', 'DELETE'].includes(request.method)).length | |||
| await workbench.getByRole('button', { name: '打开在线答疑', exact: true }).click() | |||
| const onlineDrawer = page.locator('.el-drawer:visible').filter({ hasText: '点火系统维修教研员 · 在线答疑' }).last() | |||
| const digitalInstructor = onlineDrawer.getByTestId('digital-instructor-embed') | |||
| await expect(digitalInstructor).toBeVisible() | |||
| const instructorFrame = digitalInstructor.locator('iframe') | |||
| await expect(instructorFrame).toHaveAttribute('src', /\/embed\/dh-b69424ed0904\?open=1$/) | |||
| await expect(instructorFrame).toHaveAttribute('allow', 'autoplay; microphone') | |||
| await expect(digitalInstructor.locator('.digital-instructor-embed__status')).toHaveText('页面已加载') | |||
| await expect(page.frameLocator('[data-testid="digital-instructor-embed"] iframe').getByTestId('mock-ai-person')).toHaveText('虚拟教员在线答疑') | |||
| await expect.poll(() => contract.digitalInstructorFrameLoads).toBe(1) | |||
| expect(contract.requests.filter((request) => ['POST', 'PUT', 'DELETE'].includes(request.method))).toHaveLength(writesBefore) | |||
| await page.keyboard.press('Escape') | |||
| await expect(onlineDrawer).toBeHidden() | |||
| await expect(page.locator('[data-testid="digital-instructor-embed"] iframe')).toHaveCount(0) | |||
| await workbench.getByRole('button', { name: '判定详情', exact: true }).click() | |||
| await expect(workbench.locator('.preview-rule-drawer')).toContainText('IDENTIFY_TARGET') | |||
| await expect(workbench.locator('.preview-rule-drawer')).toContainText('RELEASE_PRESSURE_BASELINE') | |||
| await expect(workbench.locator('.preview-rule-drawer')).toContainText('这里只展示发布快照中的公开字段') | |||
| await workbench.getByRole('button', { name: '规则演示', exact: true }).click() | |||
| await workbench.getByRole('button', { name: '步骤演示', exact: true }).click() | |||
| await workbench.getByRole('button', { name: '引导完成', exact: true }).click() | |||
| await expect(workbench.getByRole('button', { name: /下一步/ })).toBeEnabled() | |||
| await workbench.getByRole('button', { name: /下一步/ }).click() | |||
| await expect(workbench.getByRole('heading', { name: /第 2 步:完成检修操作/ })).toBeVisible() | |||
| await expect(workbench.getByRole('heading', { name: /第 2 步:油管与接头外泄检查/ })).toBeVisible() | |||
| await expect(workbench.locator('.preview-step-panel > header')).toContainText('2 / 8') | |||
| await workbench.getByRole('button', { name: /过程记录/ }).click() | |||
| const drawer = page.locator('.el-drawer:visible') | |||
| const drawer = page.locator('.el-drawer:visible').last() | |||
| await expect(drawer).toContainText('不会提交到教学事件接口') | |||
| await expect(drawer).toContainText('本地试走步骤') | |||
| await expect(drawer).toContainText('规则演示') | |||
| await expect(drawer).toContainText('步骤演示') | |||
| const writesAfter = contract.requests.filter((request) => ['POST', 'PUT', 'DELETE'].includes(request.method)).length | |||
| expect(writesAfter).toBe(writesBefore) | |||