| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- 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()
- }
|