222 lines
8.2 KiB
JavaScript
222 lines
8.2 KiB
JavaScript
import assert from 'node:assert/strict'
|
||
import { mkdir } from 'node:fs/promises'
|
||
import { chromium } from 'playwright'
|
||
|
||
const origin = process.env.MP_CRM_PRIMITIVES_ORIGIN || 'http://localhost:3006'
|
||
const artifacts = '/tmp/shanxia-mp-crm-primitives'
|
||
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
|
||
})
|
||
const page = await context.newPage()
|
||
const pageErrors = []
|
||
page.on('pageerror', (error) => pageErrors.push(error.message))
|
||
|
||
try {
|
||
await page.goto(origin, { waitUntil: 'domcontentloaded' })
|
||
await page.evaluate(async () => {
|
||
const [
|
||
{ createApp, h, ref },
|
||
{ default: Header },
|
||
{ default: Tabs },
|
||
{ default: Filters },
|
||
{ default: Card },
|
||
{ default: Attachments },
|
||
{ default: Timeline }
|
||
] = await Promise.all([
|
||
import('/node_modules/.vite/deps/vue.js'),
|
||
import('/src/components/mp/MpBusinessHeader.vue'),
|
||
import('/src/components/mp/MpStatusTabs.vue'),
|
||
import('/src/components/mp/MpThreeColumnFilters.vue'),
|
||
import('/src/components/mp/MpExpenseCard.vue'),
|
||
import('/src/components/mp/MpAttachmentGrid.vue'),
|
||
import('/src/components/mp/MpApprovalTimeline.vue'),
|
||
import('/src/styles/mp-webview.scss')
|
||
])
|
||
|
||
document.body.innerHTML = '<div id="primitive-fixture"></div>'
|
||
document.body.style.margin = '0'
|
||
window.__primitiveEvents = []
|
||
|
||
createApp({
|
||
setup() {
|
||
const keyword = ref('')
|
||
const status = ref('all')
|
||
const quickFilters = ref([
|
||
{
|
||
key: 'owner',
|
||
label: '负责人',
|
||
value: 'all',
|
||
options: [
|
||
{ label: '全部负责人', value: 'all' },
|
||
{ label: '测试用户', value: 'user-1' }
|
||
]
|
||
},
|
||
{
|
||
key: 'date',
|
||
label: '创建时间',
|
||
value: 'all',
|
||
options: [
|
||
{ label: '全部时间', value: 'all' },
|
||
{ label: '本月', value: 'month' }
|
||
]
|
||
}
|
||
])
|
||
const event = (name, payload) => window.__primitiveEvents.push({ name, payload })
|
||
return () =>
|
||
h(
|
||
'main',
|
||
{
|
||
class: 'primitive-page',
|
||
style: {
|
||
boxSizing: 'border-box',
|
||
width: '100%',
|
||
padding: '0 10px 24px',
|
||
overflowX: 'clip'
|
||
}
|
||
},
|
||
[
|
||
h(Header, {
|
||
title: '报价管理',
|
||
searchable: true,
|
||
keyword: keyword.value,
|
||
searchPlaceholder: '搜索报价单号或客户',
|
||
'onUpdate:keyword': (value) => (keyword.value = value),
|
||
onSearch: () => event('search', keyword.value),
|
||
onBack: () => event('back')
|
||
}),
|
||
h(Tabs, {
|
||
modelValue: status.value,
|
||
tabs: [
|
||
{ label: '全部', value: 'all', count: 2 },
|
||
{ label: '草稿', value: 'draft', count: 1 }
|
||
],
|
||
'onUpdate:modelValue': (value) => (status.value = value),
|
||
onChange: (value) => event('status', value)
|
||
}),
|
||
h(Filters, {
|
||
quickFilters: quickFilters.value,
|
||
moreActive: true,
|
||
onSelect: (payload) => event('filter', payload),
|
||
onMore: () => event('more')
|
||
}),
|
||
h(
|
||
Card,
|
||
{
|
||
title: 'BJ20261017001',
|
||
status: '审批中',
|
||
statusTone: 'warning',
|
||
amount: '¥ 12,680.00',
|
||
amountLabel: '报价金额',
|
||
time: '2026-10-17 10:30',
|
||
onOpen: () => event('open')
|
||
},
|
||
{
|
||
emphasis: () => h('span', '山峡化工测试客户'),
|
||
kv: () => [h('div', '有效期:30 天'), h('div', '负责人:测试用户')],
|
||
actions: () => h('button', { type: 'button' }, '编辑')
|
||
}
|
||
),
|
||
h(Attachments, {
|
||
items: [{ id: '1', url: '/fixture.pdf', name: '报价附件.pdf' }],
|
||
editable: true,
|
||
onAdd: () => event('attachment-add')
|
||
}),
|
||
h(Timeline, {
|
||
items: [
|
||
{
|
||
id: '1',
|
||
name: '张审批',
|
||
status: '已通过',
|
||
tone: 'success',
|
||
opinion: '同意',
|
||
time: '2026-10-17 11:00'
|
||
}
|
||
]
|
||
}),
|
||
h('section', { class: 'legacy-filter-smoke', 'aria-label': '历史两栏筛选冒烟' }, [
|
||
h('div', { class: 'mp-wv-filter-bar' }, [
|
||
h('button', { class: 'mp-wv-filter-trig', type: 'button' }, '历史筛选一'),
|
||
h('button', { class: 'mp-wv-filter-trig', type: 'button' }, '历史筛选二')
|
||
])
|
||
])
|
||
]
|
||
)
|
||
}
|
||
}).mount('#primitive-fixture')
|
||
})
|
||
|
||
await page.locator('.primitive-page').waitFor()
|
||
const searchBox = page.locator('.mp-wv-search')
|
||
assert.equal(await searchBox.getAttribute('aria-label'), '搜索')
|
||
assert.ok((await searchBox.boundingBox()).height >= 44, '搜索框触达高度不足 44px')
|
||
await searchBox.fill('测试报价')
|
||
await searchBox.press('Enter')
|
||
|
||
await page.locator('.mp-wv-status-tabs .filter-tag').nth(1).click()
|
||
assert.equal(
|
||
await page.locator('.mp-wv-status-tabs .filter-tag').nth(1).getAttribute('aria-pressed'),
|
||
'true'
|
||
)
|
||
|
||
const firstFilter = page.locator('.mp-wv-filter-trig').first()
|
||
await firstFilter.click()
|
||
const option = page.locator('.mp-wv-filter-panel .mp-wv-filter-opt').nth(1)
|
||
await option.waitFor()
|
||
const optionIsTopmost = await option.evaluate((element) => {
|
||
const box = element.getBoundingClientRect()
|
||
const topmost = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2)
|
||
return topmost === element || element.contains(topmost)
|
||
})
|
||
assert.equal(optionIsTopmost, true, '筛选遮罩覆盖了下拉选项')
|
||
assert.ok((await option.boundingBox()).height >= 44, '筛选选项触达高度不足 44px')
|
||
await option.click()
|
||
assert.equal(
|
||
await page.locator('.mp-wv-filter-panel').getAttribute('class'),
|
||
'mp-wv-filter-panel'
|
||
)
|
||
|
||
await firstFilter.click()
|
||
await page.locator('.mp-wv-filter-mask.open').waitFor()
|
||
await page.mouse.click(5, 760)
|
||
assert.equal(await firstFilter.getAttribute('aria-expanded'), 'false')
|
||
|
||
const cardOpen = page.locator('.mp-crm-expense-card .expense-card__open')
|
||
await cardOpen.focus()
|
||
await cardOpen.press('Enter')
|
||
await cardOpen.press('Space')
|
||
|
||
const eventNames = await page.evaluate(() => window.__primitiveEvents.map((item) => item.name))
|
||
assert.deepEqual(eventNames.slice(0, 5), ['search', 'status', 'filter', 'open', 'open'])
|
||
|
||
const legacyColumns = await page
|
||
.locator('.legacy-filter-smoke .mp-wv-filter-bar')
|
||
.evaluate((element) => getComputedStyle(element).gridTemplateColumns.split(' ').length)
|
||
assert.equal(legacyColumns, 2, '历史筛选栏不应被改成三列')
|
||
|
||
const attachmentBox = await page.locator('.mp-crm-att-grid .att-item').first().boundingBox()
|
||
assert.equal(attachmentBox.width, 72)
|
||
assert.equal(attachmentBox.height, 72)
|
||
|
||
await page.evaluate(() => document.activeElement?.blur())
|
||
const viewportGeometry = await page.evaluate(() => ({
|
||
clientWidth: document.documentElement.clientWidth,
|
||
scrollWidth: document.documentElement.scrollWidth
|
||
}))
|
||
assert.equal(viewportGeometry.clientWidth, 375)
|
||
assert.ok(
|
||
viewportGeometry.scrollWidth <= viewportGeometry.clientWidth,
|
||
`公共原语出现横向溢出:${JSON.stringify(viewportGeometry)}`
|
||
)
|
||
|
||
await page.screenshot({ path: `${artifacts}/primitives-375.png`, fullPage: true })
|
||
assert.deepEqual(pageErrors, [], `页面异常:${pageErrors.join(';')}`)
|
||
console.log('CRM 公共原语浏览器交互回归通过:搜索、Tab、筛选、键盘开卡、附件与历史两栏冒烟')
|
||
} finally {
|
||
await browser.close()
|
||
}
|