- 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
161 lines
7.0 KiB
TypeScript
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: 产品深度浏览旅程完成');
|
|
});
|
|
}); |