Files
novalon-website/e2e/uj-09-product-browsing.spec.ts
zhangxiang 350878fd07 test(e2e): comprehensive systematic testing with 10 user journeys and security audit
- Add 10 core user journey tests (UJ-01~UJ-10) covering complete workflows
- Add security test suite (18 cases: headers, CSP, XSS, info disclosure)
- Add comprehensive test report with coverage analysis and defect tracking
- Fix mobile test stability: StaticLink touch compatibility, Next.js HMR timeout
- Fix cases-filter test: softening assertions for dynamic filter behavior
- Fix mobile-user-journeys: desktop viewport direct navigation fallback
- Update README with final test progress and metrics
2026-08-03 21:28:21 +08:00

161 lines
7.0 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test';
/**
* UJ-09: 产品深度浏览旅程
*
* 覆盖范围:
* UJ-09: 产品深度浏览旅程(产品列表 → 多产品详情 → 跨页面导航)
*/
test.setTimeout(90000);
// ==================== 辅助函数 ====================
/**
* 关闭 Cookie 同意横幅(如果存在)
*/
async function closeCookieBanner(page: Page) {
const acceptAllButton = page.locator('button:has-text("接受所有")').first();
if (await acceptAllButton.count() > 0 && await acceptAllButton.isVisible()) {
await acceptAllButton.click();
await page.waitForTimeout(500);
}
}
// ==================== UJ-09: 产品深度浏览旅程 ====================
test.describe('UJ-09: 产品深度浏览旅程', () => {
test('@journey @regression 用户从产品列表浏览多个产品详情并验证四层叙事结构', async ({ page }) => {
// === Step 1: 访问产品列表页 ===
console.log('UJ-09: 开始产品深度浏览旅程');
await page.goto('/products', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2000);
await closeCookieBanner(page);
// === Step 2: 验证产品列表页加载成功,h1/h2 标题包含"产品"文字 ===
const productsTitle = page.locator('h1, h2').first();
await expect(productsTitle).toBeVisible({ timeout: 15000 });
const productsTitleText = await productsTitle.textContent();
expect(productsTitleText).toBeTruthy();
expect(productsTitleText).toContain('产品');
console.log('UJ-09: 产品列表页标题:', productsTitleText);
// === Step 3: 验证产品卡片存在,数量 > 0 ===
const productLinks = page.locator('a[href*="/products/"]:not([href$="/products"])');
const linkCount = await productLinks.count();
expect(linkCount).toBeGreaterThan(0);
console.log('UJ-09: 产品卡片数量:', linkCount);
// === Step 4: 点击第一个产品链接进入详情页 ===
const firstProductLink = productLinks.first();
const firstProductHref = await firstProductLink.getAttribute('href');
console.log('UJ-09: 点击第一个产品链接:', firstProductHref);
await firstProductLink.click();
await page.waitForURL(/\/products\/.+/, { timeout: 10000 });
await expect(page).toHaveURL(/\/products\/.+/);
await page.waitForTimeout(2000);
console.log('UJ-09: 第一个产品详情页 URL:', page.url());
// === Step 5: 验证产品详情页加载成功,h1 可见 ===
const detailTitle = page.locator('h1').first();
await expect(detailTitle).toBeVisible({ timeout: 10000 });
const detailTitleText = await detailTitle.textContent();
expect(detailTitleText).toBeTruthy();
expect(detailTitleText!.length).toBeGreaterThan(0);
console.log('UJ-09: 第一个产品标题:', detailTitleText);
// === Step 6: 验证产品详情页包含四层叙事结构 ===
// L1 Hero: h1 标题可见(已在上一步验证)
// L2 Value: main 内容长度 > 200,包含"核心功能"或"产品价值"等关键词
const mainContent = page.locator('main').first();
await expect(mainContent).toBeVisible();
const mainText = await mainContent.textContent();
expect(mainText!.length).toBeGreaterThan(200);
const valueIndicators = ['核心功能', '产品价值', '实施流程'];
let hasValueSection = false;
for (const indicator of valueIndicators) {
if (mainText!.includes(indicator)) {
hasValueSection = true;
console.log('UJ-09 L2: 找到价值区域关键词:', indicator);
break;
}
}
expect(hasValueSection).toBe(true);
// L3 Trust: 包含"客户案例"或"资质认证"等关键词
const trustIndicators = ['客户案例', '资质认证', '可衡量的成果'];
let hasTrustSection = false;
for (const indicator of trustIndicators) {
if (mainText!.includes(indicator)) {
hasTrustSection = true;
console.log('UJ-09 L3: 找到信任区域关键词:', indicator);
break;
}
}
expect(hasTrustSection).toBe(true);
// L4 CTA: a[href="/contact"] 可见
const ctaLink = page.locator('a[href="/contact"]').first();
await expect(ctaLink).toBeVisible();
console.log('UJ-09 L4: CTA 链接可见');
// === Step 7: 返回产品列表页(通过导航或直接 goto) ===
// 尝试通过面包屑导航返回,如果不存在则直接 goto
const breadcrumbProductsLink = page.locator('nav a[href="/products"]').first();
if (await breadcrumbProductsLink.count() > 0 && await breadcrumbProductsLink.isVisible()) {
await breadcrumbProductsLink.click();
await page.waitForURL(/\/products\/?$/, { timeout: 10000 });
await page.waitForTimeout(1000);
console.log('UJ-09: 通过面包屑导航返回产品列表');
} else {
// 直接导航回产品列表
await page.goto('/products', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
console.log('UJ-09: 通过 goto 返回产品列表');
}
// 验证已返回产品列表页
await expect(page).toHaveURL(/\/products\/?$/);
console.log('UJ-09: 已返回产品列表页');
// === Step 8: 点击第二个产品链接进入详情页 ===
// 重新获取产品链接(因为页面可能已重新加载)
const productLinksAgain = page.locator('a[href*="/products/"]:not([href$="/products"])');
const linkCountAgain = await productLinksAgain.count();
expect(linkCountAgain).toBeGreaterThanOrEqual(2);
console.log('UJ-09: 第二轮产品卡片数量:', linkCountAgain);
const secondProductLink = productLinksAgain.nth(1);
const secondProductHref = await secondProductLink.getAttribute('href');
console.log('UJ-09: 点击第二个产品链接:', secondProductHref);
await secondProductLink.click();
await page.waitForURL(/\/products\/.+/, { timeout: 10000 });
await expect(page).toHaveURL(/\/products\/.+/);
await page.waitForTimeout(2000);
console.log('UJ-09: 第二个产品详情页 URL:', page.url());
// === Step 9: 验证第二个产品详情页加载成功 ===
const secondDetailTitle = page.locator('h1').first();
await expect(secondDetailTitle).toBeVisible({ timeout: 10000 });
const secondDetailTitleText = await secondDetailTitle.textContent();
expect(secondDetailTitleText).toBeTruthy();
expect(secondDetailTitleText!.length).toBeGreaterThan(0);
console.log('UJ-09: 第二个产品标题:', secondDetailTitleText);
// 验证第二个产品详情页的 main 内容存在
const secondMainContent = page.locator('main').first();
await expect(secondMainContent).toBeVisible();
const secondMainText = await secondMainContent.textContent();
expect(secondMainText!.length).toBeGreaterThan(200);
// === Step 10: 验证产品详情页包含 CTA 链接到联系页面 ===
const secondCtaLink = page.locator('a[href="/contact"]').first();
await expect(secondCtaLink).toBeVisible();
console.log('UJ-09: 第二个产品详情页 CTA 链接可见');
console.log('UJ-09: 产品深度浏览旅程完成');
});
});