frontend-live.spec.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * ================================================================
  3. * 小程序 H5(cfc-frontend uni-app)线上 UI 级 E2E · TC-F-U01 ~ TC-F-U06
  4. * ================================================================
  5. * 目标站点:http://cfc.iwintrue.com/frontend/(uni-app H5,hash 路由)
  6. * 场景来源:用户场景「游客浏览首页 → 五维能量沙盘 → 商品/活动 → TabBar 切换 → 我的(未登录)」
  7. *
  8. * DOM 依据(2026-11-07 实测):
  9. * 落地路由 #/pages/index-home/index,title=首页,
  10. * TabBar 文案:首页 / 健康 / 成长 / 我的(uni-app 原生 tabbar)
  11. *
  12. * 只读约束:游客态浏览,不登录、不下单、不提交表单。
  13. * ================================================================
  14. */
  15. const { test, expect } = require('@playwright/test');
  16. const fs = require('fs');
  17. const path = require('path');
  18. const ART = path.join(__dirname, '..', '..', '_artifacts_live');
  19. if (!fs.existsSync(ART)) fs.mkdirSync(ART, { recursive: true });
  20. /** 采集页面运行期异常与失败请求 */
  21. function watch(page) {
  22. const errors = [];
  23. const failed = [];
  24. page.on('pageerror', (e) => errors.push(String(e).slice(0, 300)));
  25. page.on('requestfailed', (r) => failed.push(`${r.method()} ${r.url().slice(0, 120)}`));
  26. return { errors, failed };
  27. }
  28. /** 点击 TabBar 项(uni-app H5 渲染为 uni-tabbar > .uni-tabbar__item) */
  29. async function clickTab(page, label) {
  30. const inTabbar = page.locator('uni-tabbar .uni-tabbar__item', { hasText: label }).first();
  31. if (await inTabbar.count()) {
  32. await inTabbar.click();
  33. return true;
  34. }
  35. const byText = page.getByText(label, { exact: true }).last();
  36. if (await byText.count()) {
  37. await byText.click();
  38. return true;
  39. }
  40. return false;
  41. }
  42. test.describe('小程序 H5 UI · 游客浏览与导航', () => {
  43. test('TC-F-U01 首页可渲染(标题/落地路由/TabBar 四项)', async ({ page }) => {
  44. const w = watch(page);
  45. const resp = await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
  46. expect(resp, '首页应有 HTTP 响应').not.toBeNull();
  47. expect(resp.status(), '首页状态码应 < 400').toBeLessThan(400);
  48. await page.waitForTimeout(5000);
  49. expect(page.url(), '应落到 hash 路由首页').toMatch(/#\/pages\//);
  50. await expect(page).toHaveTitle(/首页|浠艾福/);
  51. // uni-app 页面根节点已挂载
  52. expect(await page.locator('uni-page, .uni-page').count(), '应渲染 uni-page 根节点').toBeGreaterThan(0);
  53. // TabBar 四项
  54. const tabTexts = await page.locator('uni-tabbar .uni-tabbar__item, .uni-tabbar__label').allInnerTexts();
  55. const joined = tabTexts.join('|').replace(/\s+/g, '');
  56. expect(joined, 'TabBar 应包含 首页/健康/成长/我的').toMatch(/首页/);
  57. expect(joined).toMatch(/健康/);
  58. expect(joined).toMatch(/成长/);
  59. expect(joined).toMatch(/我的/);
  60. await page.screenshot({ path: path.join(ART, 'frontend-01-index.png') });
  61. expect(w.errors, `首页不应有未捕获 JS 异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
  62. });
  63. test('TC-F-U02 首页核心内容渲染(五维沙盘 / 三步引导)', async ({ page }) => {
  64. await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
  65. await page.waitForTimeout(5000);
  66. const body = (await page.locator('body').innerText()).replace(/\s+/g, '');
  67. // 五维能量(身/心/智/行/富)与能量沙盘区块
  68. for (const dim of ['身', '心', '智', '富', '行']) {
  69. expect(body, `首页应出现五维之一「${dim}」`).toContain(dim);
  70. }
  71. expect(body).toMatch(/沙盘|五维/);
  72. expect(body.length, '首页正文应有实质内容').toBeGreaterThan(200);
  73. await page.screenshot({ path: path.join(ART, 'frontend-02-index-content.png') });
  74. });
  75. test('TC-F-U03 TabBar 切换到「健康」正常跳转且无异常', async ({ page }) => {
  76. const w = watch(page);
  77. await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
  78. await page.waitForTimeout(4500);
  79. const before = page.url();
  80. const clicked = await clickTab(page, '健康');
  81. test.skip(!clicked, 'TabBar「健康」不可点(DOM 结构与预期不符),需更新选择器');
  82. await page.waitForTimeout(3500);
  83. expect(page.url(), '切 Tab 后路由应变化').not.toBe(before);
  84. expect(page.url()).toMatch(/#\/pages\//);
  85. const pageCount = await page.locator('uni-page, .uni-page').count();
  86. expect(pageCount, '切换后仍应渲染页面根节点').toBeGreaterThan(0);
  87. await page.screenshot({ path: path.join(ART, 'frontend-03-health.png') });
  88. expect(w.errors, `切 Tab 过程不应有未捕获异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
  89. });
  90. test('TC-F-U04 TabBar 切换到「成长」正常跳转', async ({ page }) => {
  91. const w = watch(page);
  92. await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
  93. await page.waitForTimeout(4500);
  94. const before = page.url();
  95. const clicked = await clickTab(page, '成长');
  96. test.skip(!clicked, 'TabBar「成长」不可点,需更新选择器');
  97. await page.waitForTimeout(3500);
  98. expect(page.url()).not.toBe(before);
  99. await page.screenshot({ path: path.join(ART, 'frontend-04-growth.png') });
  100. expect(w.errors, `切 Tab 过程不应有未捕获异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
  101. });
  102. test('TC-F-U05 未登录访问「我的」应出现登录/注册入口(鉴权引导)', async ({ page }) => {
  103. await page.goto('/frontend/#/pages/mine/index', { waitUntil: 'domcontentloaded' });
  104. await page.waitForTimeout(4500);
  105. const body = (await page.locator('body').innerText()).replace(/\s+/g, '');
  106. const hasLoginEntry = /登录|注册|立即登录/.test(body);
  107. expect(hasLoginEntry, '未登录态「我的」页应提供登录入口').toBeTruthy();
  108. await page.screenshot({ path: path.join(ART, 'frontend-05-mine.png') });
  109. });
  110. test('TC-F-U06 首屏关键资源加载无失败(无 requestfailed)', async ({ page }) => {
  111. const w = watch(page);
  112. await page.goto('/frontend/', { waitUntil: 'networkidle', timeout: 60000 }).catch(() => {});
  113. await page.waitForTimeout(2000);
  114. // 过滤掉已知的媒体/统计类非关键失败不做断言,只统计 js/css 类资源
  115. const critical = w.failed.filter((u) => /\.(js|css)(\?|$)/i.test(u));
  116. expect(critical, `关键静态资源不应加载失败:${critical.join(' ;; ')}`).toHaveLength(0);
  117. });
  118. });