This commit is contained in:
zlwu committed 2026-07-18 09:14:02 +08:00
1 parent a903fcf6d4
commit 259c55cc23
80 files changed
+6090 -123

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
/**
* H5 UI 冒烟(不改动 miniprogram 业务工程)
* 用法:H5 已在 http://localhost:5173 运行时
* cd miniprogram-e2e && node smoke.mjs
*
* uni-app H5 注意:
* - placeholder 在独立 div,真实输入为 input.uni-input-input
* - 文案常在 span/text 上,点击会被父级 uni-button/view 拦截,需 force 点容器
*/
import { chromium } from 'playwright'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const BASE = process.env.E2E_BASE || 'http://localhost:5173'
const USER = process.env.E2E_USER || 'admin'
const PASS = process.env.E2E_PASS || 'admin123'
const outDir = path.join(__dirname, 'artifacts')
fs.mkdirSync(outDir, { recursive: true })
const results = []
function record(id, ok, detail) {
results.push({ id, ok, detail })
console.log(`[${ok ? 'PASS' : 'FAIL'}] ${id}: ${detail}`)
}
async function shot(page, name) {
await page.screenshot({ path: path.join(outDir, `${name}.png`), fullPage: true })
}
async function tapText(page, text, opts = {}) {
const loc = opts.exact
? page.getByText(text, { exact: true }).first()
: page.getByText(text).first()
await loc.click({ force: true, timeout: opts.timeout || 15000 })
}
async function login(page) {
await page.goto(BASE + '/', { waitUntil: 'networkidle' })
await page.waitForTimeout(1200)
await shot(page, '01-landing')
if (page.url().includes('/pages/workbench/workbench')) return
const wbVisible = await page.getByText('工作台', { exact: true }).first().isVisible().catch(() => false)
if (wbVisible) return
await page.locator('input.uni-input-input[type="text"]').first().fill(USER)
await page.locator('input.uni-input-input[type="password"]').first().fill(PASS)
await page.locator('uni-button.btn-login, button.btn-login').first().click({ force: true })
// 协议弹窗:未勾选时出现
try {
const confirm = page.getByText('是,继续登录')
await confirm.waitFor({ state: 'visible', timeout: 3000 })
await confirm.click({ force: true })
} catch (_) {
/* 已勾选协议则无弹窗 */
}
await page.waitForURL(/\/pages\/workbench\/workbench/, { timeout: 20000 })
await page.getByText('工作台', { exact: true }).first().waitFor({ state: 'visible', timeout: 10000 })
await page.waitForTimeout(800)
await shot(page, '02-after-login')
}
async function main() {
const browser = await chromium.launch({ headless: true })
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
locale: 'zh-CN'
})
const page = await context.newPage()
page.setDefaultTimeout(20000)
try {
await login(page)
const wb = await page.getByText('工作台', { exact: true }).first().isVisible().catch(() => false)
const crm = await page.getByText('CRM 管理').first().isVisible().catch(() => false)
const bx = await page.getByText('报销管理').first().isVisible().catch(() => false)
const db = await page.getByText('地磅管理').first().isVisible().catch(() => false)
const pl = await page.getByText('运输管理').first().isVisible().catch(() => false)
record(
'E1',
wb && crm && bx && db && pl,
`工作台=${wb} CRM=${crm} 报销=${bx} 地磅=${db} 运输=${pl}`
)
if (!(wb && crm && bx && db && pl)) {
await shot(page, '02-fail-workbench')
}
async function openMenu(label) {
await tapText(page, label, { exact: true })
await page.waitForTimeout(2200)
}
async function backWorkbench() {
const back = page.getByText(/←\s*工作台|←\s*返回/).first()
if (await back.isVisible().catch(() => false)) {
await back.click({ force: true })
await page.waitForTimeout(1500)
return
}
await page.goto(BASE + '/pages/workbench/workbench', { waitUntil: 'networkidle' })
await page.waitForTimeout(1200)
}
if (!wb) return
// E2 线索
try {
await openMenu('线索管理')
await shot(page, '03-crm-clue')
const ok =
(await page.getByText(/线索|暂无/).first().isVisible().catch(() => false)) ||
(await page.locator('.mp-card, .mp-empty, .mp-header__title').first().isVisible().catch(() => false))
record('E2', ok, ok ? '线索列表页已打开' : '未识别到线索列表特征')
await backWorkbench()
} catch (e) {
record('E2', false, String(e.message || e))
await shot(page, '03-crm-fail')
await page.goto(BASE + '/pages/workbench/workbench', { waitUntil: 'domcontentloaded' })
}
// E3 报销申请
try {
await openMenu('报销申请')
await shot(page, '04-bx-reimburse')
const ok =
(await page.getByText(/报销|暂无|新建/).first().isVisible().catch(() => false)) ||
(await page.locator('.mp-header__title, .mp-empty, .mp-card').first().isVisible().catch(() => false))
record('E3', ok, ok ? '报销申请页已打开' : '未识别到报销页特征')
await backWorkbench()
} catch (e) {
record('E3', false, String(e.message || e))
await page.goto(BASE + '/pages/workbench/workbench', { waitUntil: 'domcontentloaded' })
}
// E4 地磅仪表盘
try {
await openMenu('仪表盘')
await shot(page, '05-db-dashboard')
const ok =
(await page.getByText(/仪表盘|今日|入库|出库|统计/).first().isVisible().catch(() => false)) ||
(await page.locator('.mp-stat, .mp-header__title').first().isVisible().catch(() => false))
record('E4', ok, ok ? '地磅仪表盘已打开' : '未识别到仪表盘特征')
await backWorkbench()
} catch (e) {
record('E4', false, String(e.message || e))
await page.goto(BASE + '/pages/workbench/workbench', { waitUntil: 'domcontentloaded' })
}
// E5 运输计划
try {
await openMenu('运输计划')
await shot(page, '06-pl-plan')
const ok =
(await page.getByText(/待发运|在途|已完成|运输计划|暂无/).first().isVisible().catch(() => false)) ||
(await page.locator('.mp-tab, .mp-header__title').first().isVisible().catch(() => false))
record('E5', ok, ok ? '运输计划页已打开' : '未识别到运输计划特征')
} catch (e) {
record('E5', false, String(e.message || e))
await shot(page, '06-pl-fail')
}
} catch (e) {
record('EX', false, String(e.message || e))
try {
await shot(page, '99-exception')
} catch (_) {}
} finally {
await browser.close()
}
const summary = {
base: BASE,
user: USER,
results,
fail: results.filter((r) => !r.ok).length,
ts: new Date().toISOString()
}
fs.writeFileSync(path.join(outDir, 'smoke-result.json'), JSON.stringify(summary, null, 2))
console.log('\n=== SUMMARY ===')
results.forEach((r) => console.log(` ${r.id}: ${r.ok ? 'OK' : 'FAIL'} — ${r.detail}`))
console.log(`fail=${summary.fail} artifacts=${outDir}`)
process.exit(summary.fail ? 1 : 0)
}
main()