233 lines
8.4 KiB
JavaScript
233 lines
8.4 KiB
JavaScript
/**
|
|
* 第 1 屏:登录页 — 按用例逐条执行(人类测试工程师节奏)
|
|
* cd miniprogram-e2e && node pages/01-login.mjs
|
|
*/
|
|
import { chromium } from 'playwright'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
import {
|
|
env,
|
|
openLoginFresh,
|
|
fillLogin,
|
|
clickLoginButton,
|
|
clickAgreeCheck,
|
|
waitToast,
|
|
isOnLogin,
|
|
isOnWorkbench
|
|
} from '../lib/uni-h5.js'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const root = path.join(__dirname, '..')
|
|
const outDir = path.join(root, 'artifacts', 'login')
|
|
fs.mkdirSync(outDir, { recursive: true })
|
|
|
|
const { base, user, pass } = env()
|
|
const results = []
|
|
|
|
function logStep(msg) {
|
|
console.log(` → ${msg}`)
|
|
}
|
|
|
|
async function shot(page, id) {
|
|
await page.screenshot({ path: path.join(outDir, `${id}.png`), fullPage: true })
|
|
}
|
|
|
|
async function runCase(page, id, title, fn) {
|
|
console.log(`\n[${id}] ${title}`)
|
|
try {
|
|
await openLoginFresh(page)
|
|
await fn()
|
|
results.push({ id, title, ok: true, detail: 'PASS' })
|
|
console.log(` PASS`)
|
|
} catch (e) {
|
|
const detail = String(e.message || e).split('\n')[0]
|
|
results.push({ id, title, ok: false, detail })
|
|
console.log(` FAIL: ${detail}`)
|
|
try {
|
|
await shot(page, `${id}-fail`)
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== 登录页界面测试(逐条)===')
|
|
console.log(`base=${base} user=${user}`)
|
|
|
|
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(12000)
|
|
|
|
// LGN-01
|
|
await runCase(page, 'LGN-01', '登录页基础展示', async () => {
|
|
logStep('检查品牌与表单控件')
|
|
await page.getByText('山峡化工').first().waitFor({ state: 'visible' })
|
|
await page.getByText('一体化业务移动端').first().waitFor({ state: 'visible' })
|
|
await page.getByText('用户名', { exact: true }).first().waitFor({ state: 'visible' })
|
|
await page.getByText('密码', { exact: true }).first().waitFor({ state: 'visible' })
|
|
await page.getByText('账号密码登录').first().waitFor({ state: 'visible' })
|
|
await page.getByText(/用户服务协议/).first().waitFor({ state: 'visible' })
|
|
await page.getByText(/隐私政策/).first().waitFor({ state: 'visible' })
|
|
await shot(page, 'LGN-01')
|
|
})
|
|
|
|
// LGN-02
|
|
await runCase(page, 'LGN-02', '输入框占位提示', async () => {
|
|
logStep('检查 placeholder 独立节点文案')
|
|
const texts = await page.locator('.uni-input-placeholder, .input-placeholder').allTextContents()
|
|
const joined = texts.join('|')
|
|
if (!joined.includes('请输入用户名')) throw new Error(`缺用户名占位: ${joined}`)
|
|
if (!joined.includes('请输入密码')) throw new Error(`缺密码占位: ${joined}`)
|
|
await shot(page, 'LGN-02')
|
|
})
|
|
|
|
// LGN-03
|
|
await runCase(page, 'LGN-03', '未勾选协议点击登录弹出确认', async () => {
|
|
logStep('填写账号密码(不勾选)并点登录')
|
|
await fillLogin(page, user, pass)
|
|
await clickLoginButton(page)
|
|
await page.getByText('协议确认').waitFor({ state: 'visible' })
|
|
await page.getByText('是,继续登录').waitFor({ state: 'visible' })
|
|
await page.getByText('否', { exact: true }).waitFor({ state: 'visible' })
|
|
await shot(page, 'LGN-03')
|
|
})
|
|
|
|
// LGN-04
|
|
await runCase(page, 'LGN-04', '协议弹窗点「否」取消', async () => {
|
|
logStep('唤起弹窗后点否')
|
|
await fillLogin(page, user, pass)
|
|
await clickLoginButton(page)
|
|
await page.getByText('协议确认').waitFor({ state: 'visible' })
|
|
await page.getByText('否', { exact: true }).click({ force: true })
|
|
await page.waitForTimeout(500)
|
|
const dialogGone = !(await page.getByText('协议确认').isVisible().catch(() => false))
|
|
if (!dialogGone) throw new Error('弹窗未关闭')
|
|
if (await isOnWorkbench(page)) throw new Error('不应进入工作台')
|
|
if (!(await isOnLogin(page))) throw new Error('应停留登录页')
|
|
await shot(page, 'LGN-04')
|
|
})
|
|
|
|
// LGN-05
|
|
await runCase(page, 'LGN-05', '同意协议后用户名为空', async () => {
|
|
logStep('勾选协议,清空用户名后提交')
|
|
await clickAgreeCheck(page)
|
|
await fillLogin(page, '', 'x')
|
|
await clickLoginButton(page)
|
|
await waitToast(page, /请输入用户名/)
|
|
if (await isOnWorkbench(page)) throw new Error('不应进入工作台')
|
|
await shot(page, 'LGN-05')
|
|
})
|
|
|
|
// LGN-06
|
|
await runCase(page, 'LGN-06', '有用户名无密码', async () => {
|
|
logStep('勾选协议,仅填用户名')
|
|
await clickAgreeCheck(page)
|
|
await fillLogin(page, user, '')
|
|
await clickLoginButton(page)
|
|
await waitToast(page, /请输入密码/)
|
|
if (await isOnWorkbench(page)) throw new Error('不应进入工作台')
|
|
await shot(page, 'LGN-06')
|
|
})
|
|
|
|
// LGN-07
|
|
await runCase(page, 'LGN-07', '错误密码登录失败', async () => {
|
|
logStep('勾选协议,错误密码提交')
|
|
await clickAgreeCheck(page)
|
|
await fillLogin(page, user, 'wrong-password-xxx')
|
|
await clickLoginButton(page)
|
|
await waitToast(page, /失败|错误|不正确|无效|密码/)
|
|
await page.waitForTimeout(800)
|
|
if (await isOnWorkbench(page)) throw new Error('错误密码不应进入工作台')
|
|
if (!(await isOnLogin(page))) throw new Error('应停留登录页')
|
|
await shot(page, 'LGN-07')
|
|
})
|
|
|
|
// LGN-08
|
|
await runCase(page, 'LGN-08', '正确账号登录成功', async () => {
|
|
logStep('勾选协议,正确账号登录')
|
|
await clickAgreeCheck(page)
|
|
await fillLogin(page, user, pass)
|
|
await clickLoginButton(page)
|
|
await page.waitForURL(/\/pages\/workbench\/workbench/, { timeout: 20000 })
|
|
await page.getByText('工作台', { exact: true }).first().waitFor({ state: 'visible' })
|
|
await shot(page, 'LGN-08')
|
|
})
|
|
|
|
// LGN-09
|
|
await runCase(page, 'LGN-09', '打开用户服务协议', async () => {
|
|
logStep('点击协议链接')
|
|
await page.getByText('《用户服务协议》').click({ force: true })
|
|
await page.waitForURL(/user-agreement/, { timeout: 10000 })
|
|
await page.waitForTimeout(600)
|
|
await shot(page, 'LGN-09')
|
|
})
|
|
|
|
// LGN-10
|
|
await runCase(page, 'LGN-10', '打开隐私政策', async () => {
|
|
logStep('点击隐私政策链接')
|
|
await page.getByText('《隐私政策》').click({ force: true })
|
|
await page.waitForURL(/privacy-policy/, { timeout: 10000 })
|
|
await page.waitForTimeout(600)
|
|
await shot(page, 'LGN-10')
|
|
})
|
|
|
|
// LGN-11
|
|
await runCase(page, 'LGN-11', '勾选框可切换同意状态', async () => {
|
|
logStep('勾选后提交不应再弹协议窗;取消后再弹')
|
|
await fillLogin(page, user, 'any')
|
|
await clickAgreeCheck(page)
|
|
await clickLoginButton(page)
|
|
await page.waitForTimeout(600)
|
|
const firstDialog = await page.getByText('协议确认').isVisible().catch(() => false)
|
|
if (firstDialog) throw new Error('已勾选仍弹出协议确认')
|
|
// 可能出现请输入…或登录失败 toast,忽略;回到干净页再测取消
|
|
await openLoginFresh(page)
|
|
await fillLogin(page, user, pass)
|
|
await clickAgreeCheck(page) // on
|
|
await clickAgreeCheck(page) // off
|
|
await clickLoginButton(page)
|
|
await page.getByText('协议确认').waitFor({ state: 'visible', timeout: 5000 })
|
|
await shot(page, 'LGN-11')
|
|
})
|
|
|
|
await browser.close()
|
|
|
|
const fail = results.filter((r) => !r.ok).length
|
|
const summary = {
|
|
screen: 'login',
|
|
base,
|
|
user,
|
|
results,
|
|
fail,
|
|
ts: new Date().toISOString()
|
|
}
|
|
fs.writeFileSync(path.join(outDir, 'result.json'), JSON.stringify(summary, null, 2))
|
|
|
|
// 回写用例执行记录(追加一行到 md 表尾不便解析,写独立 result 即可)
|
|
const mdNote = path.join(outDir, 'RESULT.md')
|
|
const lines = [
|
|
`# 登录页执行结果`,
|
|
``,
|
|
`时间:${summary.ts}`,
|
|
`失败数:${fail} / ${results.length}`,
|
|
``,
|
|
`| ID | 结果 | 说明 |`,
|
|
`|----|------|------|`,
|
|
...results.map((r) => `| ${r.id} | ${r.ok ? 'PASS' : 'FAIL'} | ${r.detail.replace(/\|/g, '/')} |`),
|
|
``
|
|
]
|
|
fs.writeFileSync(mdNote, lines.join('\n'))
|
|
|
|
console.log('\n=== 登录页汇总 ===')
|
|
results.forEach((r) => console.log(` ${r.id}: ${r.ok ? 'PASS' : 'FAIL'} — ${r.detail}`))
|
|
console.log(`fail=${fail} artifacts=${outDir}`)
|
|
console.log(fail ? '\n本屏未通过,请先修登录页问题,再进入下一界面。' : '\n本屏全部通过。确认后可进入下一屏「工作台」。')
|
|
process.exit(fail ? 1 : 0)
|
|
}
|
|
|
|
main()
|