Files
msh-system/tests/e2e/tool-main.spec.ts

669 lines
30 KiB
TypeScript
Raw Normal View History

/**
* E2E Tests: pages/tool_main/index
*
* :
* TC-001
* TC-002
* TC-003 + UI
* TC-004
* TC-005 AI
* TC-006
* TC-007 Tab
* TC-008
* TC-009
* TC-010
* TC-011
* TC-012
* TC-013 JS
* TC-014 tool_main/community Tab
*/
import { test, expect, Page, APIRequestContext } from '@playwright/test';
import * as fs from 'fs';
// ─── 常量 ───────────────────────────────────────────────────────────────────
const BASE = 'http://localhost:8080';
const API_BASE = 'http://127.0.0.1:20822';
const LOGIN_URL = `${BASE}/#/pages/users/login/index`;
const TOOL_MAIN = 'pages/tool_main/index';
const PHONE = '18621813282';
const PASSWORD = 'A123456';
/** 截图目录 */
const SCREENSHOT_DIR = 'tests/e2e/screenshots';
// UniApp Cache 使用的 expire 后缀
const EXPIRE_SUFFIX = '_expire_2019_12_17_18_44';
// 设为 Year 2099 确保不过期 (Unix timestamp in seconds)
const FAR_FUTURE = '4102444800';
// ─── 全局 Auth 状态 ──────────────────────────────────────────────────────────
let AUTH_TOKEN = '';
let AUTH_UID = 0;
// ─── 工具函数 ─────────────────────────────────────────────────────────────────
/** 通过后端 API 登录,获取 token */
async function apiLogin(request: APIRequestContext): Promise<{ token: string; uid: number }> {
const res = await request.post(`${API_BASE}/api/front/login`, {
data: { account: PHONE, password: PASSWORD },
headers: { 'Content-Type': 'application/json' },
});
const body = await res.json();
if (body.code !== 200 || !body.data?.token) {
throw new Error(`API login failed: ${JSON.stringify(body)}`);
}
return { token: body.data.token, uid: body.data.uid };
}
/**
* page localStorage token
* 使 UniApp Cache/Vuex
* page.goto()
*/
async function injectAuth(page: Page, token: string, uid: number): Promise<void> {
await page.addInitScript(
({ t, u, suffix, farFuture }) => {
localStorage.setItem('LOGIN_STATUS_TOKEN', t);
localStorage.setItem(`LOGIN_STATUS_TOKEN${suffix}`, farFuture);
localStorage.setItem('UID', u.toString());
localStorage.setItem(`UID${suffix}`, farFuture);
},
{ t: token, u: uid, suffix: EXPIRE_SUFFIX, farFuture: FAR_FUTURE },
);
}
/** 导航到指定 UniApp hash 路由,等待 Vue 组件挂载 */
async function goto(page: Page, route: string, waitMs = 2500): Promise<void> {
await page.goto(`${BASE}/#/${route}`);
await page.waitForTimeout(waitMs);
}
/** 确保截图目录存在 */
function ensureScreenshotDir(): void {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}
/** 截图(不中断测试) */
async function screenshot(page: Page, name: string): Promise<void> {
await page.screenshot({
path: `${SCREENSHOT_DIR}/${name}.png`,
fullPage: false,
}).catch(() => {});
}
// ─── 全局 beforeAll / beforeEach ─────────────────────────────────────────────
test.beforeAll(async ({ request }) => {
ensureScreenshotDir();
try {
const { token, uid } = await apiLogin(request);
AUTH_TOKEN = token;
AUTH_UID = uid;
console.log(`[Auth] Logged in as uid=${uid}`);
} catch (e) {
console.error('[Auth] API login failed, tests may not work correctly:', e);
}
});
test.beforeEach(async ({ page }) => {
// 监听 JS 错误(记录但不中断)
page.on('pageerror', (err) => {
if (!err.message.includes('sockjs') && !err.message.includes('Unexpected end of stream')) {
console.warn(`[PageError] ${err.message}`);
}
});
// 忽略 hot-reload sockjs 网络失败
page.on('requestfailed', (req) => {
if (!req.url().includes('sockjs-node')) {
console.warn(`[NetworkFail] ${req.method()} ${req.url()}`);
}
});
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-001 页面加载 主界面核心元素可见(已登录)
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-001 页面加载:主界面核心元素可见', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
// 用户健康卡片
await expect(page.locator('.user-card')).toBeVisible({ timeout: 12_000 });
// 四大功能入口
await expect(page.locator('.function-grid')).toBeVisible();
await expect(page.getByText('食谱计算器').first()).toBeVisible();
await expect(page.getByText('AI营养师').first()).toBeVisible();
await expect(page.getByText('食物百科').first()).toBeVisible();
await expect(page.getByText('营养知识').first()).toBeVisible();
// 精选食谱 & 健康知识区块
await expect(page.getByText('精选食谱').first()).toBeVisible();
await expect(page.getByText('健康知识').first()).toBeVisible();
// 营养方案卡片
await expect(page.locator('.promotion-card')).toBeVisible();
await screenshot(page, 'tc001-tool-main');
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-002 用户卡片 已登录时不显示"请点击登录"
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-002 用户卡片:已登录时不显示"请点击登录"', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await expect(page.locator('.user-card')).toBeVisible({ timeout: 12_000 });
// 登录后不应显示 "请点击登录"
const loginPrompt = page.locator('.user-name').filter({ hasText: '请点击登录' });
await expect(loginPrompt).toHaveCount(0);
// 打卡按钮可见
await expect(page.locator('.checkin-btn')).toBeVisible();
await screenshot(page, 'tc002-user-card-logged-in');
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-003 登录流程 手机号+密码 UI 登录测试
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-003 登录流程:手机号+密码登录成功', async ({ page }) => {
// 本用例不注入 auth测试真实登录流程
await page.goto(LOGIN_URL);
await page.waitForTimeout(3000);
await screenshot(page, 'tc003-login-initial');
// 登录页默认显示 SMS 模式current=1
// 点击"账号登录"切换到密码模式current=0
const accountLoginBtn = page.getByText('账号登录').first();
await accountLoginBtn.waitFor({ state: 'visible', timeout: 10_000 });
await accountLoginBtn.click();
await page.waitForTimeout(1000);
await screenshot(page, 'tc003-login-password-mode');
// 密码模式下phone + password 输入框
const phoneInput = page.locator('input[type="number"]').first();
const pwdInput = page.locator('input[type="password"]').first();
await phoneInput.waitFor({ state: 'visible', timeout: 8_000 });
await phoneInput.click();
await phoneInput.fill(PHONE);
await pwdInput.click();
await pwdInput.fill(PASSWORD);
// 勾选协议checkbox-group
await page.locator('.checkgroup').first().click().catch(() => {});
await page.waitForTimeout(500);
await screenshot(page, 'tc003-login-filled');
// 点击"登录"current=0 时显示的 .logon 按钮)
// 使用 force:true 绕过可能的 overlay 拦截
await page.locator('.logon').first().click({ force: true });
await page.waitForTimeout(4000);
const urlAfterLogin = page.url();
const stillOnLogin = urlAfterLogin.includes('users/login');
if (stillOnLogin) {
await screenshot(page, 'tc003-login-failed-debug');
}
expect(stillOnLogin, '登录成功后应离开登录页').toBe(false);
await screenshot(page, 'tc003-login-success');
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-004 食谱计算器 进入页面并填写参数
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-004 食谱计算器:进入页面并填写参数', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 点击食谱计算器卡片
await page.locator('.calculator').click();
await page.waitForTimeout(3000);
// 验证进入计算器页面(取第一个匹配元素,避免 strict mode 报错)
const formContainer = page.locator('.calculator-page').first();
await formContainer.waitFor({ state: 'visible', timeout: 10_000 });
await screenshot(page, 'tc004-calculator-entered');
// 选择性别:男
const maleOption = page.locator('.radio-item').filter({ hasText: '男' }).first();
await maleOption.click();
await page.waitForTimeout(500);
// 填写年龄
const ageInput = page.locator('input').filter({ has: page.locator(':scope') }).nth(0);
// UniApp input: 按顺序查找 type="number" inputs
const numberInputs = page.locator('input[type="number"], input.uni-input-input');
const ageI = numberInputs.nth(0);
const htI = numberInputs.nth(1);
const wtI = numberInputs.nth(2);
if (await ageI.count() > 0) {
await ageI.fill('45');
await htI.fill('170');
await wtI.fill('65');
await page.waitForTimeout(500);
}
await screenshot(page, 'tc004-calculator-filled');
// 点击计算按钮(如有)
const calcBtn = page.locator('.submit-btn, .calc-btn, button').filter({ hasText: /计算|确定|提交/ }).first();
if (await calcBtn.count() > 0) {
await calcBtn.click();
await page.waitForTimeout(3000);
await screenshot(page, 'tc004-calculator-result');
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-005 AI 营养师 进入页面并发送一条消息
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-005 AI营养师进入页面并发送一条消息', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 点击 AI营养师 卡片
await page.locator('.ai-nutritionist').click();
await page.waitForTimeout(3500);
// 验证进入 AI 营养师页面(含输入区或消息列表)
const pageIndicator = page.locator(
'.ai-page, .chat-page, .message-list, .input-bar, .text-input-container, .chat-container',
).first();
await pageIndicator.waitFor({ state: 'visible', timeout: 12_000 }).catch(async () => {
// fallback: 只要 URL 变化即可
expect(page.url()).toContain('ai-nutritionist');
});
await screenshot(page, 'tc005-ai-entered');
// 找到文本输入框
const textInput = page
.locator('input[type="text"], textarea, input.text-input, .chat-input input')
.first();
if (await textInput.isVisible().catch(() => false)) {
await textInput.fill('你好,请问什么食物富含蛋白质?');
await screenshot(page, 'tc005-ai-typed');
// 点击发送按钮
const sendBtn = page.locator('.send-btn, .submit-btn')
.filter({ hasText: /发送/ })
.first();
if (await sendBtn.isVisible().catch(() => false)) {
await sendBtn.click();
await page.waitForTimeout(3000);
await screenshot(page, 'tc005-ai-sent');
}
} else {
// 快捷问题按钮:点击第一条快捷问题
const quickBtn = page.locator('.quick-question, .quick-btn').first();
if (await quickBtn.isVisible().catch(() => false)) {
await quickBtn.click();
await page.waitForTimeout(3000);
await screenshot(page, 'tc005-ai-quick-question');
}
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-006 食物百科 进入并搜索、切换分类
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-006 食物百科:进入并搜索"鸡肉",切换分类', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 点击食物百科卡片
await page.locator('.food-encyclopedia').click();
await page.waitForTimeout(3000);
// 验证进入食物百科页面
const foodPage = page.locator('.food-page, .search-container, .search-box').first();
await foodPage.waitFor({ state: 'visible', timeout: 10_000 });
await screenshot(page, 'tc006-food-entered');
// 搜索框UniApp input → 找到 type="text" 的 input
const searchInput = page.locator('input[type="text"], input[type="search"], .search-input input, input').first();
if (await searchInput.isVisible().catch(() => false)) {
await searchInput.click();
await searchInput.fill('鸡肉');
await page.waitForTimeout(2000);
await screenshot(page, 'tc006-food-search');
}
// 切换分类:肉蛋类
const meatCategory = page.locator('.category-item').filter({ hasText: '肉蛋类' }).first();
if (await meatCategory.isVisible().catch(() => false)) {
await meatCategory.click();
await page.waitForTimeout(1500);
await expect(meatCategory).toHaveClass(/active/);
await screenshot(page, 'tc006-food-meat-category');
}
// 点击第一个食物卡片进入详情
const firstFood = page.locator('.food-item, .food-card, .food-list-item').first();
if (await firstFood.isVisible().catch(() => false)) {
await firstFood.click();
await page.waitForTimeout(2500);
await screenshot(page, 'tc006-food-detail');
await page.goBack();
await page.waitForTimeout(2000);
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-007 营养知识 进入并切换 Tab
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-007 营养知识:进入并切换 Tab', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 点击营养知识卡片
await page.locator('.nutrition-knowledge').click();
await page.waitForTimeout(3000);
// 验证进入营养知识页面(含 Tab
const tabContainer = page.locator('.tab-container, .tab-item').first();
await tabContainer.waitFor({ state: 'visible', timeout: 10_000 });
await screenshot(page, 'tc007-nutrition-entered');
// 默认 Tab营养素active
const nutrientsTab = page.locator('.tab-item').filter({ hasText: '营养素' }).first();
await expect(nutrientsTab).toBeVisible();
await expect(nutrientsTab).toHaveClass(/active/);
// 切换到"饮食指南"
const guideTab = page.locator('.tab-item').filter({ hasText: '饮食指南' }).first();
await guideTab.click();
await page.waitForTimeout(1000);
await expect(guideTab).toHaveClass(/active/);
await screenshot(page, 'tc007-nutrition-guide');
// 切换到"科普文章"
const articlesTab = page.locator('.tab-item').filter({ hasText: '科普文章' }).first();
await articlesTab.click();
await page.waitForTimeout(1000);
await expect(articlesTab).toHaveClass(/active/);
await screenshot(page, 'tc007-nutrition-articles');
// 返回营养素 Tab 并点击第一条进入详情
await nutrientsTab.click();
await page.waitForTimeout(1000);
const firstNutrient = page.locator('.nutrient-card').first();
if (await firstNutrient.isVisible().catch(() => false)) {
await firstNutrient.click();
await page.waitForTimeout(2500);
await screenshot(page, 'tc007-nutrient-detail');
await page.goBack();
await page.waitForTimeout(2000);
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-008 打卡功能 进入打卡页并查看状态
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-008 打卡功能:进入打卡页并查看状态', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
const checkinBtn = page.locator('.checkin-btn').first();
await checkinBtn.waitFor({ state: 'visible', timeout: 12_000 });
await checkinBtn.click();
await page.waitForTimeout(3000);
// 验证进入打卡页面
const checkinPage = page.locator('.checkin-page, .checkin-card').first();
await checkinPage.waitFor({ state: 'visible', timeout: 10_000 });
// 积分显示
await expect(page.locator('.points-text')).toBeVisible();
// 打卡任务列表
await expect(page.locator('.task-list')).toBeVisible();
await screenshot(page, 'tc008-checkin-page');
// 积分规则按钮(位于导航栏下方,需 force click 绕过 uni-page-head 拦截)
const rulesBtn = page.locator('.rules-btn-top').first();
if (await rulesBtn.isVisible().catch(() => false)) {
await rulesBtn.scrollIntoViewIfNeeded();
await rulesBtn.click({ force: true });
await page.waitForTimeout(1500);
await screenshot(page, 'tc008-checkin-rules');
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-009 精选食谱 列表渲染并可进入详情
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-009 精选食谱:列表渲染并可进入详情', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 精选食谱区块标题
await expect(page.getByText('精选食谱').first()).toBeVisible({ timeout: 10_000 });
await screenshot(page, 'tc009-recipe-list');
// 若有食谱条目,点击第一条
const firstRecipe = page.locator('.recipe-item').first();
if (await firstRecipe.isVisible().catch(() => false)) {
await expect(firstRecipe.locator('.recipe-title')).toBeVisible();
await firstRecipe.click();
await page.waitForTimeout(2500);
await screenshot(page, 'tc009-recipe-detail');
await page.goBack();
await page.waitForTimeout(2000);
}
// 点击""查看更多(应弹出"功能开发中" toast
const moreBtn = page
.locator('.section-header')
.filter({ has: page.locator('.section-title', { hasText: '精选食谱' }) })
.locator('.section-more')
.first();
if (await moreBtn.isVisible().catch(() => false)) {
await moreBtn.click();
await page.waitForTimeout(1500);
await screenshot(page, 'tc009-recipe-list-wip');
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-010 营养方案卡片 点击"立即领取福利"跳转
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-010 营养方案:点击"立即领取福利"跳转', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
const promoCard = page.locator('.promotion-card');
await promoCard.waitFor({ state: 'visible', timeout: 12_000 });
// 验证文案
await expect(promoCard.locator('.promotion-title')).toContainText('慢生活营养专家');
await expect(promoCard.locator('.promotion-btn')).toContainText('立即领取福利');
await screenshot(page, 'tc010-promo-before-click');
// 点击跳转
await promoCard.click();
await page.waitForTimeout(3000);
await screenshot(page, 'tc010-welcome-gift');
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-011 健康知识 列表渲染并可进入详情
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-011 健康知识:列表渲染并可进入详情', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 健康知识区块
await expect(page.getByText('健康知识').first()).toBeVisible({ timeout: 10_000 });
await screenshot(page, 'tc011-knowledge-list');
// 点击第一条知识条目
const firstKnowledge = page.locator('.knowledge-item').first();
if (await firstKnowledge.isVisible().catch(() => false)) {
await expect(firstKnowledge.locator('.knowledge-title')).toBeVisible();
await firstKnowledge.click();
await page.waitForTimeout(2500);
await screenshot(page, 'tc011-knowledge-detail');
await page.goBack();
await page.waitForTimeout(2000);
}
// 点击""(更多健康知识)→ 应导航到营养知识页面
const moreBtn = page
.locator('.section-header')
.filter({ has: page.locator('.section-title', { hasText: '健康知识' }) })
.locator('.section-more')
.first();
if (await moreBtn.isVisible().catch(() => false)) {
await moreBtn.click();
await page.waitForTimeout(2500);
// 营养知识页面有 tab-container
await expect(page.locator('.tab-container')).toBeVisible({ timeout: 8_000 });
await screenshot(page, 'tc011-knowledge-more');
}
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-012 下拉刷新 刷新后核心元素仍可见
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-012 下拉刷新:刷新后核心元素仍可见', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 3000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 模拟下拉刷新(页面顶部向下滑动)
await page.mouse.move(187, 80);
await page.mouse.down();
await page.mouse.move(187, 320, { steps: 12 });
await page.mouse.up();
await page.waitForTimeout(4000);
// 核心元素仍应可见
await expect(page.locator('.function-grid')).toBeVisible({ timeout: 12_000 });
await expect(page.getByText('食谱计算器').first()).toBeVisible();
await screenshot(page, 'tc012-pull-refresh');
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-013 页面错误检查 主界面无未捕获 JS 错误
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-013 页面错误检查:主界面无严重 JS 错误', async ({ page }) => {
const errors: string[] = [];
// 收集严重错误(排除已知开发环境噪声)
page.on('pageerror', (err) => {
const msg = err.message;
if (
!msg.includes('sockjs') &&
!msg.includes('Unexpected end of stream') &&
!msg.includes('192.168.110') &&
!msg.includes('ResizeObserver')
) {
errors.push(msg);
}
});
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, TOOL_MAIN, 4000);
await page.locator('.user-card').waitFor({ state: 'visible', timeout: 12_000 });
// 等待页面稳定
await page.waitForTimeout(2000);
await screenshot(page, 'tc013-error-check');
if (errors.length > 0) {
console.warn('[TC-013] Captured JS errors:', errors);
}
// 允许轻微错误,严重错误(超过 3 条不同错误)才失败
expect(errors.length, `未捕获 JS 错误: ${errors.join('\n')}`).toBeLessThan(3);
});
// ═══════════════════════════════════════════════════════════════════════════════
// TC-014 社区子页 tool_main/community 打卡社区
// ═══════════════════════════════════════════════════════════════════════════════
test('TC-014 社区子页:打卡社区 Tab 与列表', async ({ page }) => {
await injectAuth(page, AUTH_TOKEN, AUTH_UID);
await goto(page, 'pages/tool_main/community', 3000);
// 社区页容器
const communityPage = page.locator('.community-page').first();
await communityPage.waitFor({ state: 'visible', timeout: 12_000 });
// Tab 导航:推荐 / 最新 / 关注 / 热门
const tabNav = page.locator('.tab-nav').first();
await expect(tabNav).toBeVisible();
await expect(page.getByText('推荐').first()).toBeVisible();
await expect(page.getByText('最新').first()).toBeVisible();
await expect(page.getByText('关注').first()).toBeVisible();
await expect(page.getByText('热门').first()).toBeVisible();
await screenshot(page, 'tc014-community-tabs');
// 默认「推荐」为 active可切换到「最新」
const latestTab = page.locator('.tab-item').filter({ hasText: '最新' }).first();
await latestTab.click();
await page.waitForTimeout(1500);
await expect(latestTab).toHaveClass(/active/);
// 有内容则显示 post-grid无内容则显示 empty-container
const hasPosts = await page.locator('.post-grid').isVisible().catch(() => false);
const isEmpty = await page.locator('.empty-container').isVisible().catch(() => false);
expect(hasPosts || isEmpty || await page.locator('.loading-container').isVisible().catch(() => false)).toBe(true);
if (hasPosts) {
const firstCard = page.locator('.post-card').first();
if (await firstCard.isVisible().catch(() => false)) {
await firstCard.click();
await page.waitForTimeout(2500);
await screenshot(page, 'tc014-post-detail');
await page.goBack();
await page.waitForTimeout(2000);
}
}
await screenshot(page, 'tc014-community-done');
});