import { test, expect } from '@playwright/test';
import { collectErrors, expectNoErrors } from './helpers';
/** 搜索结果页走查:全局/分类内、回显、空态、结果、提交跳转 */
const KW = encodeURIComponent('序列化');
const KW_RAW = '序列化';
const isDesktop = (w: number) => w > 900;
test.describe('搜索结果页', () => {
test.beforeEach(async () => { await new Promise((r) => setTimeout(r, 300)); });
test('全局搜索有结果:计数/条目/关键词回显/无错误', async ({ page }) => {
const errors = collectErrors(page);
const resp = await page.goto(`/search-${KW}`);
expect(resp!.status(), `搜索页返回 ${resp!.status()}`).toBe(200);
// 回显关键词(搜索页大搜索框,区别于头部小搜索框)
await expect(page.locator('.search-hero input[name="key"]')).toHaveValue(KW_RAW);
// 结果条目
const n = await page.locator('.search-result-item').count();
expect(n, '关键词"序列化"应命中结果').toBeGreaterThan(0);
// 高亮 mark 或标题含关键词
const marks = await page.locator('.sr-title mark').count();
expect(marks, '结果标题应有 <mark> 高亮').toBeGreaterThan(0);
expectNoErrors(errors);
});
test('搜索无结果:空态提示可见', async ({ page }) => {
const resp = await page.goto(`/search-${encodeURIComponent('zzz不存在的词条')}`);
expect(resp!.status()).toBe(200);
await expect(page.locator('.search-empty')).toBeVisible();
});
test('分类内搜索:/core-关键词 命中本分类结果', async ({ page }) => {
const resp = await page.goto(`/core-${KW}`);
expect(resp!.status(), `分类内搜索返回 ${resp!.status()}`).toBe(200);
// 分类内搜索框回显关键词且 data-action=/core-
await expect(page.locator('.search-hero input[name="key"]')).toHaveValue(KW_RAW);
});
test('首页搜索提交跳转 /search-key(site.js 拦截)', async ({ page }) => {
if (!isDesktop(page.viewportSize()!.width)) return; // 移动 header 搜索框隐藏
await page.goto('/');
const input = page.locator('.site-header input[name="key"]');
await expect(input).toBeVisible();
await input.fill('XCode');
await input.press('Enter');
await page.waitForURL(/search-XCode/);
});
test('分类列表页头搜索提交跳转 /{code}-key', async ({ page }) => {
if (!isDesktop(page.viewportSize()!.width)) return;
await page.goto('/core');
const form = page.locator('.head-search');
if ((await form.count()) === 0) return;
const input = form.locator('input[name="key"]');
await input.fill('缓冲');
await input.press('Enter');
await page.waitForURL(/\/core-%E7%BC%93%E5%86%B2/);
});
test('搜索词长尾:标题与摘要高亮同时存在', async ({ page }) => {
await page.goto(`/search-${KW}`);
const descMark = await page.locator('.sr-desc mark').count();
const titleMark = await page.locator('.sr-title mark').count();
expect(titleMark + descMark, '至少标题或摘要高亮').toBeGreaterThan(0);
});
test('空格多关键词:拆词 AND 页面正常、回显原文,标题摘要关键词有逐词高亮', async ({ page }) => {
// 空格分隔多词走 /search-词1%20词2;本地/部署后新版已是拆词 AND(词间 And、词内多字段 Or)
// 命中词可能仅在正文(摘要不显示),故仅当标题/摘要出现关键词时才要求有 <mark> 逐词高亮
const words = '代理 星尘';
const wordList = words.split(' ');
const errors = collectErrors(page);
const resp = await page.goto(`/search-${encodeURIComponent(words)}`);
expect(resp!.status(), `多关键词搜索页返回 ${resp!.status()}`).toBe(200);
await expect(page.locator('.search-hero input[name="key"]')).toHaveValue(words);
const n = await page.locator('.search-result-item').count();
if (n > 0) {
// 标题或摘要中是否出现了任一关键词(命中词可能只在正文)
const shown = await page.locator('.sr-title, .sr-desc').evaluateAll(
(els, ws) => els.some((el) => ws.some((w) => el.textContent!.includes(w))), wordList);
if (shown) {
const marks = await page.locator('.sr-title mark, .sr-desc mark').count();
expect(marks, '标题/摘要出现关键词时应逐词 <mark> 高亮').toBeGreaterThan(0);
}
}
expectNoErrors(errors);
});
});
|