更新说明文档
大石头 authored at 2022-04-16 15:30:05
2.08 KiB
YuQue
import { test } from '@playwright/test';
import fs from 'node:fs';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

/**
 * 阶段 B:从生产站点收集真实链接清单,输出 tests/.urls.json
 * 运行:npm run test:collect
 */
const OUT = fileURLToPath(new URL('./.urls.json', import.meta.url));

test('收集站点真实链接', async ({ page }) => {
  const collect = (sel: String) =>
    page.$$eval(String(sel), (as) =>
      as
        .map((a) => ({ href: a.getAttribute('href') || '', text: (a.textContent || '').trim().slice(0, 40) }))
        .filter((x) => x.href && !x.href.startsWith('http') && !x.href.startsWith('javascript')),
    );

  const out: Record<String, any> = {};
  await page.goto('/');
  await page.waitForLoadState('domcontentloaded');

  // 顶部导航项(分类/外链)
  out.nav = await collect('.main-nav a');
  // 核心产品卡片(推荐树 → 产品页候选)
  out.projects = await collect('.project-grid a');
  // 分类网格(book 表)
  out.books = await collect('.book-grid a');
  // 最新文章
  out.latests = await collect('.latest-list a');
  // 页尾链接
  out.footer = await collect('.site-footer a');

  // 从导航/页脚/产品提取候选分类 code(形如 /core 单段)
  const codes = new Set<String>();
  [...out.nav, ...out.books].forEach((x: any) => {
    const m = /^\/([^/\-?]+)$/.exec(x.href);
    if (m) codes.add(m[1]);
  });

  // 每分类列表页取前 3 篇文章链接
  out.categoryArticles = {};
  for (const code of codes) {
    const hrefs = await page.goto(`/${code}`).then(() =>
      page.$$eval('.article-list a.article-card', (as) =>
        as.slice(0, 3).map((a) => a.getAttribute('href') || ''),
      ),
    ).catch(() => []);
    out.categoryArticles[String(code)] = hrefs.filter(Boolean);
  }

  fs.mkdirSync(dirname(OUT), { recursive: true });
  fs.writeFileSync(OUT, JSON.stringify(out, null, 2), 'utf-8');
  // eslint-disable-next-line no-console
  console.log('已输出', OUT);
  // eslint-disable-next-line no-console
  console.log('分类 code:', [...codes].join(', '));
});