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', }) })