diff --git a/frontend/e2e/smoke-iis.spec.ts b/frontend/e2e/smoke-iis.spec.ts index 3c0e55e..f165d05 100644 --- a/frontend/e2e/smoke-iis.spec.ts +++ b/frontend/e2e/smoke-iis.spec.ts @@ -265,6 +265,219 @@ test.describe('产量报表', () => { // 关闭弹窗 await dialog.locator('.el-dialog__headerbtn').click() }) + + // === 新增:表格所有列验证 === + test('表格所有列有数据(运行时间、切削时间、日状态、修正标记)', async ({ page }) => { + const rows = page.locator('.el-table__body-wrapper .el-table__row') + expect(await rows.count(), '表格应有数据行').toBeGreaterThan(0) + const cells = rows.first().locator('td .cell') + + // 运行时间列(第5列)— 应有数值,不为空 + const runTime = await cells.nth(4).textContent() + assertValidValue(runTime, '运行时间列') + + // 切削时间列(第6列)— 应有数值,不为空 + const cutTime = await cells.nth(5).textContent() + assertValidValue(cutTime, '切削时间列') + + // 日状态列(第7列)— 应为"正常"或"缺失"或"离线" + const status = await cells.nth(6).textContent() + expect(status, '日状态列应有值').toBeTruthy() + const validStatuses = ['正常', '缺失', '离线'] + expect(validStatuses, `日状态应为已知值,实际: ${status?.trim()}`).toContain(status?.trim()) + + // 修正列(第8列)— 应为"✓"或"-" + const adjusted = await cells.nth(7).textContent() + expect(adjusted, '修正列应有值').toBeTruthy() + }) + + // === 新增:分页切换 === + test('分页切换有效', async ({ page }) => { + // 检查分页组件 + const pagination = page.locator('.el-pagination') + await expect(pagination, '分页组件应可见').toBeVisible() + + // 检查总数 + const totalText = await pagination.locator('.el-pagination__total').textContent() + expect(totalText, '分页应显示总数').toContain('条') + + // 如果有多页,切换到第2页 + const page2Btn = pagination.locator('.el-pager > li').nth(1) + if (await page2Btn.isVisible()) { + // 记录第一页第一行机床名 + const row1Before = await page.locator('.el-table__body-wrapper .el-table__row').first().locator('td .cell').nth(1).textContent() + + // 点击第2页 + await page2Btn.click() + await page.waitForTimeout(1500) + + // 验证:第2页高亮 + await expect(page2Btn, '第2页应高亮').toHaveClass(/is-active/) + + // 验证:表格第一行机床名变化了(不同页数据不同) + const row1After = await page.locator('.el-table__body-wrapper .el-table__row').first().locator('td .cell').nth(1).textContent() + expect(row1After, '切页后第一行数据应不同').toBeTruthy() + } + }) + + // === 新增:日期范围筛选 === + test('日期范围筛选生效', async ({ page }) => { + // 记录当前总数 + const totalBefore = await page.locator('.el-pagination__total').textContent() + + // 用JS直接修改日期范围(选一个未来日期确保无数据) + await page.evaluate(() => { + // 找到Vue实例并修改日期 + const inputs = document.querySelectorAll('.el-date-editor input') + if (inputs.length >= 2) { + // 模拟选2099-01-01,一个不可能有数据的日期 + const evt = new Event('input', { bubbles: true }) + ;(inputs[0] as HTMLInputElement).value = '2099-01-01' + inputs[0].dispatchEvent(evt) + ;(inputs[1] as HTMLInputElement).value = '2099-01-01' + inputs[1].dispatchEvent(evt) + } + }) + // 点查询 + await page.locator('button:has-text("查询")').click() + await page.waitForTimeout(2000) + + // 验证:2099年不应该有数据,表格行数应为0或显示空状态 + const rows = page.locator('.el-table__body-wrapper .el-table__row') + const rowCount = await rows.count() + expect(rowCount, '2099年不应该有产量数据').toBe(0) + }) + + // === 新增:程序名文本筛选 === + test('程序名筛选生效', async ({ page }) => { + // 先看第一行的程序名 + const firstProgramCell = page.locator('.el-table__body-wrapper .el-table__row').first().locator('td .cell').nth(2) + const programName = await firstProgramCell.textContent() + assertValidValue(programName, '第一行程序名列') + + // 输入程序名筛选 + const programInput = page.locator('input[placeholder="输入程序名"]') + await programInput.fill(programName!.trim()) + await page.locator('button:has-text("查询")').click() + await page.waitForTimeout(2000) + + // 验证:表格数据应包含该程序名 + const rows = page.locator('.el-table__body-wrapper .el-table__row') + const rowCount = await rows.count() + expect(rowCount, `筛选"${programName?.trim()}"后应有数据`).toBeGreaterThan(0) + + // 验证:每行的程序名列都包含筛选关键词 + for (let i = 0; i < Math.min(rowCount, 5); i++) { + const cell = rows.nth(i).locator('td .cell').nth(2) + const text = await cell.textContent() + expect(text?.trim(), `第${i + 1}行程序名应包含筛选关键词`).toContain(programName!.trim()) + } + }) + + // === 新增:API返回值 vs 页面显示值对账 === + test('汇总卡片数值与API返回一致', async ({ page }) => { + // 通过浏览器fetch调API,拿到实际返回值 + const apiData = await page.evaluate(async () => { + const token = localStorage.getItem('token') + const today = new Date().toISOString().slice(0, 10) + const res = await fetch(`/api/admin/production/daily-summary?date=${today}`, { + headers: { Authorization: `Bearer ${token}` } + }) + const json = await res.json() + return json.data + }) + + expect(apiData, 'API应返回数据').toBeTruthy() + + // 验证总产量卡片 + const totalQtyLabel = page.locator('text=总产量').first() + if (await totalQtyLabel.isVisible()) { + const card = totalQtyLabel.locator('..').locator('..') + const valueDiv = card.locator('div').last() + const displayText = await valueDiv.textContent() + const displayNum = parseInt(displayText!.trim().replace(/,/g, ''), 10) + const apiNum = apiData.totalQuantity + expect(displayNum, `页面总产量${displayNum}应与API返回${apiNum}一致`).toBe(apiNum) + } + + // 验证运行机床卡片 + const machineLabel = page.locator('text=运行机床').first() + if (await machineLabel.isVisible()) { + const card = machineLabel.locator('..').locator('..') + const valueDiv = card.locator('div').last() + const displayText = await valueDiv.textContent() + const displayNum = parseInt(displayText!.trim(), 10) + const apiNum = apiData.activeMachineCount + expect(displayNum, `页面运行机床${displayNum}应与API返回${apiNum}一致`).toBe(apiNum) + } + }) + + // === 新增:下拉框选项值验证(前后端字段映射) === + test('下拉框选项文本和值都有效', async ({ page }) => { + // 展开车间下拉框 + const workshopSelect = page.locator('.el-form-item').filter({ hasText: '车间' }).locator('.el-select') + await workshopSelect.click() + await page.waitForTimeout(500) + + const options = page.locator('.el-select-dropdown__item:visible') + const count = await options.count() + expect(count, '车间下拉框应有选项').toBeGreaterThan(0) + + // 验证每个选项的文本不是空/undefined/null + for (let i = 0; i < Math.min(count, 5); i++) { + const text = await options.nth(i).textContent() + assertValidValue(text, `车间第${i + 1}个选项`) + } + + // 关闭下拉框 + await page.keyboard.press('Escape') + }) + + // === 新增:修正弹窗提交闭环 === + test('修正弹窗提交后列表刷新', async ({ page }) => { + // 获取第一行当前产量 + const firstRow = page.locator('.el-table__body-wrapper .el-table__row').first() + const qtyCell = firstRow.locator('td .cell').nth(3) + const qtyBefore = await qtyCell.textContent() + + // 点击修正按钮 + await firstRow.getByRole('button', { name: '修正' }).click() + await page.waitForTimeout(500) + + const dialog = page.locator('.el-dialog:visible') + await expect(dialog).toBeVisible() + + // 读取当前产量 + const currentInput = dialog.locator('input').first() + const currentValue = await currentInput.inputValue() + expect(currentValue, '当前产量应有值').toBeTruthy() + + // 修改为新值(原值+1) + const newQty = parseInt(currentValue, 10) + 1 + const newQtyInput = dialog.locator('input').nth(1) + // 清空并输入新值 + await newQtyInput.fill(String(newQty)) + + // 填写修正原因 + const reasonTextarea = dialog.locator('textarea') + await reasonTextarea.fill('E2E自动化测试修正') + + // 监听confirm弹窗并自动确认 + page.on('dialog', async dialog => { + await dialog.accept() + }) + + // 点确认修正 + await dialog.locator('button:has-text("确认修正")').click() + await page.waitForTimeout(2000) + + // 验证:弹窗关闭 + await expect(page.locator('.el-dialog:visible'), '修正后弹窗应关闭').toHaveCount(0) + + // 验证:表格刷新了(行数>0说明没崩溃) + const rows = page.locator('.el-table__body-wrapper .el-table__row') + expect(await rows.count(), '修正后表格应有数据').toBeGreaterThan(0) + }) }) // ========================