| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- /**
- * 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()
|