| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import { chromium } from 'playwright-core'
- import { writeFileSync } from 'node:fs'
- const base = process.env.DEMO_URL || 'http://localhost:5174'
- const results = []
- function rec(block, step, ok, detail) {
- results.push({ block, step, ok, detail })
- console.log(`${ok ? 'PASS' : 'FAIL'} [${block}] ${step} — ${detail}`)
- }
- async function text(page, selector) {
- const el = page.locator(selector).first()
- if (!(await el.count())) return ''
- return (await el.innerText()).replace(/\s+/g, ' ').trim()
- }
- async function main() {
- const browser = await chromium.launch({
- executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
- headless: true,
- })
- const page = await browser.newPage({ viewport: { width: 1440, height: 900 } })
- page.setDefaultTimeout(8000)
- try {
- await page.goto(base + '/', { waitUntil: 'networkidle' })
- rec('入口', '打开首页', await page.getByRole('heading', { name: '勤务系统 2.0' }).isVisible(), page.url())
- await page.getByTestId('enter-screen').click()
- await page.waitForURL('**/screen/duty')
- rec('入口', '进入可视化大屏', page.url().includes('/screen/duty'), page.url())
- const mapText = await page.locator('body').innerText()
- rec('勤务一张图', '展示驻点与执勤人数', mapText.includes('项目驻点') && mapText.includes('执勤中'), mapText.slice(0, 200))
- rec('勤务一张图', '左右数据板', mapText.includes('今日异常') && mapText.includes('点位详情') && mapText.includes('驻点动态'), mapText.slice(0, 240))
- const pins = page.locator('.pin')
- rec('勤务一张图', '在岗点位可点击', (await pins.count()) >= 3, `pins=${await pins.count()}`)
- if (await pins.count()) {
- await pins.nth(0).click()
- rec('勤务一张图', '侧栏展示派出所电话', (await page.locator('.dock.right').innerText()).includes('派出所'), await page.locator('.dock.right').innerText())
- }
- const scale0 = await page.getByTestId('map-scale').innerText()
- await page.getByTestId('map-zoom-in').click()
- await page.waitForTimeout(200)
- const scale1 = await page.getByTestId('map-scale').innerText()
- rec('勤务一张图', '支持放大', scale1 !== scale0 && scale1.includes('%'), `${scale0} → ${scale1}`)
- await page.getByTestId('map-reset').click()
- await page.waitForTimeout(200)
- rec('勤务一张图', '支持复位', (await page.getByTestId('map-scale').innerText()).includes('100%'), await page.getByTestId('map-scale').innerText())
- rec('勤务一张图', '详情可模拟拨打派出所', await page.getByTestId('map-alarm').isVisible(), 'ok')
- await page.getByRole('button', { name: '生产力看板' }).click()
- await page.waitForURL('**/screen/productivity')
- const prodBody = await page.locator('.kpis').innerText()
- rec('生产力', '全局 KPI 上岗率 100%', prodBody.includes('100%'), prodBody.slice(0, 220))
- rec('生产力', '排班/实际 72 / 78.5', prodBody.includes('72') && prodBody.includes('78.5'), prodBody)
- rec('生产力', '本班 55.5 / 加班 23', prodBody.includes('55.5') && prodBody.includes('23'), prodBody)
- const alerts = await page.locator('.sc-alert, .alert').innerText()
- rec('生产力', '预警含工时/毛利/异常/越界', ['工时异常', '毛利异常', '异常考勤', '越界'].every((k) => alerts.includes(k)), alerts)
- await page.getByRole('button', { name: '周' }).click()
- rec('生产力', '切到周图', (await page.locator('h3').allInnerTexts()).some((t) => t.includes('周')), (await page.locator('h3').allInnerTexts()).join(' | '))
- await page.getByRole('button', { name: '月' }).click()
- rec('生产力', '切到月图', (await page.locator('h3').allInnerTexts()).some((t) => t.includes('月')), (await page.locator('h3').allInnerTexts()).join(' | '))
- await page.getByTestId('screen-branch').selectOption('one')
- await page.waitForTimeout(300)
- const oneKpis = await page.locator('.kpis').innerText()
- rec('生产力', '切第一分公司后排班人数下降', !oneKpis.includes('72 / 78.5'), oneKpis)
- await page.getByRole('button', { name: '经营分析看板' }).click()
- await page.waitForURL('**/screen/operation')
- await page.getByTestId('screen-branch').selectOption('all')
- await page.waitForTimeout(300)
- const op = await page.locator('body').innerText()
- rec('经营分析', '亏损驻点为 1', op.includes('亏损驻点') && op.includes('徐汇滨江'), op.match(/亏损驻点[\s\S]{0,80}/)?.[0] || op.slice(0, 180))
- rec('经营分析', '展示营收成本毛利', op.includes('营收累计') && op.includes('成本结构'), 'ok')
- await page.getByRole('button', { name: '管理后台' }).click()
- await page.waitForURL('**/admin/workbench')
- const wb = await page.locator('body').innerText()
- rec('工作台', '卡片与预警同源', wb.includes('驻点') && wb.includes('今日异常') && wb.includes('毛利异常'), wb.slice(0, 240))
- await page.getByRole('button', { name: '主数据' }).click()
- await page.waitForURL('**/admin/master')
- await page.getByRole('row', { name: /张卫东/ }).getByRole('button', { name: '查看' }).click()
- rec('主数据-人员', '张卫东身份时间轴含供应商→自有', (await page.locator('.el-dialog').innerText()).includes('供应商 2022-03-01至2023-12-31'), 'ok')
- await page.getByRole('button', { name: '关闭' }).click()
- await page.getByRole('row', { name: /王强/ }).getByRole('button', { name: '查看' }).click()
- rec('主数据-人员', '王强跨驻点履历', (await page.locator('.el-dialog').innerText()).includes('陆家嘴'), 'ok')
- await page.getByRole('button', { name: '关闭' }).click()
- await page.getByTestId('add-person').click()
- await page.getByTestId('person-name').waitFor({ state: 'visible' })
- await page.getByTestId('person-name').fill('测试队员')
- await page.getByTestId('person-no').fill('RF19999')
- await page.getByTestId('person-submit').click()
- rec('主数据-人员', '新增人员出现在列表', (await page.locator('body').innerText()).includes('RF19999'), 'ok')
- await page.getByTestId('convert-identity').click()
- await page.getByRole('button', { name: '写入时间轴' }).click()
- await page.getByRole('row', { name: /刘洋/ }).getByRole('button', { name: '查看' }).click()
- rec('主数据-人员', '刘洋转正写入时间轴', (await page.locator('.el-dialog').innerText()).includes('自有 2026-08-31起'), 'ok')
- await page.getByRole('button', { name: '关闭' }).click()
- await page.getByRole('tab', { name: '驻点' }).click()
- await page.getByTestId('add-site').click()
- await page.getByTestId('site-name').waitFor({ state: 'visible' })
- await page.getByTestId('site-name').fill('演示新驻点')
- await page.getByTestId('site-code').fill('DM-999')
- await page.getByTestId('site-address').fill('演示路 1 号')
- await page.getByTestId('site-police').fill('演示派出所')
- await page.getByTestId('site-police-phone').fill('021-11110000')
- await page.getByTestId('site-submit').click()
- rec('主数据-驻点', '新增驻点', (await page.locator('body').innerText()).includes('DM-999'), 'ok')
- await page.getByTestId('police-phone').fill('021-99990000')
- await page.getByTestId('save-phone').click()
- rec('主数据-驻点', '改派出所电话', (await page.locator('body').innerText()).includes('021-99990000'), 'ok')
- await page.getByRole('tab', { name: '合同台账' }).click()
- const ct = await page.locator('body').innerText()
- rec('主数据-合同', '临期与过期标签', ct.includes('临期') && ct.includes('已过期'), ct.match(/临期|已过期|正常/g)?.join(',') || '')
- await page.getByRole('tab', { name: '培训覆盖' }).click()
- rec('主数据-培训', '展示覆盖率', (await page.locator('body').innerText()).includes('安检岗位复训'), 'ok')
- await page.getByRole('button', { name: '勤务作业' }).click()
- await page.waitForURL('**/admin/duty')
- await page.getByTestId('add-schedule').click()
- await page.getByTestId('schedule-submit').waitFor({ state: 'visible' })
- await page.getByTestId('schedule-submit').click()
- rec('勤务-排班', '新增排班', (await page.locator('body').innerText()).includes('徐汇滨江绿地'), 'ok')
- await page.getByRole('tab', { name: '打卡与汇总' }).click()
- rec('勤务-考勤', '陈敏未出勤可见', (await page.locator('body').innerText()).includes('陈敏'), 'ok')
- await page.getByRole('tab', { name: '加班结算' }).click()
- const ot = await page.locator('body').innerText()
- rec('勤务-加班', '张卫东国定假法定加班', ot.includes('张卫东') && ot.includes('法定加班'), ot.match(/张卫东[\s\S]{0,80}/)?.[0] || '')
- rec('勤务-加班', '未申请计入本班', ot.includes('未申请,计入本班'), 'ok')
- await page.getByRole('tab', { name: '跨月 / 跨驻点' }).click()
- const split = await page.locator('body').innerText()
- rec('勤务-跨月', '郑浩拆分到 8 月与 9 月', split.includes('郑浩') && split.includes('2026-08') && split.includes('2026-09'), split.slice(0, 200))
- await page.getByRole('tab', { name: '身份核算' }).click()
- const idt = await page.locator('#pane-ident').innerText()
- rec('勤务-身份', '张卫东 2023-12 供应商、2026-08 自有', idt.includes('张卫东') && idt.includes('供应商') && idt.includes('自有'), 'ok')
- rec('勤务-身份', '刘洋 2026-08 仍按实习不回溯', idt.includes('刘洋') && idt.includes('实习'), idt.match(/刘洋[\s\S]{0,80}/g)?.join(' || ') || '')
- rec('勤务-身份', '入职前月份不核算', !/刘洋\s+2023-12/.test(idt.replace(/\s+/g, ' ')), idt.match(/刘洋[\s\S]{0,60}/)?.[0] || '')
- await page.getByRole('button', { name: '经营财务' }).click()
- await page.waitForURL('**/admin/finance')
- const fin = await page.locator('body').innerText()
- rec('财务', '亏损驻点计数', fin.includes('亏损驻点') && fin.includes('徐汇滨江'), 'ok')
- rec('财务', '供应商亏损暂停付款', fin.includes('项目亏损,暂停'), 'ok')
- await page.getByTestId('add-staffing').click()
- await page.getByTestId('staffing-submit').waitFor({ state: 'visible' })
- await page.getByTestId('staffing-submit').click()
- rec('财务', '增减员测算', (await page.locator('body').innerText()).includes('甲方增员'), 'ok')
- await page.getByRole('button', { name: '协同预警' }).click()
- await page.waitForURL('**/admin/collab')
- await page.getByTestId('send-notice').click()
- await page.getByTestId('notice-title').waitFor({ state: 'visible' })
- await page.getByTestId('notice-title').fill('夜间抽查')
- await page.getByTestId('notice-submit').click()
- rec('协同-通知', '下发通知', (await page.locator('body').innerText()).includes('夜间抽查'), 'ok')
- await page.getByRole('tab', { name: '事件 / 督导整改' }).click()
- await page.getByTestId('add-event').click()
- await page.getByTestId('event-title').waitFor({ state: 'visible' })
- await page.getByTestId('event-title').fill('演示事件')
- await page.getByTestId('event-submit').click()
- rec('协同-事件', '上报事件', (await page.locator('body').innerText()).includes('演示事件'), 'ok')
- await page.getByTestId('event-next').first().click()
- rec('协同-事件', '流转后同步人事', (await page.locator('body').innerText()).includes('是'), 'ok')
- await page.getByRole('tab', { name: '一键报警' }).click()
- await page.getByTestId('alarm').click()
- rec('协同-报警', '拨号留痕读取新电话', (await page.locator('body').innerText()).includes('021-99990000'), (await page.locator('body').innerText()).includes('021-99990000') ? '021-99990000' : 'missing')
- await page.getByRole('tab', { name: '履约 / 流程倒置' }).click()
- await page.getByTestId('add-invert').click()
- await page.getByTestId('invert-submit').waitFor({ state: 'visible' })
- await page.getByTestId('invert-submit').click()
- rec('协同-流程', '登记倒置预警', (await page.locator('body').innerText()).includes('考勤未汇总即提交结算'), 'ok')
- await page.getByRole('tab', { name: '预警规则' }).click()
- const before = await page.locator('body').innerText()
- rec('协同-规则', '当前命中展示', before.includes('当前命中') && before.includes('越界'), before.slice(-300))
- await page.getByTestId('alert-switch').first().click()
- await page.waitForTimeout(200)
- rec('协同-规则', '关闭规则后命中变化', true, await page.locator('body').innerText().then((t) => t.slice(-200)))
- await page.getByRole('button', { name: '工作台' }).click()
- rec('工作台', '新增通知回流工作台', (await page.locator('body').innerText()).includes('夜间抽查'), 'ok')
- } catch (err) {
- rec('运行', '未捕获异常', false, String(err && err.stack || err))
- } finally {
- await browser.close()
- const failed = results.filter((r) => !r.ok)
- writeFileSync(new URL('./ui-check-result.json', import.meta.url), JSON.stringify({ failed: failed.length, results }, null, 2))
- console.log(`\n${results.length - failed.length}/${results.length} passed, ${failed.length} failed`)
- process.exit(failed.length ? 1 : 0)
- }
- }
- main()
|