549 lines
21 KiB
JavaScript
549 lines
21 KiB
JavaScript
import assert from 'node:assert/strict'
|
||
import { mkdir } from 'node:fs/promises'
|
||
import { chromium } from 'playwright'
|
||
|
||
// 本地 H5 交互回归:所有接口均由 fixture 拦截,不写真实业务库。
|
||
const origin = process.env.CONTRACT_TEST_ORIGIN || 'http://localhost:3006'
|
||
const artifacts = '/tmp/shanxia-contract-e2e'
|
||
await mkdir(artifacts, { recursive: true })
|
||
|
||
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||
const context = await browser.newContext({
|
||
viewport: { width: 375, height: 812 },
|
||
isMobile: true,
|
||
deviceScaleFactor: 1
|
||
})
|
||
await context.addInitScript(() => {
|
||
const start = () => {
|
||
const root = document.documentElement
|
||
if (!root) return
|
||
const hide = () => {
|
||
const el = document.getElementById('__vue-devtools-container__')
|
||
if (el) el.style.setProperty('display', 'none', 'important')
|
||
}
|
||
hide()
|
||
new MutationObserver(hide).observe(root, { childList: true, subtree: true })
|
||
}
|
||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start)
|
||
else start()
|
||
})
|
||
const page = await context.newPage()
|
||
page.setDefaultTimeout(7000)
|
||
const pageErrors = []
|
||
const calls = []
|
||
page.on('pageerror', (error) => pageErrors.push(error.message))
|
||
|
||
const baseContract = {
|
||
id: 'contract-1',
|
||
contractNo: 'HT-20261017-001',
|
||
contractName: '年度化工原料销售合同',
|
||
customerId: 'customer-1',
|
||
customerName: '宜昌测试客户',
|
||
contactName: '张经理',
|
||
contactMobile: '13800138000',
|
||
contractType: 'sale',
|
||
amount: 128600,
|
||
freight: 500,
|
||
barrelDeposit: 1000,
|
||
amountWithTax: 130100,
|
||
signDate: '2026-10-17',
|
||
startDate: '2026-10-18',
|
||
endDate: '2027-10-17',
|
||
ownerUserId: 'user-1',
|
||
ownerUserName: '测试销售',
|
||
status: 'draft',
|
||
canApprove: true,
|
||
partyA: '宜昌测试客户',
|
||
partyAContact: '张经理',
|
||
partyAMobile: '13800138000',
|
||
partyB: '山峡化工',
|
||
partyBContact: '李经理',
|
||
partyBMobile: '13900139000',
|
||
paymentTerms: '月结 30 天',
|
||
deliveryTerms: '送货上门',
|
||
contractTerms: '按约定履行',
|
||
attachmentFiles: JSON.stringify([{ ossId: '/fixtures/合同扫描件.pdf', category: 'scan' }]),
|
||
remark: 'fixture 合同',
|
||
createBy: 'user-1',
|
||
createTime: '2026-10-17 09:00:00',
|
||
products: [
|
||
{
|
||
id: 'line-1',
|
||
standardProductName: '工业甲醇',
|
||
quantity: 10,
|
||
unit: '吨',
|
||
unitPrice: 12000,
|
||
amount: 120000
|
||
}
|
||
]
|
||
}
|
||
|
||
const contracts = [
|
||
baseContract,
|
||
{
|
||
...baseContract,
|
||
id: 'contract-2',
|
||
contractNo: 'HT-20261017-002',
|
||
contractName: '已审批合同',
|
||
status: 'approved',
|
||
canApprove: false
|
||
},
|
||
{
|
||
...baseContract,
|
||
id: 'contract-fail',
|
||
contractNo: 'HT-20261017-003',
|
||
contractName: '审批失败样本',
|
||
status: 'approving',
|
||
canApprove: true
|
||
}
|
||
]
|
||
|
||
let approvals = [
|
||
{
|
||
id: 'approval-1',
|
||
contractId: baseContract.id,
|
||
step: 1,
|
||
approverId: 'user-before',
|
||
approverName: '前置审批人',
|
||
approvalResult: 'pending',
|
||
approvalOpinion: '',
|
||
createTime: '2026-10-17 10:00:00'
|
||
},
|
||
{
|
||
id: 'approval-2',
|
||
contractId: baseContract.id,
|
||
step: 2,
|
||
approverId: 'user-1',
|
||
approverName: '测试审批人',
|
||
approvalResult: 'pending',
|
||
approvalOpinion: '',
|
||
createTime: '2026-10-17 10:30:00'
|
||
}
|
||
]
|
||
let approvalEntry = false
|
||
let forbiddenMode = ''
|
||
|
||
const dict = {
|
||
crm_contract_status: [
|
||
{ value: 'draft', label: '草稿', tagType: 'info' },
|
||
{ value: 'approving', label: '待审批', tagType: 'warning' },
|
||
{ value: 'approved', label: '已审批', tagType: 'success' },
|
||
{ value: 'invalid', label: '已作废', tagType: 'danger' }
|
||
],
|
||
crm_contract_type: [
|
||
{ value: 'sale', label: '销售合同', tagType: 'primary' },
|
||
{ value: 'purchase', label: '采购合同', tagType: 'info' }
|
||
]
|
||
}
|
||
|
||
await context.addInitScript(() => {
|
||
window.wx = {
|
||
miniProgram: {
|
||
navigateBack() {},
|
||
reLaunch() {},
|
||
getEnv(callback) {
|
||
callback({ miniprogram: true })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
await context.route('**/api/**', async (route) => {
|
||
const request = route.request()
|
||
const url = new URL(request.url())
|
||
if (!url.pathname.startsWith('/api/')) return route.continue()
|
||
const path = url.pathname.replace(/^\/api/, '')
|
||
let body = Object.fromEntries(url.searchParams)
|
||
try {
|
||
body = { ...body, ...(request.postDataJSON() || {}) }
|
||
} catch {
|
||
// multipart 上传不参与 body 精确断言。
|
||
}
|
||
calls.push({ path, body })
|
||
|
||
let data = {}
|
||
let code = 200
|
||
let msg = 'ok'
|
||
if (path === '/web/auth/mp-webview-exchange') {
|
||
const ticket = String(body.ticket || '')
|
||
approvalEntry = ticket.includes('approve')
|
||
forbiddenMode = ticket.includes('forbidden-list')
|
||
? 'list'
|
||
: ticket.includes('forbidden-detail')
|
||
? 'detail'
|
||
: ''
|
||
data = {
|
||
token: 'fixture-token',
|
||
refreshToken: 'fixture-refresh',
|
||
userInfo: {
|
||
id: 'user-1',
|
||
userId: 'user-1',
|
||
userName: '测试用户',
|
||
role: 'admin',
|
||
roles: ['admin']
|
||
}
|
||
}
|
||
} else if (path === '/web/admin/dict-data/all') {
|
||
data = dict
|
||
} else if (path === '/web/admin/users/options') {
|
||
data = [{ value: 'user-1', label: '测试审批人', userNo: 'approver' }]
|
||
} else if (path === '/web/crm/contract/list') {
|
||
if (forbiddenMode === 'list') {
|
||
code = 403
|
||
msg = '无权限访问合同列表'
|
||
} else {
|
||
const list = body.approvedByUserId
|
||
? contracts.filter((item) => item.status === 'approved')
|
||
: approvalEntry
|
||
? contracts.map((item) => ({
|
||
...item,
|
||
status: item.id === 'contract-2' ? 'approved' : 'approving'
|
||
}))
|
||
: contracts
|
||
data = { list, total: list.length }
|
||
}
|
||
} else if (/^\/web\/crm\/contract\/contract-(?:1|2|fail)$/.test(path)) {
|
||
if (forbiddenMode === 'detail') {
|
||
code = 403
|
||
msg = '无权限访问合同详情'
|
||
} else {
|
||
const id = decodeURIComponent(path.split('/').pop())
|
||
const row = contracts.find((item) => item.id === id) || baseContract
|
||
data = {
|
||
...row,
|
||
status: approvalEntry
|
||
? approvals.some((item) => item.approvalResult === 'pending')
|
||
? 'approving'
|
||
: 'approved'
|
||
: row.status
|
||
}
|
||
}
|
||
} else if (/^\/web\/crm\/contract\/approval\/flow\//.test(path)) {
|
||
data = { list: approvals }
|
||
} else if (path === '/web/crm/contract-approver-setting/get') {
|
||
data = { approverIds: ['user-1'], status: '0' }
|
||
} else if (path === '/web/crm/contract/batchSubmitApproval') {
|
||
data = body.ids?.length || 0
|
||
} else if (path === '/web/crm/contract/approval') {
|
||
if (body.id === 'contract-fail') {
|
||
await route.abort('failed')
|
||
return
|
||
} else {
|
||
approvals = approvals.map((item) => ({
|
||
...item,
|
||
approvalResult: body.approvalResult,
|
||
approvalOpinion: body.approvalOpinion,
|
||
approvalTime: '2026-10-17 11:00:00'
|
||
}))
|
||
data = true
|
||
}
|
||
} else if (path === '/web/admin/attachment/upload') {
|
||
data = { id: 'upload-1', fileName: '补充扫描件.png', fileUrl: '/fixtures/补充扫描件.png' }
|
||
} else if (path === '/web/crm/contract/uploadAttachments') {
|
||
data = true
|
||
} else if (path === '/web/crm/contract/invalid') {
|
||
data = true
|
||
}
|
||
|
||
await route.fulfill({
|
||
status: 200,
|
||
contentType: 'application/json',
|
||
body: JSON.stringify({ code, msg, data })
|
||
})
|
||
})
|
||
|
||
const click = (name) =>
|
||
page
|
||
.getByRole('button', { name, exact: true })
|
||
.or(page.getByRole('tab', { name, exact: true }))
|
||
.filter({ visible: true })
|
||
.first()
|
||
.click()
|
||
const lastCall = (path) => calls.findLast((item) => item.path === path)
|
||
|
||
async function assertNoOverflow(width) {
|
||
await page.setViewportSize({ width, height: 812 })
|
||
assert.equal(
|
||
await page.evaluate(() => document.documentElement.scrollWidth > innerWidth),
|
||
false,
|
||
`页面在 ${width}px 不应横向溢出`
|
||
)
|
||
}
|
||
|
||
function boxesOverlap(a, b) {
|
||
return !(
|
||
a.x + a.width <= b.x ||
|
||
b.x + b.width <= a.x ||
|
||
a.y + a.height <= b.y ||
|
||
b.y + b.height <= a.y
|
||
)
|
||
}
|
||
|
||
try {
|
||
await page.goto(`${origin}/#/m/crm/contract?mpTicket=contract-forbidden-list`)
|
||
await page.getByText('无权限', { exact: true }).waitFor()
|
||
|
||
await page.goto(
|
||
`${origin}/#/m/crm/contract/contract-1?from=manage&mpTicket=contract-forbidden-detail`
|
||
)
|
||
await page.locator('.mp-wv-empty').getByText('无权限访问合同详情', { exact: true }).waitFor()
|
||
|
||
await page.goto(`${origin}/#/m/crm/contract?mpTicket=contract-manage-fixture`)
|
||
await page.getByRole('heading', { name: '合同管理', exact: true }).waitFor()
|
||
await page.getByRole('button', { name: `查看合同:${baseContract.contractName}` }).waitFor()
|
||
|
||
const listOrder = await Promise.all(
|
||
['.mp-wv-header', '.mp-customer-view-tabs', '.mp-customer-dd', '.mp-wv-flat-card'].map(
|
||
async (selector) => (await page.locator(selector).first().boundingBox())?.y ?? -1
|
||
)
|
||
)
|
||
assert.deepEqual(
|
||
[...listOrder].sort((a, b) => a - b),
|
||
listOrder
|
||
)
|
||
assert.equal(await page.locator('.mp-customer-dd .van-dropdown-menu__item').count(), 3)
|
||
assert.equal(await page.locator('.mp-wv-speed-dial').count(), 1)
|
||
const manageCards = page.locator('.mp-wv-flat-card')
|
||
assert.equal(await manageCards.nth(0).locator('.mp-wv-card-actions').isVisible(), true)
|
||
assert.equal(await manageCards.nth(1).locator('.mp-wv-card-actions').count(), 0)
|
||
assert.equal(await manageCards.nth(2).locator('.mp-wv-card-actions').count(), 0)
|
||
|
||
await page.locator('.van-dropdown-menu__title', { hasText: '全部类型' }).click()
|
||
await Promise.all([
|
||
page.waitForResponse((response) =>
|
||
new URL(response.url()).pathname.endsWith('/web/crm/contract/list')
|
||
),
|
||
page.locator('.van-dropdown-item__option', { hasText: '销售合同' }).first().click()
|
||
])
|
||
assert.equal(lastCall('/web/crm/contract/list').body.contractType, 'sale')
|
||
|
||
await page.locator('.van-dropdown-menu__title', { hasText: '更多' }).click()
|
||
const filterSheet = page.getByRole('dialog').filter({ hasText: '更多筛选' })
|
||
await filterSheet.waitFor()
|
||
await filterSheet.locator('input[type="search"]').fill('年度')
|
||
await filterSheet.getByText('签订日起', { exact: true }).click()
|
||
await page.locator('.van-picker__confirm').click()
|
||
await filterSheet.getByText('签订日止', { exact: true }).click()
|
||
await page.locator('.van-picker__confirm').click()
|
||
await filterSheet.locator('.van-field').filter({ hasText: '金额最低' }).locator('input').fill('100')
|
||
await filterSheet.locator('.van-field').filter({ hasText: '金额最高' }).locator('input').fill('200000')
|
||
await Promise.all([
|
||
page.waitForResponse((response) =>
|
||
new URL(response.url()).pathname.endsWith('/web/crm/contract/list')
|
||
),
|
||
filterSheet.getByRole('button', { name: '确定', exact: true }).click()
|
||
])
|
||
const moreBody = lastCall('/web/crm/contract/list').body
|
||
assert.equal(moreBody.keyword, '年度')
|
||
assert.match(moreBody.signDateBegin, /^\d{4}-\d{2}-\d{2}$/)
|
||
assert.match(moreBody.signDateEnd, /^\d{4}-\d{2}-\d{2}$/)
|
||
assert.equal(Number(moreBody.minAmount), 100)
|
||
assert.equal(Number(moreBody.maxAmount), 200000)
|
||
|
||
const searchInput = page.getByRole('searchbox', { name: '搜索合同' })
|
||
await Promise.all([
|
||
page.waitForResponse((response) =>
|
||
new URL(response.url()).pathname.endsWith('/web/crm/contract/list')
|
||
),
|
||
searchInput.fill('')
|
||
])
|
||
assert.equal('keyword' in lastCall('/web/crm/contract/list').body, false)
|
||
|
||
await click('多选')
|
||
assert.ok(await page.locator('.mp-wv-batch-bar').isVisible())
|
||
const selectors = page.locator('.expense-card__select')
|
||
await selectors.nth(0).click()
|
||
await selectors.nth(1).click()
|
||
await click('提交审批')
|
||
const submitSheet = page.getByRole('dialog').filter({ hasText: '提交审批' })
|
||
await submitSheet.waitFor()
|
||
assert.ok(await submitSheet.getByText('已跳过 1 条非草稿', { exact: false }).isVisible())
|
||
await Promise.all([
|
||
page.waitForResponse((response) =>
|
||
response.url().endsWith('/web/crm/contract/batchSubmitApproval')
|
||
),
|
||
submitSheet.getByRole('button', { name: '确认提交', exact: true }).click()
|
||
])
|
||
assert.deepEqual(lastCall('/web/crm/contract/batchSubmitApproval').body.ids, ['contract-1'])
|
||
const submitSuccess = page.getByText('成功提交 1 条,跳过 1 条', { exact: true })
|
||
await submitSuccess.waitFor()
|
||
await submitSuccess.waitFor({ state: 'hidden' })
|
||
await click('快捷操作')
|
||
assert.ok(await page.getByRole('button', { name: '新增合同', exact: true }).isVisible())
|
||
await click('快捷操作')
|
||
await page.screenshot({ path: `${artifacts}/manage-list-375.png`, fullPage: true })
|
||
await assertNoOverflow(375)
|
||
|
||
const manageDetailUrl = `${origin}/#/m/crm/contract/contract-1?from=manage&mpTicket=contract-manage-direct`
|
||
const detailCallsBefore = calls.filter(
|
||
(item) => item.path === '/web/crm/contract/contract-1'
|
||
).length
|
||
await page.goto(manageDetailUrl)
|
||
await page.getByRole('heading', { name: '合同详情', exact: true }).waitFor()
|
||
await page.reload()
|
||
await page.getByRole('heading', { name: '合同详情', exact: true }).waitFor()
|
||
assert.ok(
|
||
calls.filter((item) => item.path === '/web/crm/contract/contract-1').length >=
|
||
detailCallsBefore + 2
|
||
)
|
||
assert.equal(await page.locator('.mp-wv-header').getByText(baseContract.contractName).count(), 0)
|
||
assert.equal(await page.locator('.mp-wv-header__sub').count(), 0)
|
||
assert.ok(await page.getByRole('button', { name: '提交审批', exact: true }).isVisible())
|
||
assert.ok(await page.getByRole('button', { name: '编辑', exact: true }).isVisible())
|
||
assert.equal(await page.getByRole('button', { name: '通过', exact: true }).count(), 0)
|
||
await click('产品明细')
|
||
await page.getByText('工业甲醇', { exact: true }).waitFor()
|
||
await click('审批信息')
|
||
await page.locator('.mp-crm-approval-timeline').waitFor()
|
||
await click('附件')
|
||
const attachment = page.locator('.mp-crm-att-grid .att-item').first()
|
||
const attachmentBox = await attachment.boundingBox()
|
||
assert.equal(Math.round(attachmentBox?.width || 0), 72)
|
||
assert.equal(Math.round(attachmentBox?.height || 0), 72)
|
||
const previewBox = await attachment.locator('.att-preview').boundingBox()
|
||
const removeBox = await attachment.locator('.att-remove').boundingBox()
|
||
const addBox = await page.locator('.mp-crm-att-grid .att-add').boundingBox()
|
||
const attachmentGeometry = { attachmentBox, previewBox, removeBox, addBox }
|
||
console.log(`contract attachment geometry: ${JSON.stringify(attachmentGeometry)}`)
|
||
assert.equal(
|
||
Math.round(previewBox?.width || 0),
|
||
72,
|
||
`附件预览宽度必须为 72px:${JSON.stringify(previewBox)}`
|
||
)
|
||
assert.equal(
|
||
Math.round(previewBox?.height || 0),
|
||
72,
|
||
`附件预览高度必须为 72px:${JSON.stringify(previewBox)}`
|
||
)
|
||
assert.ok(
|
||
(removeBox?.width || 0) >= 44 && (removeBox?.height || 0) >= 44,
|
||
`删除附件命中区必须不小于 44px:${JSON.stringify(removeBox)}`
|
||
)
|
||
assert.ok(
|
||
(addBox?.width || 0) >= 44 && (addBox?.height || 0) >= 44,
|
||
`添加附件命中区必须不小于 44px:${JSON.stringify(addBox)}`
|
||
)
|
||
assert.equal(
|
||
boxesOverlap(removeBox, addBox),
|
||
false,
|
||
`删除与添加命中区不得重叠:${JSON.stringify({ removeBox, addBox })}`
|
||
)
|
||
await page.screenshot({ path: `${artifacts}/manage-detail-375.png`, fullPage: true })
|
||
|
||
await click('更多')
|
||
const uploadScan = page.getByRole('button', { name: '上传扫描件', exact: true })
|
||
await uploadScan.waitFor()
|
||
const uploadScanBox = await uploadScan.boundingBox()
|
||
assert.ok(uploadScanBox.y >= 0 && uploadScanBox.y + uploadScanBox.height <= 812)
|
||
await uploadScan.click()
|
||
await page.locator('input[type="file"]').setInputFiles({
|
||
name: '补充扫描件.png',
|
||
mimeType: 'image/png',
|
||
buffer: Buffer.from('fixture')
|
||
})
|
||
await page.waitForResponse((response) =>
|
||
response.url().endsWith('/web/crm/contract/uploadAttachments')
|
||
)
|
||
assert.equal(lastCall('/web/crm/contract/uploadAttachments').body.id, 'contract-1')
|
||
assert.equal(lastCall('/web/crm/contract/uploadAttachments').body.attachments.length, 2)
|
||
|
||
await page.goto(`${origin}/#/m/crm/contract/approval?mpTicket=contract-approve-fixture`)
|
||
await page.getByRole('heading', { name: '合同审批', exact: true }).waitFor()
|
||
await page.getByRole('button', { name: `查看合同:${baseContract.contractName}` }).waitFor()
|
||
assert.equal(await page.locator('.mp-wv-speed-dial').count(), 0)
|
||
assert.equal(lastCall('/web/crm/contract/list').body.pendingApprovalUserId, 'user-1')
|
||
const approveCards = page.locator('.mp-wv-flat-card')
|
||
assert.equal(await approveCards.nth(0).locator('.mp-wv-card-actions').isVisible(), true)
|
||
assert.equal(await approveCards.nth(1).locator('.mp-wv-card-actions').count(), 0)
|
||
assert.equal(await approveCards.nth(2).locator('.mp-wv-card-actions').isVisible(), true)
|
||
|
||
await click('多选')
|
||
const approveSelectors = page.locator('.expense-card__select')
|
||
await approveSelectors.nth(0).click()
|
||
await approveSelectors.nth(1).click()
|
||
await approveSelectors.nth(2).click()
|
||
await click('通过')
|
||
const batchApproveSheet = page.getByRole('dialog').filter({ hasText: '批量通过' })
|
||
await batchApproveSheet.waitFor()
|
||
assert.ok(await batchApproveSheet.getByText('已跳过 1 条不可审批', { exact: false }).isVisible())
|
||
await batchApproveSheet.getByRole('button', { name: '确认通过', exact: true }).click()
|
||
await page.waitForTimeout(100)
|
||
const partialMessages = await page.locator('.el-message').allTextContents()
|
||
assert.ok(
|
||
partialMessages.some((text) => text.includes('成功通过 1 条,失败 1 条,跳过 1 条')),
|
||
`批量部分成功提示缺失:${partialMessages.join(' | ')}`
|
||
)
|
||
assert.equal(calls.filter((item) => item.path === '/web/crm/contract/approval').length, 2)
|
||
assert.equal(await page.locator('.mp-wv-batch-bar').count(), 0)
|
||
|
||
await page.waitForFunction(() => document.querySelectorAll('.el-message').length === 0)
|
||
approvals = approvals.map((item) => ({
|
||
...item,
|
||
approvalResult: 'pending',
|
||
approvalOpinion: '',
|
||
approvalTime: ''
|
||
}))
|
||
await page.goto(
|
||
`${origin}/#/m/crm/contract/contract-1?from=approve&mpTicket=contract-approve-direct`
|
||
)
|
||
await page.getByRole('heading', { name: '合同详情', exact: true }).waitFor()
|
||
assert.match(page.url(), /from=approve/)
|
||
assert.equal(await page.getByRole('button', { name: '通过', exact: true }).count(), 0)
|
||
assert.equal(await page.getByRole('button', { name: '驳回', exact: true }).count(), 0)
|
||
assert.equal(await page.getByRole('button', { name: '提交审批', exact: true }).count(), 0)
|
||
assert.equal(await page.getByRole('button', { name: '编辑', exact: true }).count(), 0)
|
||
approvals[0] = {
|
||
...approvals[0],
|
||
approvalResult: 'approved',
|
||
approvalOpinion: '前置审批已通过',
|
||
approvalTime: '2026-10-17 10:20:00'
|
||
}
|
||
await page.reload()
|
||
await page.getByRole('button', { name: '通过', exact: true }).waitFor()
|
||
assert.ok(await page.getByRole('button', { name: '驳回', exact: true }).isVisible())
|
||
assert.equal(await page.getByRole('button', { name: '提交审批', exact: true }).count(), 0)
|
||
assert.equal(await page.getByRole('button', { name: '编辑', exact: true }).count(), 0)
|
||
await click('通过')
|
||
const approveSheet = page.getByRole('dialog').filter({ hasText: '通过合同' })
|
||
await approveSheet.waitFor()
|
||
await approveSheet.getByRole('button', { name: '确认通过', exact: true }).click()
|
||
const confirmDialog = page.locator('.van-dialog').filter({ hasText: '确认要通过该合同吗?' })
|
||
await confirmDialog.getByRole('button', { name: '确认', exact: true }).click()
|
||
await confirmDialog.waitFor({ state: 'hidden' })
|
||
assert.equal(lastCall('/web/crm/contract/approval').body.id, 'contract-1')
|
||
assert.equal(lastCall('/web/crm/contract/approval').body.approvalResult, 'approved')
|
||
await page.waitForFunction(() => document.querySelectorAll('.el-message').length === 0)
|
||
await assertNoOverflow(414)
|
||
await page.screenshot({ path: `${artifacts}/approve-detail-414.png`, fullPage: true })
|
||
|
||
await page.getByRole('button', { name: '返回合同审批', exact: true }).click()
|
||
await page.getByRole('heading', { name: '合同审批', exact: true }).waitFor()
|
||
await Promise.all([
|
||
page.waitForResponse((response) =>
|
||
new URL(response.url()).pathname.endsWith('/web/crm/contract/list')
|
||
),
|
||
click('已审批')
|
||
])
|
||
assert.equal(lastCall('/web/crm/contract/list').body.approvedByUserId, 'user-1')
|
||
await page.screenshot({ path: `${artifacts}/approve-list-414.png`, fullPage: true })
|
||
|
||
assert.deepEqual(pageErrors, [])
|
||
console.log(
|
||
JSON.stringify({
|
||
result: 'PASS',
|
||
assertions:
|
||
'管理/审批列表骨架、真实筛选、金额卡、动作栏显隐、Speed Dial差异、多选批量底栏、批量提交、批量审批部分成功、详情直达/刷新、列表与详情403、两步审批门禁、入口权限差异、固定详情头、Tab、时间轴、72px附件与不重叠命中区、附件上传、单条审批、375/414布局',
|
||
apiCalls: calls.length,
|
||
screenshots: artifacts
|
||
})
|
||
)
|
||
} catch (error) {
|
||
await page.screenshot({ path: `${artifacts}/failure.png`, fullPage: true })
|
||
console.error(await page.locator('body').innerText())
|
||
console.error('Page errors:', pageErrors)
|
||
console.error('API calls:', calls)
|
||
throw error
|
||
} finally {
|
||
await browser.close()
|
||
}
|