| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- /**
- * ================================================================
- * 小程序 H5(cfc-frontend uni-app)线上 UI 级 E2E · TC-F-U01 ~ TC-F-U06
- * ================================================================
- * 目标站点:http://cfc.iwintrue.com/frontend/(uni-app H5,hash 路由)
- * 场景来源:用户场景「游客浏览首页 → 五维能量沙盘 → 商品/活动 → TabBar 切换 → 我的(未登录)」
- *
- * DOM 依据(2026-11-07 实测):
- * 落地路由 #/pages/index-home/index,title=首页,
- * TabBar 文案:首页 / 健康 / 成长 / 我的(uni-app 原生 tabbar)
- *
- * 只读约束:游客态浏览,不登录、不下单、不提交表单。
- * ================================================================
- */
- const { test, expect } = require('@playwright/test');
- const fs = require('fs');
- const path = require('path');
- const ART = path.join(__dirname, '..', '..', '_artifacts_live');
- if (!fs.existsSync(ART)) fs.mkdirSync(ART, { recursive: true });
- /** 采集页面运行期异常与失败请求 */
- function watch(page) {
- const errors = [];
- const failed = [];
- page.on('pageerror', (e) => errors.push(String(e).slice(0, 300)));
- page.on('requestfailed', (r) => failed.push(`${r.method()} ${r.url().slice(0, 120)}`));
- return { errors, failed };
- }
- /** 点击 TabBar 项(uni-app H5 渲染为 uni-tabbar > .uni-tabbar__item) */
- async function clickTab(page, label) {
- const inTabbar = page.locator('uni-tabbar .uni-tabbar__item', { hasText: label }).first();
- if (await inTabbar.count()) {
- await inTabbar.click();
- return true;
- }
- const byText = page.getByText(label, { exact: true }).last();
- if (await byText.count()) {
- await byText.click();
- return true;
- }
- return false;
- }
- test.describe('小程序 H5 UI · 游客浏览与导航', () => {
- test('TC-F-U01 首页可渲染(标题/落地路由/TabBar 四项)', async ({ page }) => {
- const w = watch(page);
- const resp = await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
- expect(resp, '首页应有 HTTP 响应').not.toBeNull();
- expect(resp.status(), '首页状态码应 < 400').toBeLessThan(400);
- await page.waitForTimeout(5000);
- expect(page.url(), '应落到 hash 路由首页').toMatch(/#\/pages\//);
- await expect(page).toHaveTitle(/首页|浠艾福/);
- // uni-app 页面根节点已挂载
- expect(await page.locator('uni-page, .uni-page').count(), '应渲染 uni-page 根节点').toBeGreaterThan(0);
- // TabBar 四项
- const tabTexts = await page.locator('uni-tabbar .uni-tabbar__item, .uni-tabbar__label').allInnerTexts();
- const joined = tabTexts.join('|').replace(/\s+/g, '');
- expect(joined, 'TabBar 应包含 首页/健康/成长/我的').toMatch(/首页/);
- expect(joined).toMatch(/健康/);
- expect(joined).toMatch(/成长/);
- expect(joined).toMatch(/我的/);
- await page.screenshot({ path: path.join(ART, 'frontend-01-index.png') });
- expect(w.errors, `首页不应有未捕获 JS 异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
- });
- test('TC-F-U02 首页核心内容渲染(五维沙盘 / 三步引导)', async ({ page }) => {
- await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
- await page.waitForTimeout(5000);
- const body = (await page.locator('body').innerText()).replace(/\s+/g, '');
- // 五维能量(身/心/智/行/富)与能量沙盘区块
- for (const dim of ['身', '心', '智', '富', '行']) {
- expect(body, `首页应出现五维之一「${dim}」`).toContain(dim);
- }
- expect(body).toMatch(/沙盘|五维/);
- expect(body.length, '首页正文应有实质内容').toBeGreaterThan(200);
- await page.screenshot({ path: path.join(ART, 'frontend-02-index-content.png') });
- });
- test('TC-F-U03 TabBar 切换到「健康」正常跳转且无异常', async ({ page }) => {
- const w = watch(page);
- await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
- await page.waitForTimeout(4500);
- const before = page.url();
- const clicked = await clickTab(page, '健康');
- test.skip(!clicked, 'TabBar「健康」不可点(DOM 结构与预期不符),需更新选择器');
- await page.waitForTimeout(3500);
- expect(page.url(), '切 Tab 后路由应变化').not.toBe(before);
- expect(page.url()).toMatch(/#\/pages\//);
- const pageCount = await page.locator('uni-page, .uni-page').count();
- expect(pageCount, '切换后仍应渲染页面根节点').toBeGreaterThan(0);
- await page.screenshot({ path: path.join(ART, 'frontend-03-health.png') });
- expect(w.errors, `切 Tab 过程不应有未捕获异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
- });
- test('TC-F-U04 TabBar 切换到「成长」正常跳转', async ({ page }) => {
- const w = watch(page);
- await page.goto('/frontend/', { waitUntil: 'domcontentloaded' });
- await page.waitForTimeout(4500);
- const before = page.url();
- const clicked = await clickTab(page, '成长');
- test.skip(!clicked, 'TabBar「成长」不可点,需更新选择器');
- await page.waitForTimeout(3500);
- expect(page.url()).not.toBe(before);
- await page.screenshot({ path: path.join(ART, 'frontend-04-growth.png') });
- expect(w.errors, `切 Tab 过程不应有未捕获异常:${w.errors.join(' ;; ')}`).toHaveLength(0);
- });
- test('TC-F-U05 未登录访问「我的」应出现登录/注册入口(鉴权引导)', async ({ page }) => {
- await page.goto('/frontend/#/pages/mine/index', { waitUntil: 'domcontentloaded' });
- await page.waitForTimeout(4500);
- const body = (await page.locator('body').innerText()).replace(/\s+/g, '');
- const hasLoginEntry = /登录|注册|立即登录/.test(body);
- expect(hasLoginEntry, '未登录态「我的」页应提供登录入口').toBeTruthy();
- await page.screenshot({ path: path.join(ART, 'frontend-05-mine.png') });
- });
- test('TC-F-U06 首屏关键资源加载无失败(无 requestfailed)', async ({ page }) => {
- const w = watch(page);
- await page.goto('/frontend/', { waitUntil: 'networkidle', timeout: 60000 }).catch(() => {});
- await page.waitForTimeout(2000);
- // 过滤掉已知的媒体/统计类非关键失败不做断言,只统计 js/css 类资源
- const critical = w.failed.filter((u) => /\.(js|css)(\?|$)/i.test(u));
- expect(critical, `关键静态资源不应加载失败:${critical.join(' ;; ')}`).toHaveLength(0);
- });
- });
|