feat(前端): 配套授权、监控与同步进度页面
- 角色授权:权限矩阵抽屉与角色创建,按模块分组勾选 - 系统监控:总览、趋势、接口与事件页,15 秒自动刷新, 请求可中断、窗口切换暂停,详情抽屉支持导出 - T+ 同步:同步进度条组件与工作台状态拆分 - 工资:试用期面板、产品提取弹窗与无权限占位页 - 路由权限:按权限码控制菜单与页面可达性 影响范围:前端全部页面 验证:随本次后端联调本地人工核对,e2e 脚本入库待跑
This commit is contained in:
1 parent
1461a8d113
commit
a2f9a58de5
74 files changed
+3025
-576
No files matched your search
@@ -0,0 +1,126 @@
|
||||
/** 角色授权界面验收;合成数据只验证交互,不改真实账号或权限。 */
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
const require = createRequire(import.meta.url)
|
||||
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'C:/Users/Administrator/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
|
||||
const base = process.env.AUTH_UI_URL || 'http://127.0.0.1:5173'
|
||||
const output = process.env.AUTH_UI_OUTPUT || '.ai'
|
||||
const browser = await chromium.launch({ headless: true, channel: 'msedge' })
|
||||
const page = await browser.newPage({ viewport: { width: 1366, height: 1000 } })
|
||||
const checks = [], errors = [], saves = []
|
||||
let revision = 1, failPreview = false, conflict = false
|
||||
const names = { view: '查看', create: '新增', update: '编辑', regions: '维护区域', plans: '维护计划', prepare: '准备核算资料', calculate: '核算与重算', allocation: '修改工资分配', generate: '生成工资', export: '导出' }
|
||||
const definitions = [
|
||||
['system:users', '系统管理', '用户管理', ['view', 'create', 'update']],
|
||||
['system:roles', '系统管理', '角色管理', ['view', 'create', 'update']],
|
||||
['base:personnel', '基础资料', '人员管理', ['view', 'create', 'update', 'regions', 'plans']],
|
||||
['payroll', '绩效工资', '正式核算与分配', ['view', 'prepare', 'calculate', 'allocation']],
|
||||
['settlements', '绩效工资', '月度工资', ['view', 'generate']],
|
||||
['reports:business', '报表统计', '业务统计', ['view', 'export']],
|
||||
]
|
||||
const catalog = definitions.flatMap(([module, group, module_name, actions]) => actions.map(action => ({ code: `${module}:${action}`, module, group, module_name, name: names[action], action, view_code: `${module}:view`, admin_only: module.startsWith('system:'), maker_required: false })))
|
||||
const roleCodes = { admin: catalog.map(item => item.code), finance: ['payroll:view'], payroll_maker: ['payroll:view', 'payroll:calculate', 'settlements:view', 'settlements:generate'] }
|
||||
const roles = [{ id: 1, role_code: 'admin', role_name: '系统管理员', description: '系统管理', status: 1, user_count: 1 }, { id: 2, role_code: 'finance', role_name: '财务', status: 1, user_count: 1 }, { id: 3, role_code: 'payroll_maker', role_name: '工资经办', status: 1, user_count: 0 }]
|
||||
let assigned = ['finance']
|
||||
function effective(codes) { return [...new Set(codes.flatMap(code => roleCodes[code] ?? []))] }
|
||||
function result(codes) { const permissions = effective(codes); return { permissions, roles: codes, sources: Object.fromEntries(catalog.map(item => [item.code, { roles: codes.filter(code => roleCodes[code]?.includes(item.code)), override: null, effective: permissions.includes(item.code) }])) } }
|
||||
await page.addInitScript(() => localStorage.setItem('ys_token', 'synthetic-rbac-ui'))
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.route(url => url.pathname.startsWith('/api/'), async route => {
|
||||
const request = route.request(), url = new URL(request.url()), path = url.pathname, method = request.method()
|
||||
let data = {}, status = 200
|
||||
if (path.endsWith('/auth/me')) data = { id: 1, username: 'synthetic-admin', real_name: '授权界面演练', roles: ['admin'], permissions: roleCodes.admin, can_write_payroll: true, is_payroll_maker: true, maker_required_permissions: [] }
|
||||
else if (path.endsWith('/permissions/catalog')) data = catalog
|
||||
else if (path.endsWith('/system/roles')) data = { list: roles.map(item => ({ ...item, permission_count: roleCodes[item.role_code].length })), total: roles.length, pageNum: 1, pageSize: 200 }
|
||||
else if (path.endsWith('/users/meta/roles')) data = roles
|
||||
else if (/\/roles\/\d+\/permissions$/.test(path)) {
|
||||
const id = Number(path.match(/roles\/(\d+)/)[1]), item = roles.find(role => role.id === id)
|
||||
if (method === 'PUT') {
|
||||
const body = request.postDataJSON(); saves.push({ path, body })
|
||||
if (conflict || body.expected_revision !== revision) status = 409
|
||||
else { roleCodes[item.role_code] = body.permission_codes; revision++ }
|
||||
}
|
||||
data = { role_id: id, role_code: item.role_code, role_name: item.role_name, permission_codes: roleCodes[item.role_code], fixed: id === 1, revision }
|
||||
} else if (path.endsWith('/system/users')) data = { list: [{ id: 7, username: 'synthetic-user', real_name: '角色继承演练', roles: assigned, status: 1 }], total: 1, pageNum: 1, pageSize: 20 }
|
||||
else if (path.endsWith('/users/7/authorization/preview')) {
|
||||
if (failPreview) status = 503
|
||||
data = result(request.postDataJSON().role_codes)
|
||||
} else if (path.endsWith('/users/7/authorization')) {
|
||||
if (method === 'PUT') { const body = request.postDataJSON(); saves.push({ path, body }); assigned = body.role_codes; revision++ }
|
||||
data = { ...result(assigned), id: 7, username: 'synthetic-user', role_codes: assigned, overrides: [], revision, is_payroll_maker: true }
|
||||
}
|
||||
return route.fulfill({ status, json: { code: status, message: status === 409 ? '权限版本已更新' : status === 503 ? '合成预览失败' : 'success', data: status === 200 ? data : null } })
|
||||
})
|
||||
|
||||
try {
|
||||
await page.goto(`${base}/system/roles`)
|
||||
await page.getByRole('button', { name: '查看权限', exact: true }).click()
|
||||
await page.getByText('系统管理员拥有全部权限,仅供查看', { exact: true }).waitFor()
|
||||
assert.equal(await page.getByRole('button', { name: '保存角色权限', exact: true }).count(), 0)
|
||||
assert.equal(await page.locator('.permission-drawer .el-table').count(), 0)
|
||||
for (const width of [1366, 1920, 768]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
await page.screenshot({ path: `${output}/rbac-admin-${width}.png`, fullPage: true })
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
|
||||
}
|
||||
checks.push('管理员固定只读、模块分组、1366/1920/768 布局无横向溢出')
|
||||
await page.getByRole('button', { name: '关闭', exact: true }).click()
|
||||
await page.setViewportSize({ width: 1366, height: 1000 })
|
||||
await page.getByRole('row').filter({ hasText: '财务' }).getByRole('button', { name: '配置权限', exact: true }).click()
|
||||
await page.getByRole('navigation', { name: '权限业务分组' }).getByRole('button', { name: /^绩效工资/ }).click()
|
||||
await page.getByText('核算与重算', { exact: true }).click()
|
||||
assert.ok(await page.getByRole('checkbox', { name: '正式核算与分配 查看', exact: true }).isChecked())
|
||||
assert.ok(await page.getByRole('checkbox', { name: '正式核算与分配 核算与重算', exact: true }).isChecked())
|
||||
const moduleAll = page.getByRole('checkbox', { name: '正式核算与分配模块全选', exact: true })
|
||||
assert.equal(await moduleAll.evaluate(element => element.indeterminate), true)
|
||||
await page.getByLabel('正式核算与分配权限', { exact: true }).getByText('全选', { exact: true }).click()
|
||||
assert.ok(await page.getByRole('checkbox', { name: '正式核算与分配 修改工资分配', exact: true }).isChecked())
|
||||
await page.getByRole('textbox', { name: '搜索权限', exact: true }).fill('人员')
|
||||
await page.getByRole('checkbox', { name: '人员管理模块全选', exact: true }).locator('xpath=ancestor::label[1]').click()
|
||||
await page.getByRole('textbox', { name: '搜索权限', exact: true }).fill('不存在的模块')
|
||||
await page.getByText('没有匹配的权限', { exact: true }).waitFor()
|
||||
await page.getByRole('textbox', { name: '搜索权限', exact: true }).fill('')
|
||||
await page.getByRole('textbox', { name: '授权修改原因', exact: true }).fill('合成验收:财务经办职责调整')
|
||||
await page.screenshot({ path: `${output}/rbac-role-editor.png`, fullPage: true })
|
||||
await page.getByRole('button', { name: '保存角色权限', exact: true }).click()
|
||||
await page.getByRole('dialog').waitFor({ state: 'hidden' })
|
||||
assert.ok(saves[0].body.permission_codes.includes('payroll:view') && saves[0].body.permission_codes.includes('payroll:calculate'))
|
||||
checks.push('操作联动查看、模块全选与半选、搜索空态、原因必填及保存实际载荷')
|
||||
await page.goto(`${base}/system/users`)
|
||||
await page.getByRole('button', { name: '分配角色', exact: true }).click()
|
||||
await page.getByRole('heading', { name: '分配角色', exact: true }).waitFor()
|
||||
assert.equal(await page.getByText('个人设置', { exact: true }).count(), 0)
|
||||
assert.equal(await page.getByText('额外允许', { exact: true }).count(), 0)
|
||||
await page.getByRole('checkbox', { name: '分配工资经办角色', exact: true }).locator('xpath=ancestor::label[1]').click()
|
||||
await page.getByText('已选:财务、工资经办', { exact: false }).waitFor()
|
||||
await page.getByRole('textbox', { name: '授权修改原因', exact: true }).fill('合成验收:增加工资经办角色')
|
||||
await page.screenshot({ path: `${output}/rbac-user-role-assignment.png`, fullPage: true })
|
||||
await page.getByRole('button', { name: '保存角色分配', exact: true }).click()
|
||||
await page.getByRole('dialog').waitFor({ state: 'hidden' })
|
||||
assert.deepEqual(saves.at(-1).body.role_codes, ['finance', 'payroll_maker'])
|
||||
assert.deepEqual(saves.at(-1).body.overrides, [])
|
||||
checks.push('用户只分配角色、继承权限只读预览、无个人权限覆盖入口')
|
||||
await page.getByRole('button', { name: '分配角色', exact: true }).click()
|
||||
failPreview = true
|
||||
await page.getByRole('checkbox', { name: '分配工资经办角色', exact: true }).locator('xpath=ancestor::label[1]').click()
|
||||
await page.getByText('继承权限预览失败,请重试后再保存。', { exact: true }).waitFor()
|
||||
assert.ok(await page.getByRole('button', { name: '保存角色分配', exact: true }).isDisabled())
|
||||
failPreview = false
|
||||
await page.getByRole('button', { name: '重试预览', exact: true }).click()
|
||||
await page.getByRole('textbox', { name: '授权修改原因', exact: true }).fill('角色变更演练')
|
||||
await page.getByRole('button', { name: '取消', exact: true }).click()
|
||||
await page.getByRole('button', { name: '放弃修改', exact: true }).click()
|
||||
checks.push('预览失败禁止保存、重试恢复及未保存修改关闭保护')
|
||||
await page.goto(`${base}/system/authorization`)
|
||||
await page.waitForURL('**/system/roles')
|
||||
assert.equal(await page.getByText('权限与工资制作人', { exact: true }).count(), 0)
|
||||
checks.push('旧授权页跳转角色管理,导航无重复入口')
|
||||
assert.deepEqual(errors, [])
|
||||
fs.writeFileSync(`${output}/rbac-browser-result.json`, JSON.stringify({ checks, errors, saves: saves.length }, null, 2))
|
||||
console.log(JSON.stringify({ checks, errors, saves: saves.length }))
|
||||
} catch (error) {
|
||||
await page.screenshot({ path: `${output}/rbac-browser-failure.png`, fullPage: true })
|
||||
fs.writeFileSync(`${output}/rbac-browser-failure.txt`, String(error) + '\n' + await page.locator('body').innerText())
|
||||
throw error
|
||||
} finally { await browser.close() }
|
||||
@@ -259,8 +259,8 @@ def step_version_filter(page: Page, ctx: dict) -> None:
|
||||
def step_generate_settlement(page: Page, ctx: dict) -> None:
|
||||
page.goto(f"{BASE_URL}/settlements", wait_until="networkidle")
|
||||
page.wait_for_timeout(600)
|
||||
page.locator('input[placeholder="结算月份 YYYY-MM"]').fill(ctx["month"])
|
||||
page.click("button:has-text('生成月结快照')")
|
||||
page.locator('input[placeholder="选择结算月份"]').fill(ctx["month"])
|
||||
page.click("button:has-text('生成月度工资')")
|
||||
expect_toast(page, "月结已生成", "生成月结")
|
||||
|
||||
row = page.locator(".el-table__row", has_text=ctx["month"]).first
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/** 合成数据验证监控交互和故障状态,不登录真实账号、不写业务数据。 */
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'C:/Users/Administrator/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
|
||||
const base = process.env.MONITOR_UI_URL || 'http://127.0.0.1:5193'
|
||||
const output = process.env.MONITOR_UI_OUTPUT || '.ai'
|
||||
fs.mkdirSync(output, { recursive: true })
|
||||
const browser = await chromium.launch({ headless: true, channel: 'msedge' })
|
||||
const context = await browser.newContext({ viewport: { width: 1366, height: 1050 }, permissions: ['clipboard-read', 'clipboard-write'] })
|
||||
const page = await context.newPage()
|
||||
const errors = [], requests = [], checks = []
|
||||
let failed = false, role = 'admin', empty = false
|
||||
const at = '2026-11-02T10:00:00', id = 'a'.repeat(32)
|
||||
const metric = { count: 48, errors_5xx: 3, errors_4xx: 2, error_rate: 6.25, p95_ms: 1200, max_ms: 2600, last_error_at: at }
|
||||
const incident = { id: 1, instance_id: 'synthetic-instance', kind: 'HEALTH', code: 'WORKER_OFFLINE', component: 'tplus', level: 'warning', message: '采集进程心跳中断,请检查托管任务', request_id: null, method: null, route: null, status_code: null, duration_ms: null, batch_no: 'SYNTHETIC-BATCH', first_seen: at, last_seen: at, occurrences: 4, resolved_at: null }
|
||||
const event = { ...incident, id: 2, kind: 'REQUEST_ERROR', code: 'HTTP_5XX', component: 'http', level: 'error', message: '接口处理异常,请按请求编号检索服务端日志。', request_id: id, method: 'GET', route: '/api/v1/base/products/{id}', status_code: 500, duration_ms: 2600, occurrences: 1, batch_no: null }
|
||||
page.on('pageerror', error => errors.push(error.message))
|
||||
await page.clock.install({ time: new Date('2026-11-02T10:00:00+08:00') })
|
||||
await page.addInitScript(() => localStorage.setItem('ys_token', 'synthetic-monitor-browser-test'))
|
||||
await page.route(url => url.pathname.startsWith('/api/'), async route => {
|
||||
const request = route.request(), url = new URL(request.url()), path = url.pathname
|
||||
requests.push({ path, query: Object.fromEntries(url.searchParams) })
|
||||
assert.equal(request.method(), 'GET')
|
||||
if (path.endsWith('/auth/me')) return route.fulfill({ json: { code: 200, message: 'success', data: { id: 1, username: 'synthetic', real_name: '监控页面验证', roles: [role], permissions: role === 'admin' ? ['system:monitor:view', 'tplus:view', 'dashboard:view'] : ['base:products:view'], is_payroll_maker: false, can_write_payroll: false, payroll_approval_enabled: false } } })
|
||||
if (path.includes('/monitor/') && failed) return route.fulfill({ status: 503, json: { code: 503, message: '合成连接故障', data: null } })
|
||||
let data = {}
|
||||
if (path.endsWith('/monitor/overview')) data = {
|
||||
status: empty ? 'unknown' : 'warning', environment: '合成演练', version: '0.1.0', instance_id: 'synthetic-instance', uptime_seconds: 12345,
|
||||
collected_at: empty ? null : at, last_persisted_at: empty ? null : at, collection_status: empty ? 'unknown' : 'normal', collection_message: empty ? '等待首次采集' : '采集正常', dropped_events: 0,
|
||||
resources: empty ? {} : { cpu_percent: 32.8, memory_percent: 48.2, disk_percent: 55.4, disk_free_gb: 124.5, process_cpu_percent: 2.3, process_memory_mb: 185, scope: '合成数据,仅用于页面验证' },
|
||||
requests: empty ? { count: 0, error_rate: 0, p95_ms: null, max_ms: 0, errors_4xx: 0, errors_5xx: 0, last_error_at: null } : metric,
|
||||
dependencies: empty ? [] : ['应用', 'MySQL', 'Redis', 'T+ 采集进程'].map(name => ({ name, status: name.startsWith('T+') ? 'warning' : 'normal', message: name.startsWith('T+') ? '心跳中断,请查看同步任务。' : '连接正常。', checked_at: at, duration_ms: 2 })),
|
||||
alerts: empty ? [] : [incident], tasks: { status: 'warning', message: '存在采集异常,请查看相关任务。', enabled: true, worker_phase: 'STOPPED', heartbeat_at: at, last_success_at: at, next_retry_at: at, alerts: [{ code: 'WORKER_OFFLINE', message: incident.message, batch_no: incident.batch_no }] },
|
||||
}
|
||||
else if (path.endsWith('/monitor/trends')) data = { step_minutes: 1, points: empty ? [] : Array.from({ length: 60 }, (_, i) => ({ at: `2026-11-02T09:${String(i).padStart(2, '0')}:00`, sample_count: i === 25 ? 0 : 4, cpu_percent: i === 25 ? null : 25 + i % 13, memory_percent: i === 25 ? null : 42 + i % 8, disk_percent: 55, request_count: i === 25 ? null : i % 9 + 1, error_rate: i === 25 ? null : i % 17 === 0 ? 5 : 0, p95_ms: i === 25 ? null : 100 + i % 7 * 100 })) }
|
||||
else if (path.endsWith('/monitor/interfaces')) data = { list: empty ? [] : [{ method: event.method, route: event.route, ...metric }], truncated: false }
|
||||
else if (path.endsWith('/monitor/events')) data = { list: empty ? [] : [event], total: empty ? 0 : 1, pageNum: 1, pageSize: 20 }
|
||||
else if (/\/monitor\/events\/\d+$/.test(path)) data = path.endsWith('/1') ? incident : event
|
||||
else if (path.endsWith('/monitor/diagnostic')) return route.fulfill({ headers: { 'content-disposition': 'attachment; filename="monitor.json"' }, json: { generated_at: at, note: '脱敏诊断摘要' } })
|
||||
else if (path.endsWith('/tplus/overview')) data = { is_configured: false, configuration_message: '合成任务定位验证', status_counts: {}, total_staged_records: 0, latest_audit: null }
|
||||
else if (path.endsWith('/tplus/batches/SYNTHETIC-BATCH')) data = { batch_no: 'SYNTHETIC-BATCH', status: 'FAILED', task_type: 'READ', create_time: at, can_retry: false }
|
||||
else data = { list: [], total: 0, pageNum: 1, pageSize: 20 }
|
||||
return route.fulfill({ json: { code: 200, message: 'success', data } })
|
||||
})
|
||||
|
||||
try {
|
||||
await page.goto(`${base}/system/monitor`)
|
||||
await page.getByRole('heading', { name: '系统监控', exact: true }).waitFor()
|
||||
await page.getByText('合成演练', { exact: true }).waitFor()
|
||||
await page.getByText(incident.message, { exact: true }).waitFor()
|
||||
await page.clock.runFor(600)
|
||||
for (const width of [1366, 1920, 768]) {
|
||||
await page.setViewportSize({ width, height: 1050 })
|
||||
await page.screenshot({ path: `${output}/monitor-ui-${width}.png`, fullPage: true })
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false, `页面横向溢出 ${width}`)
|
||||
}
|
||||
checks.push('1366/1920/768 布局、真实图表渲染与无横向溢出')
|
||||
await page.setViewportSize({ width: 1366, height: 1050 })
|
||||
await page.getByRole('tab', { name: '接口性能', exact: true }).click()
|
||||
await page.getByRole('button', { name: '查看异常', exact: true }).click()
|
||||
await page.getByRole('button', { name: '详情', exact: true }).click()
|
||||
await page.getByRole('heading', { name: '诊断事件详情', exact: true }).waitFor()
|
||||
await page.getByRole('button', { name: '复制请求编号', exact: true }).click()
|
||||
assert.equal(await page.evaluate(() => navigator.clipboard.readText()), id)
|
||||
await page.screenshot({ path: `${output}/monitor-event-detail.png`, fullPage: true })
|
||||
await page.getByRole('button', { name: '关闭此对话框' }).click()
|
||||
checks.push('接口排行联动异常列表、抽屉详情与请求编号复制')
|
||||
await page.getByRole('button', { name: '诊断摘要', exact: true }).click()
|
||||
await page.getByRole('tab', { name: '任务与同步', exact: true }).click()
|
||||
await page.getByRole('button', { name: '查看相关批次', exact: true }).click()
|
||||
await page.waitForURL('**/system/tplus?batch=SYNTHETIC-BATCH')
|
||||
await page.getByRole('dialog').waitFor()
|
||||
checks.push('诊断摘要下载、T+ 批次跳转与原详情打开')
|
||||
await page.goto(`${base}/system/monitor`)
|
||||
await page.getByText('合成演练', { exact: true }).waitFor()
|
||||
await Promise.all([page.waitForResponse(response => response.url().includes('/monitor/trends') && response.url().includes('hours=24')),
|
||||
page.getByText('24 小时', { exact: true }).click()])
|
||||
checks.push('历史时间窗口筛选')
|
||||
failed = true
|
||||
await page.getByRole('button', { name: '刷新', exact: true }).click()
|
||||
await page.getByText('监控连接失败,请检查服务或稍后刷新。', { exact: true }).waitFor()
|
||||
assert.equal(await page.locator('.el-message--error').count(), 0, '轮询故障不应重复弹窗')
|
||||
assert.ok(await page.getByText('合成演练', { exact: true }).isVisible(), '应保留上次成功状态')
|
||||
checks.push('故障保留上次状态、明确未知且不弹窗轰炸')
|
||||
failed = false; empty = true
|
||||
await page.getByRole('button', { name: '刷新', exact: true }).click()
|
||||
await page.getByText('当前状态尚不能确认', { exact: true }).waitFor()
|
||||
assert.equal(await page.getByText('当前未发现活动异常', { exact: true }).count(), 0)
|
||||
checks.push('初次无数据与正常状态明确区分')
|
||||
const count = () => requests.filter(item => item.path.endsWith('/monitor/overview')).length
|
||||
await page.locator('.toolbar .el-switch').click()
|
||||
assert.equal(await page.getByRole('switch', { name: '自动刷新监控' }).isChecked(), false)
|
||||
const disabledCount = count()
|
||||
await page.clock.runFor(31000)
|
||||
assert.equal(count(), disabledCount)
|
||||
await page.locator('.toolbar .el-switch').click()
|
||||
await page.evaluate(() => { Object.defineProperty(document, 'hidden', { configurable: true, get: () => true }); document.dispatchEvent(new Event('visibilitychange')) })
|
||||
const hiddenCount = count()
|
||||
await page.clock.runFor(46000)
|
||||
assert.equal(count(), hiddenCount)
|
||||
await page.evaluate(() => { Object.defineProperty(document, 'hidden', { configurable: true, get: () => false }); document.dispatchEvent(new Event('visibilitychange')) })
|
||||
const resumed = page.waitForRequest(request => request.url().includes('/monitor/overview'))
|
||||
await page.clock.runFor(16000); await resumed
|
||||
checks.push('关闭自动刷新、隐藏页面暂停、恢复可见后继续采集视图')
|
||||
await page.getByRole('menuitem', { name: 'T+ 数据同步', exact: true }).click()
|
||||
const leftCount = count()
|
||||
await page.clock.runFor(46000)
|
||||
assert.equal(count(), leftCount)
|
||||
checks.push('离开监控页停止轮询')
|
||||
role = 'user'
|
||||
await page.goto(`${base}/system/monitor`)
|
||||
await page.waitForURL('**/base/products')
|
||||
assert.equal(await page.getByRole('heading', { name: '系统监控', exact: true }).count(), 0)
|
||||
checks.push('普通用户直接访问被拒绝,菜单不暴露监控入口')
|
||||
assert.deepEqual(errors, [])
|
||||
fs.writeFileSync(`${output}/monitor-browser-result.json`, JSON.stringify({ checks, errors, requests: requests.length }, null, 2))
|
||||
console.log(JSON.stringify({ checks, errors, requests: requests.length }))
|
||||
} catch (error) {
|
||||
await page.screenshot({ path: `${output}/monitor-browser-failure.png`, fullPage: true })
|
||||
fs.writeFileSync(`${output}/monitor-browser-failure.txt`, String(error) + '\n' + JSON.stringify({ errors, requests, url: page.url() }) + '\n' + await page.locator('body').innerText())
|
||||
throw error
|
||||
} finally { await browser.close() }
|
||||
@@ -58,10 +58,10 @@ def main() -> int:
|
||||
|
||||
step("2. 修改预付款比例 10 → 12(立即生效)")
|
||||
row = page.locator(".param-table tbody tr", has_text="预付款计提比例").first
|
||||
row.get_by_role("button", name="改", exact=True).click()
|
||||
row.get_by_role("button", name="修改", exact=True).click()
|
||||
dialog = page.locator(".el-dialog")
|
||||
dialog.locator("input").fill("12")
|
||||
dialog.get_by_role("button", name="加入待提交").click()
|
||||
dialog.get_by_role("button", name="暂存修改").click()
|
||||
if "待改为 12" not in page.content():
|
||||
fail("待提交", "参数行未出现「待改为 12」标记")
|
||||
|
||||
@@ -87,10 +87,10 @@ def main() -> int:
|
||||
|
||||
step("6. 恢复种子值 10")
|
||||
row = page.locator(".param-table tbody tr", has_text="预付款计提比例").first
|
||||
row.get_by_role("button", name="改", exact=True).click()
|
||||
row.get_by_role("button", name="修改", exact=True).click()
|
||||
dialog = page.locator(".el-dialog")
|
||||
dialog.locator("input").fill("10")
|
||||
dialog.get_by_role("button", name="加入待提交").click()
|
||||
dialog.get_by_role("button", name="暂存修改").click()
|
||||
page.get_by_role("button", name="提交变更").click()
|
||||
page.locator(".el-message-box").get_by_role("button", name="提交").click()
|
||||
wait_toast(page, "已提交")
|
||||
@@ -111,7 +111,7 @@ def main() -> int:
|
||||
drawer.locator(".el-form-item", has_text="销售额").locator("input").fill("100000")
|
||||
drawer.locator(".el-form-item", has_text="毛利润额").locator("input").fill("45000")
|
||||
drawer.locator(".el-form-item", has_text="本笔回款").locator("input").fill("60000")
|
||||
drawer.get_by_role("button", name="试 算").click()
|
||||
drawer.get_by_role("button", name="开始试算").click()
|
||||
page.wait_for_timeout(900)
|
||||
result_text = drawer.locator(".preview-result").text_content()
|
||||
for expected in ["27 天", "145.5", "97/10000"]:
|
||||
|
||||
Reference in new issue
Block a user