import { test, expect } from '@playwright/test';
import { collectErrors, expectNoErrors } from './helpers';
/** 文章详情页走查:正文/元信息/上下篇/目录锚点一致性/产品页分流 */
const DETAILS = [
{ name: 'core-nav', path: '/core/nav' },
{ name: 'events-talk004', path: '/events/night_talks_004' },
{ name: 'xcode-mysql', path: '/xcode/mysql' },
];
const PRODUCTS = ['/blood/stardust', '/blood/antjob', '/blood/newlife'];
test.describe('文章详情页', () => {
test.beforeEach(async () => { await new Promise((r) => setTimeout(r, 300)); });
for (const d of DETAILS) {
test(`${d.path} 加载:标题/正文/面包屑/无错误`, async ({ page }) => {
const errors = collectErrors(page);
const resp = await page.goto(d.path);
expect(resp!.status(), `${d.path} 返回 ${resp!.status()}`).toBe(200);
await expect(page.locator('.crumb')).toBeVisible();
await expect(page.locator('.article-header h1')).not.toBeEmpty();
await expect(page.locator('.article-content')).toBeVisible();
expectNoErrors(errors);
});
test(`${d.path} 目录锚点一致性(若存在 toc)`, async ({ page }) => {
await page.goto(d.path);
const toc = page.locator('.toc');
const n = await toc.count();
if (n === 0) return; // 无 toc 属数据决定,跳过
// 校验 toc 每个 #href 在正文存在对应 id,否则 scrollspy 失效
const missing = await page.evaluate(() => {
const out: String[] = [];
document.querySelectorAll('.toc a[href^="#"]').forEach((a) => {
const id = a.getAttribute('href')!.slice(1);
if (!document.getElementById(id)) out.push(id);
});
return out;
});
expect(missing, `${d.path} 目录锚点指向不存在的 id:${missing}`).toEqual([]);
});
}
test('详情元信息与上下篇结构完整', async ({ page }) => {
await page.goto('/core/nav');
const nav = page.locator('.article-nav');
if ((await nav.count()) > 0) {
// 上下篇卡内标题不为空(有内容才显示整卡)
const titles = await page.locator('.article-nav .nav-title').allTextContents();
expect(titles.join('').trim().length, '上下篇卡片存在但标题为空').toBeGreaterThan(0);
}
});
for (const p of PRODUCTS) {
test(`产品/宣传页 ${p} 渲染:正文容器存在且无错误`, async ({ page }) => {
const errors = collectErrors(page);
const resp = await page.goto(p);
expect(resp!.status(), `${p} 返回 ${resp!.status()}`).toBe(200);
await page.waitForTimeout(500);
// 可能是 product 分流(.product-page) 或普通文章(.article-content)
const isProduct = (await page.locator('.product-page').count()) > 0;
const isNormal = (await page.locator('.article-content').count()) > 0;
expect(isProduct || isNormal, `${p} 无任何正文容器`).toBe(true);
if (isProduct) {
// product 全宽:不应有目录/上下篇/文章头部
expect(await page.locator('.toc').count(), 'product 页不应有目录').toBe(0);
expect(await page.locator('.article-nav').count(), 'product 页不应有上下篇').toBe(0);
}
expectNoErrors(errors);
});
}
});
|