提交
This commit is contained in:
1 parent
a903fcf6d4
commit
259c55cc23
80 files changed
+6090
-123
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user