import { test, expect } from '@playwright/test';
import { collectErrors, expectNoErrors } from './helpers';
/** 布局/全局走查:头部导航树/页尾/标题合理性/下拉/移动端断点 */
const isMobile = (w: number) => w <= 900;
test.describe('全局布局', () => {
test.beforeEach(async () => { await new Promise((r) => setTimeout(r, 300)); });
test('头部导航存在且渲染真实链接', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.site-header')).toBeVisible();
// 桌面导航含可点链接(去往分类或外部),排除 js:void(0) 触发层
const realLinks = await page.locator('.main-nav a[href^="/"]').count();
expect(realLinks, '头部导航无任何站内链接').toBeGreaterThan(0);
expectNoErrors(collectErrors(page));
});
test('页尾结构:多列 + 版权/备案', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.site-footer')).toBeVisible();
const body = await page.locator('.site-footer').innerText();
expect(body.length, '页尾内容为空').toBeGreaterThan(10);
// 备案链接(若有)应指向 beian.miit.gov.cn
const icp = page.locator('.site-footer a[href*="beian.miit.gov.cn"]');
if ((await icp.count()) > 0) {
await expect(icp.first()).toHaveText(/[0-9A-Z-]+号|ICP/i);
}
});
test('标题合理性:非空且非默认占位', async ({ page }) => {
for (const p of ['/', '/core', '/core/nav', '/search-%E5%BA%8F%E5%88%97%E5%8C%96']) {
await page.goto(p);
await page.waitForTimeout(1200); // 页面间留间隔,避免连续访问触发生产限流
const title = await page.title();
expect(title.trim().length, `${p} title 为空`).toBeGreaterThan(0);
expect(title, `${p} title 异常`).not.toContain('Object reference');
}
});
test('移动端:导航下拉可点击展开(若存在下拉项)', async ({ page }) => {
if (!isMobile(page.viewportSize()!.width)) return;
await page.goto('/');
const toggle = page.locator('.nav-toggle');
await toggle.click();
const dd = page.locator('.nav-dropdown').first();
if ((await page.locator('.nav-dropdown').count()) === 0) return;
await expect(dd).toBeVisible();
// 点击下拉触发层展开子菜单
await dd.locator(':scope > a').click();
await expect(dd).toHaveClass(/open/);
const sub = dd.locator('.dd-menu a').first();
if ((await sub.count()) > 0) await expect(sub).toBeVisible();
});
test('移动端:toc 在窄屏不遮挡正文(若详情有 toc)', async ({ page }) => {
if (!isMobile(page.viewportSize()!.width)) return;
await page.goto('/core/nav');
const toc = page.locator('.toc');
if ((await toc.count()) === 0) return;
await expect(toc).toBeHidden();
});
});
|