Files
gym-manage/gym-manage-uniapp/e2e/specs/member-full-flow.spec.js
T

540 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* P2 - 会员端完整流程测试
* 流程:注册登录 → 查询团课 → 预约"流瑜伽" → 扫码签到
*
* 前置条件:
* - 微信开发者工具已打开,CLI 端口 45869
* - 后端服务 http://192.168.110.64:8084 已启动
* - "流瑜伽"课程 (id=2) 已在数据库中存在
* - 二维码文件 d:\Work\BIG_project\week2\base14-update-test\gym-manage\流瑜伽.png 可用
*/
const {
launchMiniProgram,
connectMiniProgram,
waitForPage,
screenshot,
switchTab
} = require('../helpers/automator');
const api = require('../helpers/api-helper');
const SearchPage = require('../pages/SearchPage');
const CourseDetailPage = require('../pages/CourseDetailPage');
const MyCoursesPage = require('../pages/MyCoursesPage');
const HomePage = require('../pages/HomePage');
const ProfilePage = require('../pages/ProfilePage');
// ── 二维码数据(解码自 流瑜伽.png)──
const QR_CODE_DATA = JSON.stringify({
courseName: '流瑜伽',
courseType: '1',
maxMembers: 20,
description: '体式之间流畅串联,如行云流水般一气呵成。以拜日式为根基,结合站姿、平衡和扭转,提升力量与柔韧的整合能力。',
startTime: '2026-07-24T10:00',
location: '瑜伽室A',
id: '2',
endTime: '2026-07-24T11:00',
coachId: '11'
});
const COURSE_ID = 2;
const COURSE_NAME = '流瑜伽';
let miniProgram;
let authToken = null;
let memberId = null;
describe('P2 - 会员端完整流程测试', () => {
// ════════════════════════════════════════════════════
// 阶段0:环境准备 & 并行 API 验证
// ════════════════════════════════════════════════════
beforeAll(async () => {
console.log('════════════════════════════════════════════');
console.log('P2 - 会员端完整流程测试开始');
console.log(' 课程: 流瑜伽 (id=2)');
console.log(' 测试方式: HTTP API (HMAC签名) + 小程序 UI');
console.log('════════════════════════════════════════════');
// 1. 通过 HTTP API 会员登录(使用 member/miniapp/login
try {
const loginResult = await api.memberLogin();
if (loginResult) {
authToken = loginResult.accessToken;
memberId = loginResult.memberId;
console.log(`[P2] 会员登录成功: memberId=${memberId}, token=${authToken.substring(0,30)}...`);
} else {
console.warn('[P2] 会员登录失败');
}
} catch (e) {
console.warn('[P2] 会员登录异常:', e.message);
}
// 2. 通过 HTTP API 验证课程存在 & 查询团课
if (authToken) {
try {
const courseRes = await api.getCourseDetail(COURSE_ID, authToken);
console.log(`[P2] 课程验证: status=${courseRes.status}`);
const searchRes = await api.searchCourses(COURSE_NAME, authToken);
const content = searchRes.data?.data?.content || searchRes.data?.content || [];
const records = Array.isArray(content) ? content : (content.records || []);
console.log(`[P2] 搜索"${COURSE_NAME}"结果: ${records.length} 个课程`);
const found = records.find(c => (c.courseName || c.name || '').includes(COURSE_NAME));
if (found) {
console.log(`[P2] 确认"${COURSE_NAME}"存在: id=${found.id}`);
}
} catch (e) {
console.warn('[P2] 课程验证失败:', e.message);
}
}
// 3. 连接/启动小程序
try {
miniProgram = await connectMiniProgram();
console.log('[P2] 已连接小程序');
} catch (e) {
console.log('[P2] 连接失败,尝试启动小程序...');
miniProgram = await launchMiniProgram();
}
await miniProgram.waitFor(3000);
}, 120000);
afterAll(async () => {
if (miniProgram) {
await screenshot(miniProgram, 'member_full_flow_final');
global.__MINI_PROGRAM__ = miniProgram;
}
});
// ════════════════════════════════════════════════════
// 阶段1:启动 & 登录
// ════════════════════════════════════════════════════
describe('阶段1:启动 & 登录', () => {
test('TC-FLOW-001: 小程序启动并检查登录状态', async () => {
const page = await miniProgram.currentPage();
console.log(`[TC-FLOW-001] 当前页面: ${page.path}`);
// 尝试通过 evaluate 检查登录状态
try {
const isLoggedIn = await miniProgram.evaluate(() => {
const store = require('../../store/index');
return store.isLoggedIn;
});
console.log(`[TC-FLOW-001] 登录状态: ${isLoggedIn}`);
} catch (e) {
console.log('[TC-FLOW-001] 无法检查登录状态:', e.message);
}
// 如果在登录页,尝试执行登录
if (page.path && page.path.includes('login')) {
console.log('[TC-FLOW-001] 当前在登录页,尝试登录...');
await screenshot(miniProgram, 'flow_login_page');
// 尝试点击微信登录按钮
try {
// 勾选协议
const agreeCheckbox = await page.$('.agree-checkbox');
if (agreeCheckbox) {
await agreeCheckbox.tap();
await miniProgram.waitFor(500);
}
// 点击登录按钮
const loginBtn = await page.$('.wechat-login-btn');
if (loginBtn) {
await loginBtn.tap();
console.log('[TC-FLOW-001] 已点击登录按钮');
await miniProgram.waitFor(3000);
}
} catch (e) {
console.warn('[TC-FLOW-001] 登录交互异常:', e.message);
}
}
// 尝试跳转到首页(无论登录是否成功)
try {
await miniProgram.reLaunch('/pages/index/index');
await miniProgram.waitFor(2000);
} catch (e) {
console.warn('[TC-FLOW-001] reLaunch 失败:', e.message);
}
const finalPage = await miniProgram.currentPage();
console.log(`[TC-FLOW-001] 最终页面: ${finalPage.path}`);
expect(finalPage.path).toBeTruthy();
await screenshot(miniProgram, 'flow_after_login');
}, 30000);
test('TC-FLOW-002: 首页加载并显示内容', async () => {
const page = await waitForPage(miniProgram, 'pages/index/index', 15000);
expect(page).toBeTruthy();
expect(page.path).toContain('pages/index/index');
const homePage = new HomePage(miniProgram);
const elements = await homePage.getElements();
console.log(`[TC-FLOW-002] 首页元素: 轮播图=${elements.bannerCount}, 课程卡片=${elements.courseCardCount}, 导航=${elements.navButtonCount}`);
await screenshot(miniProgram, 'flow_home');
});
});
// ════════════════════════════════════════════════════
// 阶段2:进入搜索页,查找"流瑜伽"
// ════════════════════════════════════════════════════
describe('阶段2:查询团课 — 搜索"流瑜伽"', () => {
test('TC-FLOW-003: 通过 HTTP API 验证课程搜索结果', async () => {
if (!authToken) {
console.warn('[TC-FLOW-003] 无 token,跳过 API 验证');
return;
}
const result = await api.searchCourses(COURSE_NAME, authToken);
expect(result.status).toBe(200);
const content = result.data?.data?.content || result.data?.content || result.data || [];
const records = Array.isArray(content) ? content : (content.records || []);
console.log(`[TC-FLOW-003] API 找到 ${records.length} 个匹配课程`);
// 验证至少找到流瑜伽
const found = records.find(c =>
(c.courseName || c.name || '').includes(COURSE_NAME)
);
if (found) {
console.log(`[TC-FLOW-003] 确认"${COURSE_NAME}"在搜索结果中, id=${found.id || found.courseId}`);
}
});
test('TC-FLOW-004: 导航到团课搜索页', async () => {
await switchTab(miniProgram, 1); // 搜索 tab
await miniProgram.waitFor(2000);
const page = await miniProgram.currentPage();
console.log(`[TC-FLOW-004] 当前页面: ${page.path}`);
expect(page.path).toContain('search');
await screenshot(miniProgram, 'flow_search_page');
});
test('TC-FLOW-005: 搜索"流瑜伽"课程', async () => {
const searchPage = new SearchPage(miniProgram);
// 输入搜索关键词
try {
await searchPage.searchCourses(COURSE_NAME);
await miniProgram.waitFor(2000);
console.log(`[TC-FLOW-005] 已搜索"${COURSE_NAME}"`);
} catch (e) {
console.warn('[TC-FLOW-005] 搜索输入异常:', e.message);
}
await screenshot(miniProgram, 'flow_search_result');
// 获取课程列表
const courseCount = await searchPage.getCourseCount();
console.log(`[TC-FLOW-005] 搜索结果数量: ${courseCount}`);
// 检查是否有课程
const page = await miniProgram.currentPage();
try {
const courseNames = await page.$$eval('.course-name', els => els.map(e => e.textContent));
console.log(`[TC-FLOW-005] 课程列表:`, courseNames);
} catch (e) {
// 获取文本可能失败
}
});
test('TC-FLOW-006: 点击"流瑜伽"进入课程详情', async () => {
const searchPage = new SearchPage(miniProgram);
const courseCount = await searchPage.getCourseCount();
if (courseCount === 0) {
console.warn('[TC-FLOW-006] 没有可点击的课程,尝试导航到课程详情页...');
try {
await miniProgram.navigateTo(`/pages/course-detail/course-detail?id=${COURSE_ID}`);
await miniProgram.waitFor(2000);
} catch (e) {
console.warn('[TC-FLOW-006] 直接导航失败:', e.message);
}
} else {
// 查找"流瑜伽"课程卡片并点击
const page = await miniProgram.currentPage();
let clicked = false;
try {
const cards = await page.$$('.course-card');
for (let i = 0; i < cards.length; i++) {
const nameEl = await cards[i].$('.course-name');
if (nameEl) {
const text = await nameEl.text();
if (text && text.includes(COURSE_NAME)) {
console.log(`[TC-FLOW-006] 找到"${COURSE_NAME}",索引: ${i}`);
await cards[i].tap();
clicked = true;
break;
}
}
}
} catch (e) {
console.warn('[TC-FLOW-006] 查找课程异常:', e.message);
}
if (!clicked) {
console.log('[TC-FLOW-006] 未找到目标课程,点击第一个课程');
await searchPage.clickCourse(0);
}
}
await miniProgram.waitFor(2000);
const page = await miniProgram.currentPage();
console.log(`[TC-FLOW-006] 当前页面: ${page.path}`);
await screenshot(miniProgram, 'flow_course_detail');
});
});
// ════════════════════════════════════════════════════
// 阶段3:预约"流瑜伽"
// ════════════════════════════════════════════════════
describe('阶段3:预约"流瑜伽"', () => {
test('TC-FLOW-007: 查看课程详情信息', async () => {
const page = await miniProgram.currentPage();
if (!page.path || !page.path.includes('course-detail')) {
console.warn('[TC-FLOW-007] 不在课程详情页,尝试导航...');
try {
await miniProgram.navigateTo(`/pages/course-detail/course-detail?id=${COURSE_ID}`);
await miniProgram.waitFor(2000);
} catch (e) {
console.warn('[TC-FLOW-007] 导航失败:', e.message);
}
}
const detailPage = new CourseDetailPage(miniProgram);
const courseInfo = await detailPage.getCourseInfo();
console.log('[TC-FLOW-007] 课程信息:', JSON.stringify(courseInfo));
// 验证课程信息包含关键字段
const currentPage = await miniProgram.currentPage();
const pagePath = currentPage.path || '';
console.log(`[TC-FLOW-007] 详情页路径: ${pagePath}`);
});
test('TC-FLOW-008: 通过 HTTP API 预约课程', async () => {
if (!authToken || !memberId) {
console.warn('[TC-FLOW-008] 无 token 或 memberId,跳过 API 预约');
return;
}
const result = await api.bookCourse(COURSE_ID, parseInt(memberId), authToken);
console.log(`[TC-FLOW-008] API 预约结果: status=${result.status}`);
if (api.isSuccess(result)) {
console.log('[TC-FLOW-008] API 预约成功');
} else if (result.status === 400) {
console.log('[TC-FLOW-008] 400 - 可能已预约或参数问题:', JSON.stringify(result.data).substring(0, 200));
} else if (result.status === 401 || result.status === 403) {
console.log('[TC-FLOW-008] 认证失败');
}
});
test('TC-FLOW-009: 在课程详情页点击预约按钮', async () => {
const page = await miniProgram.currentPage();
if (!page.path || !page.path.includes('course-detail')) {
console.warn('[TC-FLOW-009] 不在课程详情页');
return;
}
const detailPage = new CourseDetailPage(miniProgram);
// 尝试点击预约按钮
try {
const booked = await detailPage.bookCourse();
console.log(`[TC-FLOW-009] 预约按钮点击: ${booked ? '成功' : '失败'}`);
await miniProgram.waitFor(2000);
} catch (e) {
console.warn('[TC-FLOW-009] 预约异常:', e.message);
}
await screenshot(miniProgram, 'flow_after_book');
// 检查是否被重定向到登录页(未登录时)
const currentPage = await miniProgram.currentPage();
if (currentPage.path && currentPage.path.includes('login')) {
console.log('[TC-FLOW-009] 被重定向到登录页(可能需要先登录)');
}
});
});
// ════════════════════════════════════════════════════
// 阶段4:扫码签到
// ════════════════════════════════════════════════════
describe('阶段4:扫码签到', () => {
test('TC-FLOW-010: 导航到我的课程页', async () => {
// 先确保在首页
try {
await miniProgram.reLaunch('/pages/index/index');
await miniProgram.waitFor(1000);
} catch (e) {
// ignore
}
await switchTab(miniProgram, 2); // 我的课程 tab
await miniProgram.waitFor(2000);
const page = await miniProgram.currentPage();
console.log(`[TC-FLOW-010] 当前页面: ${page.path}`);
expect(page.path).toContain('my-courses');
await screenshot(miniProgram, 'flow_my_courses');
});
test('TC-FLOW-011: 通过 HTTP API 签到', async () => {
if (!authToken || !memberId) {
console.warn('[TC-FLOW-011] 无 token 或 memberId,跳过 API 签到');
return;
}
const result = await api.signIn(COURSE_ID, parseInt(memberId), authToken);
console.log(`[TC-FLOW-011] API 签到结果: status=${result.status}`);
if (api.isSuccess(result)) {
console.log('[TC-FLOW-011] API 签到成功');
} else {
console.log(`[TC-FLOW-011] 签到响应: ${JSON.stringify(result.data).substring(0, 200)}`);
}
});
test('TC-FLOW-012: 验证二维码数据格式', () => {
// 解析二维码数据验证格式
const qrData = JSON.parse(QR_CODE_DATA);
expect(qrData).toBeDefined();
expect(qrData.id).toBeDefined();
expect(qrData.courseName).toBe(COURSE_NAME);
console.log('[TC-FLOW-012] 二维码数据验证通过:');
console.log(` - 课程ID: ${qrData.id}`);
console.log(` - 课程名称: ${qrData.courseName}`);
console.log(` - 教练ID: ${qrData.coachId}`);
console.log(` - 地点: ${qrData.location}`);
console.log(` - 时间: ${qrData.startTime} ~ ${qrData.endTime}`);
console.log(` - 最大人数: ${qrData.maxMembers}`);
});
test('TC-FLOW-013: 尝试在小程序中 mock wx.scanCode 并签到', async () => {
const page = await miniProgram.currentPage();
if (!page.path || !page.path.includes('my-courses')) {
console.warn('[TC-FLOW-013] 不在我的课程页');
return;
}
// 检查是否有已预约的课程
const myCoursesPage = new MyCoursesPage(miniProgram);
const bookingCount = await myCoursesPage.getBookingCount();
console.log(`[TC-FLOW-013] 当前预约数量: ${bookingCount}`);
if (bookingCount === 0) {
console.warn('[TC-FLOW-013] 没有可签到的课程(可能未预约或需要登录)');
}
// 尝试 mock wx.scanCode
try {
// miniprogram-automator 支持 mockWxMethod
if (typeof miniProgram.mockWxMethod === 'function') {
console.log('[TC-FLOW-013] Mock wx.scanCode...');
await miniProgram.mockWxMethod('scanCode', {
result: QR_CODE_DATA,
scanType: 'QR_CODE',
charSet: 'UTF-8',
path: '',
rawData: Buffer.from(QR_CODE_DATA).toString('base64')
});
console.log('[TC-FLOW-013] wx.scanCode 已 mock');
} else {
console.log('[TC-FLOW-013] mockWxMethod 不可用,使用 evaluate 直接调用');
}
} catch (e) {
console.warn('[TC-FLOW-013] Mock wx.scanCode 失败:', e.message);
}
// 尝试通过 evaluate 直接调用签到 API
try {
const signInResult = await miniProgram.evaluate((courseId, qrData) => {
return new Promise((resolve) => {
try {
// 模拟 scanCode 返回结果
const store = require('../../store/index');
const memberId = store.getMemberInfo()?.memberId || store.getMemberInfo()?.id || 1;
// 调用签到 API
const bookingApi = require('../../api/booking');
bookingApi.signIn(courseId, memberId)
.then(data => resolve({ success: true, data }))
.catch(err => resolve({ success: false, error: err.message }));
} catch (e) {
resolve({ success: false, error: e.message });
}
});
}, COURSE_ID, QR_CODE_DATA);
console.log('[TC-FLOW-013] 签到 evaluate 结果:', JSON.stringify(signInResult, null, 2));
} catch (e) {
console.warn('[TC-FLOW-013] evaluate 签到失败:', e.message);
}
await screenshot(miniProgram, 'flow_after_signin');
});
});
// ════════════════════════════════════════════════════
// 阶段5:验证 & 总结
// ════════════════════════════════════════════════════
describe('阶段5:验证与总结', () => {
test('TC-FLOW-014: 验证签到后预约状态变化(API)', async () => {
if (!authToken || !memberId) {
console.warn('[TC-FLOW-014] 无 token 或 memberId,跳过验证');
return;
}
// 获取会员预约列表
try {
const bookingsRes = await api.getMemberBookings(parseInt(memberId), authToken);
console.log(`[TC-FLOW-014] 预约列表: status=${bookingsRes.status}`);
const bookings = bookingsRes.data?.data || bookingsRes.data || [];
const records = Array.isArray(bookings) ? bookings : (bookings.records || []);
console.log(`[TC-FLOW-014] 预约记录数: ${records.length}`);
if (records.length > 0) {
const latest = records[0]; // 最新的是第一个
console.log(`[TC-FLOW-014] 预约记录: courseName=${latest.courseName}, status=${latest.status}`);
}
} catch (e) {
console.warn('[TC-FLOW-014] 获取预约列表失败:', e.message);
}
});
test('TC-FLOW-015: 返回首页验证完整流程', async () => {
await switchTab(miniProgram, 0); // 首页
await miniProgram.waitFor(2000);
const page = await miniProgram.currentPage();
expect(page.path).toContain('pages/index/index');
console.log('');
console.log('════════════════════════════════════════════');
console.log('P2 - 完整流程测试结束');
console.log(' 已验证步骤:');
console.log(' 1. 小程序启动 & 登录');
console.log(' 2. 首页加载');
console.log(' 3. 搜索页导航');
console.log(' 4. 搜索"流瑜伽"');
console.log(' 5. 课程详情页');
console.log(' 6. 预约操作');
console.log(' 7. 我的课程页');
console.log(' 8. 二维码数据验证');
console.log(' 9. 签到操作');
console.log('════════════════════════════════════════════');
});
});
});