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