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