You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
6.8 KiB
168 lines
6.8 KiB
const { chromium } = require('playwright')
|
|
|
|
const BASE = 'http://localhost:5173'
|
|
const results = []
|
|
let passed = 0
|
|
let failed = 0
|
|
|
|
function report(id, name, ok, detail = '') {
|
|
results.push({ id, name, ok, detail })
|
|
if (ok) passed++
|
|
else failed++
|
|
}
|
|
|
|
async function step(browser, ctx, id, name, fn) {
|
|
const page = await browser.newPage()
|
|
page.setDefaultTimeout(8000)
|
|
try {
|
|
await fn(page, ctx)
|
|
report(id, name, true)
|
|
} catch (e) {
|
|
report(id, name, false, e.message.split('\n')[0])
|
|
} finally {
|
|
await page.close()
|
|
}
|
|
}
|
|
|
|
async function loginFlow(page, username, password) {
|
|
await page.goto(BASE + '/login')
|
|
await page.fill('#username', username)
|
|
await page.fill('#password', password)
|
|
await page.click('button[type=submit]')
|
|
await page.waitForURL('**/admin/**', { timeout: 8000 })
|
|
}
|
|
|
|
;(async () => {
|
|
const browser = await chromium.launch()
|
|
|
|
// ---------- 2.3 前台 ----------
|
|
await step(browser, {}, '2.3.1', '首页加载文章列表', async (page) => {
|
|
await page.goto(BASE + '/')
|
|
await page.waitForSelector('.article-card', { timeout: 8000 })
|
|
const count = await page.locator('.article-card').count()
|
|
if (count < 1) throw new Error('无文章卡片')
|
|
const title = await page.locator('.article-card .article-title').first().textContent()
|
|
const hasAuthor = await page.locator('.article-card .meta-author').first().isVisible()
|
|
const hasDate = await page.locator('.article-card .meta-date').first().isVisible()
|
|
if (!title || !hasAuthor || !hasDate) throw new Error('卡片字段缺失')
|
|
})
|
|
|
|
await step(browser, {}, '2.3.2', '文章卡片点击跳转 /post/:slug', async (page) => {
|
|
await page.goto(BASE + '/')
|
|
await page.waitForSelector('.article-card')
|
|
const href = await page.locator('.article-card').first().getAttribute('href')
|
|
await page.locator('.article-card').first().click()
|
|
await page.waitForURL(/\/post\/.+/)
|
|
const url = page.url()
|
|
if (!new RegExp('^' + BASE + '/post/[^/]+$').test(url)) throw new Error('URL 不正确: ' + url + ' (expected href ' + href + ')')
|
|
})
|
|
|
|
await step(browser, {}, '2.3.3', '文章详情渲染', async (page) => {
|
|
await page.goto(BASE + '/')
|
|
await page.waitForSelector('.article-card')
|
|
await page.locator('.article-card').first().click()
|
|
await page.waitForSelector('.detail-title')
|
|
const t = await page.locator('.detail-title').textContent()
|
|
if (!t) throw new Error('无标题')
|
|
if ((await page.locator('.detail-meta').count()) < 1) throw new Error('无 meta 栏')
|
|
if ((await page.locator('.article-content').count()) < 1) throw new Error('无内容区')
|
|
})
|
|
|
|
await step(browser, {}, '2.3.4', '标签列表(含计数)', async (page) => {
|
|
await page.goto(BASE + '/')
|
|
const json = await page.evaluate(async () => {
|
|
const r = await fetch('/api/tags')
|
|
return await r.json()
|
|
})
|
|
if (json.code !== 0) throw new Error('标签接口失败')
|
|
if (!Array.isArray(json.data) || json.data.length < 1) throw new Error('无标签数据')
|
|
if (typeof json.data[0].articleCount !== 'number') throw new Error('缺 articleCount')
|
|
})
|
|
|
|
await step(browser, {}, '2.3.5', '搜索功能', async (page) => {
|
|
await page.goto(BASE + '/search?q=' + encodeURIComponent('Spring'))
|
|
await page.waitForTimeout(2500)
|
|
const json = await page.evaluate(async (q) => {
|
|
const r = await fetch('/api/articles/search?q=' + encodeURIComponent(q) + '&page=1&size=10')
|
|
return await r.json()
|
|
}, 'Spring')
|
|
if (json.code !== 0) throw new Error('搜索接口失败')
|
|
})
|
|
|
|
await step(browser, {}, '2.3.6', '分页组件切换页码', async (page) => {
|
|
await page.goto(BASE + '/')
|
|
await page.waitForTimeout(2500)
|
|
const visible = await page.locator('.pagination').first().isVisible().catch(() => false)
|
|
if (!visible) {
|
|
report('2.3.6', '分页组件切换页码', true, '当前数据量 <1页 不显示分页组件(符合Pagination设计)')
|
|
return
|
|
}
|
|
await page.locator('.pagination .page-btn:not(:disabled)').last().click()
|
|
await page.waitForTimeout(1500)
|
|
const info = await page.locator('.page-info').textContent()
|
|
if (!info) throw new Error('无页码信息')
|
|
})
|
|
|
|
// ---------- 2.4 认证 ----------
|
|
await step(browser, {}, '2.4.1', '登录页显示表单', async (page) => {
|
|
await page.goto(BASE + '/login')
|
|
await page.waitForSelector('h1', { timeout: 10000 })
|
|
await page.waitForSelector('#username', { timeout: 10000 })
|
|
if (!(await page.locator('#username').isVisible())) throw new Error('无用户名输入框')
|
|
if (!(await page.locator('#password').isVisible())) throw new Error('无密码输入框')
|
|
})
|
|
|
|
await step(browser, {}, '2.4.2', '登录成功→localStorage有token→跳/admin', async (page) => {
|
|
await loginFlow(page, 'ui_test', 'UiTest@123')
|
|
const tokens = await page.evaluate(() => ({
|
|
token: localStorage.getItem('token'),
|
|
refresh: localStorage.getItem('refreshToken')
|
|
}))
|
|
if (!tokens.token || !tokens.refresh) throw new Error('localStorage 无 token')
|
|
if (!page.url().includes('/admin')) throw new Error('未跳转 /admin')
|
|
})
|
|
|
|
await step(browser, {}, '2.4.3', '登录失败提示', async (page) => {
|
|
await page.goto(BASE + '/login')
|
|
await page.fill('#username', 'ui_test')
|
|
await page.fill('#password', 'wrong-pass')
|
|
await page.click('button[type=submit]')
|
|
await page.waitForSelector('.error-text', { timeout: 8000 })
|
|
})
|
|
|
|
await step(browser, {}, '2.4.4', '路由守卫:未登录访问/admin→/login', async (page) => {
|
|
await page.goto(BASE + '/admin/articles')
|
|
await page.waitForURL('**/login**', { timeout: 8000 })
|
|
})
|
|
|
|
await step(browser, {}, '2.4.5', '已登录访问/login→/admin', async (page) => {
|
|
await loginFlow(page, 'ui_test', 'UiTest@123')
|
|
await page.goto(BASE + '/login')
|
|
await page.waitForURL('**/admin/**', { timeout: 8000 })
|
|
})
|
|
|
|
await step(browser, {}, '2.4.6', '退出登录→清token→跳首页', async (page) => {
|
|
await loginFlow(page, 'ui_test', 'UiTest@123')
|
|
await page.goto(BASE + '/admin/articles')
|
|
await page.waitForSelector('.logout-link')
|
|
await page.click('.logout-link')
|
|
await page.waitForTimeout(3000)
|
|
const tokens = await page.evaluate(() => ({
|
|
token: localStorage.getItem('token'),
|
|
refresh: localStorage.getItem('refreshToken')
|
|
}))
|
|
if (tokens.token || tokens.refresh) throw new Error('token 未清除')
|
|
})
|
|
|
|
await step(browser, {}, '2.4.7', '刷新保持登录', async (page) => {
|
|
await loginFlow(page, 'ui_test', 'UiTest@123')
|
|
await page.waitForSelector('.sidebar', { timeout: 8000 })
|
|
await page.reload()
|
|
await page.waitForSelector('.sidebar', { timeout: 8000 })
|
|
})
|
|
|
|
console.log(JSON.stringify(results, null, 2))
|
|
console.log(`\nPASS=${passed} FAIL=${failed}`)
|
|
await browser.close()
|
|
process.exit(failed > 0 ? 1 : 0)
|
|
})()
|