ff1ffbbfa3
- 列表接口加 keyword(标题/内容模糊)与 startDate/endDate(年月日闭区间)参数, 两端控制器透传,服务端补筛选日志 - 两端铃铛面板:搜索框(防抖 300ms)+ 日期区间 + 按年月日分组 + 翻页, 面板改 Teleport 到 body(挂在顶栏时会被页面 el-select 压住,提 z-index 无效) - 固化可见范围回归测试:超管全量/管理员只看本组/普通用户只看自己, 含读写两侧的 user_id+audience 裁剪断言与两端控制器身份来源断言
123 lines
5.4 KiB
TypeScript
123 lines
5.4 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
|
|
// 站内通知铃铛面板验收:关键字/年月日区间搜索、按天分组、分页。
|
|
// 依赖 scripts/mock-admin-server.mjs 的通知夹具(14 条:09-13 六条、09-12 四条、09-10 四条)。
|
|
|
|
async function openBell(page: Page) {
|
|
await page.goto('/admin-vue/')
|
|
await expect(page.locator('.admin-topbar h1')).toHaveText('用户管理')
|
|
await page.locator('.admin-notification-bell .bell-trigger').click()
|
|
await expect(page.locator('.bell-panel')).toBeVisible()
|
|
await expect(page.locator('.bell-item').first()).toBeVisible()
|
|
}
|
|
|
|
test('test_notification_panel_day_group_and_pagination', async ({ page }) => {
|
|
await openBell(page)
|
|
|
|
const panel = page.locator('.bell-panel')
|
|
const nextPage = panel.getByRole('button', { name: '下一页' })
|
|
|
|
// 每页 10 条,跨两个日期分组,页脚显示页码与总数
|
|
await expect(page.locator('.bell-item')).toHaveCount(10)
|
|
await expect(page.locator('.bell-day-head')).toHaveCount(2)
|
|
await expect(page.locator('.bell-page-info')).toHaveText('1 / 2')
|
|
await expect(page.locator('.bell-page-total')).toHaveText('共 14 条')
|
|
await expect(page.locator('.bell-day-head').first()).toHaveText(/^(今天|昨天|\d{4}年\d{1,2}月\d{1,2}日)$/)
|
|
|
|
const page2Request = page.waitForRequest(
|
|
(request) => request.url().includes('/api/admin/notifications?') && request.url().includes('page=2'),
|
|
)
|
|
await nextPage.click()
|
|
await page2Request
|
|
|
|
await expect(page.locator('.bell-item')).toHaveCount(4)
|
|
await expect(page.locator('.bell-page-info')).toHaveText('2 / 2')
|
|
await expect(page.locator('.bell-day-head')).toHaveCount(1)
|
|
await expect(nextPage).toBeDisabled()
|
|
})
|
|
|
|
test('test_notification_panel_search_by_keyword', async ({ page }) => {
|
|
await openBell(page)
|
|
|
|
const keywordRequest = page.waitForRequest((request) =>
|
|
decodeURIComponent(request.url()).includes('keyword=余额不足'),
|
|
)
|
|
await page.locator('.bell-search-input').fill('余额不足')
|
|
await keywordRequest
|
|
|
|
// 关键字命中标题/内容:夹具里 5 条含「余额不足」
|
|
await expect(page.locator('.bell-item')).toHaveCount(5)
|
|
await expect(page.locator('.bell-page-total')).toHaveText('共 5 条')
|
|
await expect(page.locator('.bell-search-reset')).toBeVisible()
|
|
|
|
await page.locator('.bell-search-reset').click()
|
|
await expect(page.locator('.bell-item')).toHaveCount(10)
|
|
await expect(page.locator('.bell-search-reset')).toBeHidden()
|
|
})
|
|
|
|
test('test_notification_panel_search_by_day_range', async ({ page }) => {
|
|
await openBell(page)
|
|
|
|
const rangeRequest = page.waitForRequest(
|
|
(request) =>
|
|
request.url().includes('startDate=2026-09-12') && request.url().includes('endDate=2026-09-12'),
|
|
)
|
|
await page.locator('.bell-date-input').first().fill('2026-09-12')
|
|
await page.locator('.bell-date-input').nth(1).fill('2026-09-12')
|
|
await rangeRequest
|
|
|
|
await expect(page.locator('.bell-item')).toHaveCount(4)
|
|
await expect(page.locator('.bell-day-head')).toHaveCount(1)
|
|
await expect(page.locator('.bell-page-total')).toHaveText('共 4 条')
|
|
await expect(page.locator('.bell-empty')).toHaveCount(0)
|
|
|
|
// 起止倒挂自动交换:09-13 起 / 09-10 止 → 实际按 09-10 ~ 09-13 查询(全量 14 条)
|
|
await page.locator('.bell-date-input').first().fill('2026-09-13')
|
|
await page.locator('.bell-date-input').nth(1).fill('2026-09-10')
|
|
await expect(page.locator('.bell-page-total')).toHaveText('共 14 条')
|
|
await expect(page.locator('.bell-item')).toHaveCount(10)
|
|
})
|
|
|
|
test('test_notification_panel_not_covered_by_page_content', async ({ page }) => {
|
|
await openBell(page)
|
|
|
|
// 回归:面板挂在顶栏内时曾被页面 el-select 压住(提 z-index 无效),现改为 Teleport 到 body。
|
|
// 这里在面板内取多点做命中测试,最上层元素必须属于面板自身。
|
|
const covered = await page.evaluate(() => {
|
|
const panel = document.querySelector('.bell-panel') as HTMLElement
|
|
const rect = panel.getBoundingClientRect()
|
|
const points: Array<[number, number]> = [
|
|
[rect.left + 30, rect.top + 130],
|
|
[rect.left + rect.width / 2, rect.top + rect.height / 2],
|
|
[rect.right - 30, rect.bottom - 60],
|
|
]
|
|
return points
|
|
.map(([x, y]) => document.elementsFromPoint(x, y)[0])
|
|
.filter((el) => !!el && el !== panel && !panel.contains(el))
|
|
.map((el) => `${el!.tagName}.${String((el as HTMLElement).className).slice(0, 40)}`)
|
|
})
|
|
expect(covered, '面板被页面元素遮挡').toEqual([])
|
|
})
|
|
|
|
test('test_notification_panel_reference_screenshot', async ({ page }) => {
|
|
await openBell(page)
|
|
|
|
// 同日多条不得重叠:逐条校验纵向排布严格递增(截图伪影与真实布局问题的分界)
|
|
const tops = await page
|
|
.locator('.bell-item')
|
|
.evaluateAll((els) => els.map((el) => el.getBoundingClientRect().top))
|
|
for (let i = 1; i < tops.length; i += 1) {
|
|
expect(tops[i], `第 ${i + 1} 条应排在第 ${i} 条下方`).toBeGreaterThan(tops[i - 1])
|
|
}
|
|
|
|
await page.screenshot({ path: 'test-results/notification-panel-admin-page.jpg', animations: 'disabled' })
|
|
await page.locator('.bell-panel').screenshot({ path: 'test-results/notification-panel-admin.jpg', animations: 'disabled' })
|
|
|
|
await page.locator('.bell-search-input').fill('余额不足')
|
|
await expect(page.locator('.bell-item')).toHaveCount(5)
|
|
await page.locator('.bell-panel').screenshot({
|
|
path: 'test-results/notification-panel-admin-search.jpg',
|
|
animations: 'disabled',
|
|
})
|
|
})
|