const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); const results = []; function log(category, name, pass, detail) { const status = pass ? '✅' : '❌'; console.log(`${status} [${category}] ${name}: ${detail}`); results.push({ category, name, pass, detail }); } // === 登录 === await page.goto('http://127.0.0.1/admin/login'); await page.waitForTimeout(500); await page.fill('input[type="text"]', 'admin'); await page.fill('input[type="password"]', 'admin123'); await page.click('button:has-text("登录")'); await page.waitForTimeout(2000); // === 导航到产量报表 === await page.goto('http://127.0.0.1/admin/production'); await page.waitForTimeout(3000); // ===================== // 1. 页面基本加载 // ===================== console.log('\n========== 1. 页面基本加载 =========='); const title = await page.title(); log('页面', '页面标题', title.length > 0, `标题: ${title}`); const url = page.url(); log('页面', 'URL正确', url.includes('production'), `URL: ${url}`); // ===================== // 2. 日期选择器 // ===================== console.log('\n========== 2. 日期选择器 =========='); const dateInputs = await page.$$eval('.el-date-editor input', els => els.map(e => e.value)); const today = new Date(); const todayStr = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`; log('日期', '默认日期是今天', dateInputs[0] === todayStr && dateInputs[1] === todayStr, `开始=${dateInputs[0]}, 结束=${dateInputs[1]}, 今天=${todayStr}`); // ===================== // 3. 汇总卡片 // ===================== console.log('\n========== 3. 汇总卡片 =========='); const summaryCards = await page.evaluate(() => { const cards = document.querySelectorAll('.el-card'); const results = []; for (const card of cards) { const text = card.textContent.trim(); if (text.includes('总产量') || text.includes('运行机床') || text.includes('切削总时') || text.includes('平均产量')) { results.push(text.replace(/\n/g, ' ').substring(0, 80)); } } return results; }); log('汇总', '总产量卡片有数据', summaryCards.some(c => c.includes('总产量')), summaryCards.filter(c => c.includes('总产量')).join(' | ') || '未找到'); log('汇总', '运行机床卡片', summaryCards.some(c => c.includes('运行机床')), summaryCards.filter(c => c.includes('运行机床')).join(' | ') || '未找到'); log('汇总', '切削总时卡片', summaryCards.some(c => c.includes('切削总时')), summaryCards.filter(c => c.includes('切削总时')).join(' | ') || '未找到'); log('汇总', '平均产量卡片', summaryCards.some(c => c.includes('平均产量')), summaryCards.filter(c => c.includes('平均产量')).join(' | ') || '未找到'); // ===================== // 4. 筛选控件 // ===================== console.log('\n========== 4. 筛选控件 =========='); // 车间下拉 const workshopOptions = await page.evaluate(() => { const sel = document.querySelectorAll('.el-select'); // 第一个是车间 return sel.length; }); log('筛选', '下拉控件存在', workshopOptions >= 3, `找到${workshopOptions}个下拉`); // 查询按钮 const queryBtn = await page.$('button:has-text("查询")'); log('筛选', '查询按钮存在', queryBtn !== null, queryBtn ? '存在' : '不存在'); // 重置按钮 const resetBtn = await page.$('button:has-text("重置")'); log('筛选', '重置按钮存在', resetBtn !== null, resetBtn ? '存在' : '不存在'); // ===================== // 5. 数据表格 // ===================== console.log('\n========== 5. 数据表格 =========='); const tableHeaders = await page.$$eval('.el-table__header th .cell', els => els.map(e => e.textContent.trim())); log('表格', '列头完整', tableHeaders.length >= 7, `列头: ${tableHeaders.join(', ')}`); const expectedHeaders = ['日期', '机床', '程序名', '产量', '运行时间', '切削时间', '日状态']; expectedHeaders.forEach(h => { log('表格', `列头含"${h}"`, tableHeaders.includes(h), tableHeaders.includes(h) ? '存在' : `缺失! 现有: ${tableHeaders.join(',')}`); }); // 检查表格数据 const tableRows = await page.$$eval('.el-table__body tr', trs => trs.slice(0, 5).map(tr => { const cells = tr.querySelectorAll('td .cell'); return Array.from(cells).map(c => c.textContent.trim()); }) ); log('表格', '有数据行', tableRows.length > 0, `${tableRows.length}行`); if (tableRows.length > 0) { // 检查每列是否有数据 const dateCol = tableRows.map(r => r[0]).filter(v => v && v !== ''); log('表格', '日期列有数据', dateCol.length > 0, `${dateCol.length}/${tableRows.length}行有日期, 样例: ${dateCol[0]}`); const machineCol = tableRows.map(r => r[1]).filter(v => v && v !== ''); log('表格', '机床列有数据', machineCol.length > 0, `${machineCol.length}/${tableRows.length}行有机床, 样例: ${machineCol[0]}`); const programCol = tableRows.map(r => r[2]).filter(v => v && v !== ''); log('表格', '程序名列有数据', programCol.length > 0, `${programCol.length}/${tableRows.length}行有程序名, 样例: ${programCol[0]}`); const qtyCol = tableRows.map(r => r[3]).filter(v => v && v !== '' && v !== '-'); log('表格', '产量列有数据', qtyCol.length > 0, `${qtyCol.length}/${tableRows.length}行有产量, 样例: ${qtyCol.slice(0, 3).join(',')}`); const statusCol = tableRows.map(r => r[6]).filter(v => v && v !== ''); log('表格', '日状态列有数据', statusCol.length > 0, `${statusCol.length}/${tableRows.length}行有状态, 样例: ${statusCol.slice(0, 3).join(',')}`); // 打印前3行完整数据 console.log('\n 前3行完整数据:'); tableRows.slice(0, 3).forEach((row, i) => console.log(` 行${i+1}: ${JSON.stringify(row)}`)); } // ===================== // 6. 分页 // ===================== console.log('\n========== 6. 分页 =========='); const pagination = await page.$('.el-pagination'); log('分页', '分页组件存在', pagination !== null, pagination ? '存在' : '不存在'); const totalText = await page.evaluate(() => { const total = document.querySelector('.el-pagination__total'); return total ? total.textContent.trim() : '未找到'; }); log('分页', '总数显示', totalText !== '未找到', totalText); // ===================== // 7. 操作按钮 // ===================== console.log('\n========== 7. 操作按钮 =========='); const adjustBtns = await page.$$('button:has-text("修正")'); log('操作', '修正按钮存在', adjustBtns.length > 0, `${adjustBtns.length}个修正按钮`); const historyBtns = await page.$$('button:has-text("修正历史")'); log('操作', '修正历史按钮存在', historyBtns.length > 0, `${historyBtns.length}个修正历史按钮`); // ===================== // 8. 交互测试:点击修正 // ===================== console.log('\n========== 8. 交互测试:修正 =========='); if (adjustBtns.length > 0) { await adjustBtns[0].click(); await page.waitForTimeout(1000); const dialog = await page.$('.el-dialog'); const dialogVisible = dialog && await dialog.isVisible(); log('交互', '点击修正弹出弹窗', dialogVisible, dialogVisible ? '弹窗可见' : '弹窗不可见'); if (dialogVisible) { const dialogTitle = await page.evaluate(() => { const t = document.querySelector('.el-dialog__title'); return t ? t.textContent.trim() : '无标题'; }); log('交互', '弹窗标题', true, dialogTitle); // 检查弹窗内的表单元素 const dialogInputs = await page.$$eval('.el-dialog input', els => els.map(e => ({ type: e.type, placeholder: e.placeholder, value: e.value }))); log('交互', '弹窗表单元素', dialogInputs.length > 0, `${JSON.stringify(dialogInputs)}`); // 关闭弹窗 const closeBtn = await page.$('.el-dialog__headerbtn'); if (closeBtn) { await closeBtn.click(); await page.waitForTimeout(500); } } } // ===================== // 9. 交互测试:点击修正历史 // ===================== console.log('\n========== 9. 交互测试:修正历史 =========='); if (historyBtns.length > 0) { await historyBtns[0].click(); await page.waitForTimeout(1000); const dialog = await page.$('.el-dialog'); const dialogVisible = dialog && await dialog.isVisible(); log('交互', '点击修正历史弹出弹窗', dialogVisible, dialogVisible ? '弹窗可见' : '弹窗不可见'); if (dialogVisible) { const dialogTitle = await page.evaluate(() => { const t = document.querySelector('.el-dialog__title'); return t ? t.textContent.trim() : '无标题'; }); log('交互', '弹窗标题', true, dialogTitle); // 关闭 const closeBtn = await page.$('.el-dialog__headerbtn'); if (closeBtn) { await closeBtn.click(); await page.waitForTimeout(500); } } } // ===================== // 10. 交互测试:重置按钮 // ===================== console.log('\n========== 10. 交互测试:重置 =========='); if (resetBtn) { await resetBtn.click(); await page.waitForTimeout(3000); const dateAfterReset = await page.$$eval('.el-date-editor input', els => els.map(e => e.value)); log('交互', '重置后日期变化', true, `重置后: ${dateAfterReset.join(' - ')}`); const rowsAfterReset = await page.$$eval('.el-table__body tr', trs => trs.length); log('交互', '重置后有数据', rowsAfterReset > 0, `${rowsAfterReset}行`); } // ===================== // 11. 交互测试:查询按钮 // ===================== console.log('\n========== 11. 交互测试:查询 =========='); if (queryBtn) { await queryBtn.click(); await page.waitForTimeout(3000); const rowsAfterQuery = await page.$$eval('.el-table__body tr', trs => trs.length); log('交互', '查询后有数据', rowsAfterQuery > 0, `${rowsAfterQuery}行`); } // ===================== // 12. 交互测试:分页切换 // // ===================== console.log('\n========== 12. 交互测试:分页 =========='); const nextBtn = await page.$('.el-pagination .btn-next'); if (nextBtn) { const isEnabled = await nextBtn.isEnabled(); if (isEnabled) { await nextBtn.click(); await page.waitForTimeout(2000); const page2Rows = await page.$$eval('.el-table__body tr', trs => trs.length); log('交互', '翻页后有数据', page2Rows > 0, `第2页${page2Rows}行`); } else { log('交互', '翻页', false, '下一页按钮不可用(可能只有1页)'); } } // ===================== // 汇总 // ===================== console.log('\n========================================'); const passed = results.filter(r => r.pass).length; const failed = results.filter(r => !r.pass).length; console.log(`总计: ${results.length}项, 通过: ${passed}, 失败: ${failed}`); if (failed > 0) { console.log('\n失败项:'); results.filter(r => !r.pass).forEach(r => console.log(` ❌ [${r.category}] ${r.name}: ${r.detail}`)); } // 截图 await page.screenshot({ path: 'test-screenshots/production-full-test.png', fullPage: true }); console.log('\n截图已保存'); await browser.close(); })();